¿Como enviar archivos a través de TCP?

Hola estoy tratando de enviar archivos entre dos PCs mediante QTcpSocket y no se como hacerlo. He realizado correctamente la conexión entre cliente y servidor y funciona, puedo enviar texto mediante socket->write("Enviando texto...!!"); pero a la hora de intentar enviar un archivo me es imposible. Para enviar un archivo lo intento de esta forma:
En el servidor.

    QByteArray data;
    QFile *file;
    file = new QFile("archivo.avi");
 
    file->open(QIODevice::ReadOnly);
 
    data = file->readAll();
    file->close();
    if(data.size()>0)
    {
        socket->write(data);
    }
    delete file;

Y para recibirlo, en el cliente hago, tras ser emitida la señal readyRead()
void ClassClient::readyRead()
{
    file->write(socket->readAll());
    file->close();
}

De esta forma consigo enviar archivos de pequeño tamaño, pero cuando el archivo.avi que quiero enviar es muy grande, tan solo recibo una parte de este, ¿Que estoy haciendo mal?

Gracias!

Saber el tamaño del archivo

Lycus tienes razón cada vez es más pequeño jaja

Ollarch, he estado mirando el ejemplo "threadedfortuneserver" y sustituye a lo que tenia programado sin la necesidad de llamar a moveToThread(), lo que no entiendo muy bien es como con tan solo indicarle el numero de socketDescriptor se crea un socket igual, leeré más acerca de ello.

Ahora me surge otra duda, he conseguido enviar archivos muy grandes a trozos con read() y write() pero el problema es que no sé como indicarle al receptor desde el principio, lo que ocupa el archivo a enviar, es decir que el receptor sepa desde un principio el tamaño del archivo que está recibiendo. He probado enviando primero un qint64 dentro de un QByteArray con write(QByteArray) y después el archivo a trozos, pero al hacer esto, no se recibe bien el archivo, no entiendo porque. También he probado usando QDataStream, pasandole el socket;

 QDataStream out(&socket); //Le indico como QIODevice el propio socket
 out << tamañoArchivo; //primero serializando el tamaño del archivo, es un qint64
 out << file.read(512); // a trozo voy serializando el archivo hasta el final

El problema es que a enviar los datos así no puedo saber a la velocidad que se estan transmitiendo. Me parece que la solución esta en no utilizar QDataStream, pero no se como hacerlo ni con una forma ni con la otra.

responder

Eviten usar muchas veces el boton "Responder" mejor usen la opcion "Añadir nuevo comentario" para que no pase como esto xD

¿Para enviar datos readyRead?

¿Te refieres a la hora de recivir los datos? En el cliente y en cada conexión (la clase conecction) del servidor, utilizo la señal readyRead(), pero a la hora de enviar los datos no tiene nada que ver esa señal, ¿me equivoco?

Hola, Cuando utilizas un

Hola,

Cuando utilizas un socket para enviar datos, estos no se reciven todos de golpe en el otro lado, estos se dividen en trocitos y se envian uno a uno(no voy a entrar en temas mas tecnicos como el buffer de la targeta de red, ...).
Cuando el receptor recive una cantidad INDEFINIDA de datos, salta la señal "readyRead", pero nadie te asegura de que recivas siempre el mismo número de bytes, ni que recivas todo el buffer de datos completo. Por esta razón debes de crearte un pequeño protocolo de transferencia.
Dicho esto, lo que haria yo es:
Envio:
-enviar el número de bytes que ocupa el fichero a recivir
-enviar el fichero

Recepción:
-leer el número de bytes a recivir: la primera vez que salta la señal "readyRead" recivimos este valor
-recivir cada trocito de datos hasta que tengamos tantos todos los bytes(sabemos cuantos hay ya que lo hemos recivido en la 1a lectura de datos)

Un saludo,

Ejemplo

Si, ya me imaginaba que no se recivirian todos los datos de golpe, pero no sabía como escribirlo en Qt.
¿Podrías poner un pequeño ejemplo?

