¿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;
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:
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í:
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.
¿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
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:
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 creaserver->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.
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.
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:
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.