Gracias

Hola, Deberias intentarlo tu

Hola,

Deberias intentarlo tu mismo y preguntar luego sobre los problemas que aparezcan. Mira el ejemplo de Qt "fortuneclient" donde la señal "readyRead" está conectada al slot "readFortune". Allí primero simpre espera a tener tantos bytes como "sizeof(quint16)". Cuando tengas este valor tienes la otra parte que solo es la de ir poniendo los datos recividos en un buffer hasta que este buffer tenga tantos bytes como te ha indicado el primer valor recivido.

Si lo he intentado

Ya lo he intentado, por eso pregunto. Lo que pasa es que no puse lo que probé.
Las dudas que me surgen son varias. Una es a la hora de enviar el archivo, he probado con esto:

    QByteArray data;
    QFile file("archivo.avi");//Este archivo ocupa más de 500 MegaBytes
 
    file.open(QIODevice::ReadOnly);
    QDataStream out(&data, QIODevice::WriteOnly);//Para serializar
    out.setVersion(QDataStream::Qt_4_0);
 
    out << (quint16)0;
    out << file.readAll();
    out.device()->seek(0);
    out << (quint16)(data.size() - sizeof(quint16));
 
    file.close();
 
    socket->write(data);//socket es QTcpSocket

Pero cuando intento enviar ese archivo el programa termina inesperadamente, ¿por qué?
Por otro lado, los ejemplos "fortuneServer" y "fortuneClient" no los he entendido bien, voy a seguir dandole vueltas para ver si saco algo en claro.

Gracias por tu respuesta.

Hola,- Mira la documentación

Hola,
- Mira la documentación de QDataStream dónde hay un par de ejemplos de como se utiliza.

- Utilizas un QByteArray para meter, primero un 0(no sé el porqué), luego el contenido del fichero y finalmente intentas posicionarte al principio otra vez para reescrivir el tamaño de ??
Utiliza "QFile::size()" y ojo, devuelve un "qint64" y tu estás convirtiendolo a un "quint64". Si el tamaño del fichero supera a "sizeof(quint64)" nunca recivirás todo el fichero.

- Si el programa se detiene de forma inesperada será que algún error hay al acceder a memoria no declarada, ... Utilizas algún depurador? Sin un depurador es difícil encontrar errores.

Así rapidito creo que seria algo así:

    QFile file("archivo.avi");//Este archivo ocupa más de 500 MegaBytes
     file.open(QIODevice::ReadOnly);
    QDataStream out(&socket, QIODevice::WriteOnly);//Para serializar
    out.setVersion(QDataStream::Qt_4_0);
    out << file.size();
    out << file.readAll();

Jajaja Yo tampoco se porque

Jajaja Yo tampoco se porque el 0 ni lo del final. Esque lo que llevo intentando estos dias es esto, parece que es lo mismo que tu me dices.

QByteArray data;
    QFile file("archivo.avi");//Este archivo ocupa más de 500 MegaBytes
    file.open(QIODevice::ReadOnly);
    QDataStream out(&data, QIODevice::WriteOnly);//Para serializar
    out.setVersion(QDataStream::Qt_4_0);
    out << file.size();
    out << file.readAll();
 
    file.close();
 
    socket->write(data);//socket es QTcpSocket

¿Pero porque en tu ejemplo pasas &socket a out? El caso es que lo que te acabo de escribir hace que se me cierre el programa, pero con archivos pequeños no... No lo entiendo!!

No he utilizado un depurador por ahora.

Hola, Pues empieza a utilizar

Hola,

Pues empieza a utilizar un depurador. Sin esta herramienta, saber que está fallando puede ser una tarea casi imposible. En el depurador, cuando el programa realiza alguna operación inválida y se detiene, puedes mirar el "call stack" dónde puedes ver la pila de llamadas a funciones hasta la que provocó el fallo y analizar los valores de las variables.

Un QTCPSocket hereda de un QIODevice por lo que puede pasarse al constructor del QDataStream.

El socket está inicializado? Está conectado al cliente?

Yo empiezaria por utilizar el depurador ya que vas a dar palos de ciego.

Sigo sin conseguirlo

Creo que el error esta en out << file.readAll(); pero no se el porqué.
El socket se le puede pasar al constructor de esta forma QDataStream out(&socket); por lo que he estado mirando.
Si, el socket está conectado al cliente perfectamente, he probado y puedo enviar texto. No se como enviar ese archivo, ¿Alguna otra idea? Bueno, seguiré buscando información.

Gracias

Hola, Has utilizado el

Hola,

Has utilizado el depurador? Como sabes que el problema está en "out << file.readAll();"?
Prueba con "out << file.readLine();"

radAll() es el problema

He estado estos días algo ocupado, lo se porque no se bloquea si comento esa línea :-)
No he utiilizado el depurador, no me aclaro con el depurador de QtCreator. ¿Como puedo utilizarlo? El problema creo que está en que radAll() lo lee todo y si pasa de cierta cantidad de bytes, se satura, por lo menos es lo que intuyo, ya que con archivos de imágenes que ocupan unos pocos megas, me funciona a la perfección. Creo que en este caso el depurador no me va a ayudar mucho, el problema está en como utilizar los diferentes métodos de Qt.

Hola, Cuando llamas a

Hola,

Cuando llamas a "readAll()" se está cargando TODO el fichero a memoria. Si tienes 1GB de RAM libre y el fichero ocupa 2GB, no podrás cargar el fichero a memória(sin tener en cuenta la memória de intercanvio o swap) y la aplicación simplemente se detiene.
Puedes hacer un bucle con "read(10240)" hasta que devuelva 0 bytes leídos y con cada lectura lo envias por el socket. De esta forma leeras solamente 10Mb en cada lectura.
El tema del depurador, mira en la web de qt hay videos dónde enseñan a utilizar Qt Creator.
Es muy recomendable que utilizes el depurador. Te va a llevar unos días pero luego vas a encontrar los errores de tu código mucho mas rápido y sin dar palos de ciego.

Si, debe ser con read()

Si, es esa tiene que ser la solución, leer poco a poco, pero no lo entiendo, según la documentación oficial QByteArray QIODevice::read ( qint64 maxSize ) dice que lee como mucho maxSize y devuelve los datos leidos en forma de QByteArray ¿debo suponer que la segunda, tercera, cuarta, etc. vez que llame a esa funcion empieza a leer desde donde leyó la última vez? además, si voy enviando trozos, en el destino, ¿como los une en el orden correcto?

Hola, "read(qint64)" empezará

Hola,

"read(qint64)" empezará a leer dónde acabó la última vez. Si has trabajado en C es como hacer un "read" y un"seek".
A la segunda pregunta. En la segunda aplicación estás esperando N bytes a recibir, que mas da si lo haces por partes, igualmente el protocolo TCP va a trocear los paquetes(no se envia un fichero de 1GB en un solo paquete). Por lo tanto, el receptor está esperando a recivir el tamaño del fichero que previamente les has enviado en el primer parámetro, no?

Funciona pero se bloquea la GUI

Haciendo esto funciona, aunque tendré que enviar de alguna forma lo que ocupa el archivo para que el receptor lo sepa, supongo que serializando podré, por ahora eso no es un problema, lo solucionaré en un futuro.

Código

    QByteArray data_to_send;
    qint64 size = 10240;//en bytes
    qint64 current_size = size;//La utilizo para saber lo que tengo enviado.
    qint64 file_size = file.size();//Tamaño del archivo
 
    data_to_send = file.read(size);//esta función no devuelve errores.
 
    while(current_size < file_size)//Hasta que no envie todo el archivo...
    {
        socket->write(data_to_send);//socket es QTcpSocket
 
        if(!socket->waitForBytesWritten(10000))//Congela la GUI del programa, si hay un error esto devuelve false.
            qDebug() << "error inesperado...";
 
        data_to_send = file.read(size);
        current_size += 10240;
    }
    qDebug() << "finished...";
 
    file.close();

El problema que me preocupa ahora es que al utilizar waitForBytesWritten() se congela la pantalla del programa.

Además lo indica en la documentación.
Warning: Calling this function from the main (GUI) thread might cause your user interface to freeze.

Lo que he pensado es utilizar QThread para evitarlo, pero no consigo hacerlo, siempre me sale:
QObject: Cannot create children for a parent that is in a different thread.

¿Como puedo crear la clase QTcpServer en el hilo de QThread?

He probado con:

class ClassServer : public QThread
{
    Q_OBJECT
public:
    ClassServer(QObject *parent = 0);
    ~ClassServer();
 
    //void startServer(QHostAddress address);
 
signals:
 
private slots:
    void newConnection();
    void destroyedConnetion(Connections*);
 
private:
    QList <Connections*> connectionsList;
    QTcpServer *server;
 
protected:
    void run();
 
};

ClassServer::ClassServer(QObject *parent)
    :QThread(parent)
{
    server = new QTcpServer(this);
    connect(server, SIGNAL(newConnection()), this, SLOT(newConnection()));
}
 
void ClassServer::newConnection()
{
    Connections *connection;
    connection = new Connections(this,server->nextPendingConnection());//Esta conexión la eliminare tras finalizar la comunicación. nextPendingConnection crea un QTCPsocket como hijo de server.
 
    connectionsList << connection;
 
    connect(connection,SIGNAL(finished(Connections*)),this,SLOT(destroyedConnetion(Connections*)));
}
.
.
.
 
void ClassServer::run()
{
    server->listen(QHostAddress::Any,TCP_PORT);
    exec();
}

y en la clase Connections en el constructor empezar a enviar el archivo con el código que te he mostrado al principio. Parece que el problema es que el objeto que está en QTcpServer *server; se ha creado en otro hilo que no es QThread. He probado a utilizar  :moveToThread ( QThread * targetThread ) con el QTcpSocket que crea  server->nextPendingConnection() pero como este es creado como hijo de server, no me deja moverlo de hilo. ¿Como puedo solucionarlo? ¿Alguna idea?

Hola,Puedes intentar enviar

Hola,

Puedes intentar enviar cada vez X bytes en lugar de todo los datos. En cada vuelta del bucle llama a la función "QApplication::processEvents()" para asegurar que el bucle de mensages de Qt se ejecuta.

No se como utilizar lo que

No se como utilizar lo que dices "QApplication::processEvents()", leeré la documentación ya que en la clase que tengo declarado el server no funciona.

Por ahora he escrito esto.

server = new ClassServer();
    server->moveToThread(server);

y funciona, server es declarado en una clase diferente, lo muevo a su propio hilo.... no lo entiendo muy bien, pero funciona

Hola, No utilizes un

Hola,

No utilizes un thread.
Haz un bucle donde vas leiendo del fichero cada vez X bytes(por ejemplo 512). Después de leerlos los envias sin el "waitForBytesWritten" y después de enviarlo llamas a "QApplication::processEvents()". Finalizas el bucle cuando no haya mas bytes para leer del fichero.

Problema con la memoria.

Si hago lo que me dices, primero leer el fichero y después enviarlo tengo un problema de memoria. Si leo el fichero de esta forma  QByteArray datos_a_enviar = file.read(10240); dentro de un bucle hasta leer todo; si el archivo ocupa más de 512 MegaBytes, este no cabe en el QByteArray y la aplicación se cierra. Si en vez de leerlo primero, leeo un trozo y lo envío con write(), también hay un problema de memoría, ya que si no utilizo waitForBytesWritten nadie me asegura que el receptor recibe cada paquete del archivo a tiempo, con lo que write lo almacena en memoría y en segundo plano lo envía, si el receptor es muy lento, llegará un momento que yo tendré muchos bytes esperando a ser enviados por write() en memoría, hasta que al final se colapse y la aplicación da error. ¿me he explicado bien?

Este es el código de lo segundo que te he comentado.

    QByteArray data_to_send;
    qint64 size = 10240;//en bytes
    qint64 current_size = size;//La utilizo para saber lo que tengo enviado.
    qint64 file_size = file.size();//Tamaño del archivo
 
    data_to_send = file.read(size);//esta función no devuelve errores.
 
while(current_size < file_size)//Hasta que no envie todo el archivo...
    {
        socket->write(data_to_send);//socket es QTcpSocket, aquí, si el receptor es lento, yo tendré más bytes en memoría que se envian en segundo plano y eso es un problema, porque mi memoría no es infinita.
        QApplication::processEvents();//Esto soluciona lo de la congelación de la GUI.
        data_to_send = file.read(size);
        current_size += 10240;
    }//Este bucle termina bastante rápido, pero write sigue funcionando en segundo plano.

para saber los bytes que han sido escritos en el receptor uso la señal, ya que el bucle while no aporta esta información.

 bytesWritten(qint64 bytes)

¿Como puedo saber si han sido escritos los bytes si llamar a waitForBytesWritten() y así no utilizar un QThread?

*Quizás este problema no se aprecie en archivos pequeños y/o en archivos grandes en los que el receptor y el emisor tienen gran velocidad de transmisión de datos.

ACTUALIZADO:
He probado utilizando los dos métodos pero sigo con problemas:

waitForBytesWitten();
QApplication::processEvents();

ya que si el receptor es muy lento, waitForBytesWitten bloquea la GUI.

error: incomplete type 'QApplication' used in nested name specif

Si hago lo que me dices me muestra:

error: incomplete type 'QApplication' used in nested name specifier

#include <QApplication>

Solucionado con un #include a ver si me funciona el server ahora...

La primera parte, te he dicho

La primera parte, te he dicho que vayas leyendo y enviando el fichero a trozos, no que leas el fichero a trozos, lo pongas todo en un QByteArray y luego lo envies(en este caso es como leerlo todo de golpe).
Dices que si el receptor es muy lento, pero estás enviando paquetes de 1 MegaByte. Prueba a enviarlos de 512 bytes por ejemplo.

Pero probar con 512 no me

Pero probar con 512 no me parece una solución (en ip se suelen enviar 1500 bytes, con lo que probaré partiendolo en un poco menos por lo de la cabecera, pero eso no soluciona el problema), mi idea es que la aplicacion no se bloquee en ningún caso, me parece que el QThread es lo mejor por ahora.

512 es un valor de EJEMPLO!

512 es un valor de EJEMPLO! Ya se que es tipico que en red se envien paquetes de 1500 bytes,te diré mas, activando los Jumbo Frames puedes enviar paquetes de 16KB(0,015625MB), pero estavas intentando enviar 1MB(1024KB) de golpe!
Lo que me dices de que el cliente lee muy lento: poniendo el peor de los casos, en una red a 10Mb/s, tenemos 1.25MB/segundo. Enviando como hacías cada vez 1MB, el servidor esperará 1 segundo a que el receptor tenga 1MB. Si el fichero es de 10 MB, tardará 10 segundos pero a cada envio estará bloqueado durante 1 segundo donde el GUI no responde.

Si sigues con la idea de utilizar un thread, mira el ejemplo de Qt "threadedfortuneserver".

En IP el valor máximo de la

En IP el valor máximo de la MTU es 65.536 bytes, pero solo se envian más de 576 si se sabe que la red puede trabajar con más(en ethernet 1500). Las velocidades que me dices son teóricas, eso es en el mejor de los casos. Digo un receptor lento porque estoy modificando la velocidad del receptor para probar todas las posibilidades y tengo problemas si no uso el waiForBytesWritten().

Miraré el ejemplo.

Anuncios Google