Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QTcpSocket : readRead() not being triggered when data is written on the socket



  • Hi,

    So I have a weird behavior trying to stream data over TCP. Basically, this is what happens:

    • The client successfuly connects to the server and sends an initial request to start the data stream.
    • The server starts sending the data stream packets through multiple write calls on the socket.
    • The client is waiting on waitForReadyRead(). The first time data is available (first readyRead()) everything goes as expected.
    • However, afterwards, readyRead() is never triggered again even though the server keeps writing on the socket.

    Here is the code (with the processing of the data replaced by a simple console output):

    • Client connecting to the server and passing the socket descriptor:
    void AccelStreamThread::run()
    {
    	// create the socket
    	QTcpSocket *wTcpSocket = new QTcpSocket();
    	connect(wTcpSocket, SIGNAL(disconnected()), this, SLOT(connectionClosedByServer(QTcpSocket *socket)));
    	connect(wTcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(error(QTcpSocket *socket)));
    	
    	// connect to the server
    	connectToServer(wTcpSocket, mMclpcIpAddress, mMclpcPort);
    	
    	// send the initial request
    	sendRequest(wTcpSocket, mClientRequest);
    
    	QDataStream wIn(wTcpSocket);
    	wIn.setVersion(QDataStream::Qt_DefaultCompiledVersion);
    
    	forever
    	{
     		QVector<QPair<float, float>> wAccelData;
    		
    		// whenever data is available in the socket
    		if (wTcpSocket->waitForReadyRead(3000))
    		{
    			std::cout << "Client inside waitForReadyRead." << std::endl;
    
    			//emit accelStreamThreadResultsRdy(wAccelData);
    		}
    	}
    }
    
    • Here are the connectToServer and sendRequest methods:
    bool AccelStreamThread::connectToServer(QTcpSocket *socket, QString ipAdress, QString port)
    {
    	if (socket != NULL)
    	{
    		socket->connectToHost(ipAdress, port.toInt());
    		if (socket->waitForConnected(3000))
    		{
    			qDebug() << "Client connected to server.";
    			return true;
    		}
    		else
    			qDebug() << "Client couldn't connect to server.";
    	}
    	return false;
    }
    
    void AccelStreamThread::sendRequest(QTcpSocket *socket, QString request)
    {
    	QByteArray wBlock;
    	QDataStream wOut(&wBlock, QIODevice::WriteOnly);
    	wOut.setVersion(QDataStream::Qt_DefaultCompiledVersion);
    	wOut << quint16(0) << request;
    	
    	wOut.device()->seek(0);
    	wOut << quint16(wBlock.size() - sizeof(quint16));
    	socket->write(wBlock);
    }
    
    • Whenever the server receives an incoming connection, this is what happens:
    void TCPServer::incomingConnection(qintptr handle)
    {
    	ClientSocket* wSocket = new ClientSocket(this);
    	wSocket->setSocketDescriptor(handle);
    }
    

    And then the data is written from the server to the client through this SLOT which is called multiple times (loop).

    void ClientSocket::AccelStreamTaskResult(QVector<QPair<float, float>> accelData)
    {
    	QByteArray wBlock;
    	QDataStream wOut(&wBlock, QIODevice::WriteOnly);
    	wOut.setVersion(QDataStream::Qt_DefaultCompiledVersion);
    	
    	wOut << quint16(0);
    	wOut << accelData.front();
    	wOut.device()->seek(0);
    	wOut << quint16(wBlock.size() - sizeof(quint16));
    	
    	std::cout << "Server calling write function" << std::endl
    	write(wBlock);
    }
    

    Now, what happens is that the server will start firing many write() calls through AccelStreamTaskResult reflected by many console outputs of "Server calling write function". However, the client will only pass the if(waitForReadyRead()) gate once, therefore only outputing "Client inside waitForReadyRead." once.

    Any ideas as to what is happening?

    Thank you very much!



  • Sure,

    Ask yourself where do you think you are actually writing the data to the client socket?

    QDataStream::QDataStream(QIODevice *d)

    You are just writing to some QByteArray, you want to call the constructor which asks for a ptr to QIODevice i.e. your client socket.
    You actually did the QDataStream right on the client side.

    Also I would use readyRead signal from the socket rather than use a thread's run method.


  • Qt Champions 2019

    Don't use a blocking connection when waiting for data but signals and slots.
    Then your QIODevice::seek() does not work: https://doc.qt.io/qt-5/qiodevice.html#details


  • Lifetime Qt Champion

    Hi,

    One thing you did not share was the code reading the data received.

    Why are you using the blocking API and then mixing in the synchronous API ?



  • @SGaist

    Hi, so I'm fairly new to both QT and Tcp programming so perhaps the way I'm seeing things is completely wrong.

    Basically, the idea is that the client will receive data packets at a very fast rate from the server that it needs to update live on a graph. So I thought that I would have the receive function in a separate thread. This is what happens: whenever the client sends the request to start the data streaming, it spawns a new thread that receives the data and then emits a SIGNAL for the client to process it and display the results. However, inside the newly created thread, I had to waitForReadyRead otherwise I don't know if data is available on the socket...

    Feel free to suggest another way to do this as it might not be the best way to do it...



  • @RBLL
    Because QTcpSocket is already implemented asynchronously for you, you don't need the overhead or complication of your own separate threads or signals. And no need to call the unattractive waitForReadyRead.

    Dump your separate thread. Just use signal https://doc.qt.io/qt-5/qiodevice.html#readyRead from your GUI thread to act on data received.



  • @JonB Ah thank you very much. I guess I was over complicating things for no reason. Thanks I'll try it out!



  • @RBLL
    Yep, give it a go. Hopefully it keeps up, come back if it is does not, that's a separate issue.

    I also see you seem to be receiving "messages" back to your client. readyRead signal will activate (potentially) even with just one byte back, it makes no guarantee the number of bytes you'll get in one go will be equal to a complete sent message. Your client code is responsible for buffering received data to implement this. To save you writing it, look at https://doc.qt.io/qt-5/qiodevice.html#startTransaction, where Qt has the code to implement this for you.

    I think the example https://doc.qt.io/qt-5/qtnetwork-fortuneclient-example.html uses transactions, it's worth looking at.



  • @JonB
    Ah thats a fair point. I always assumed that at least the number of bytes would make it. I'll look into transactions!

    However, I still have the issue (even after removing the client thread and only usind readyRead) that the readyRead signal is only triggered once. I'll keep investigating. The issue might be from the server. I'll keep you posted.

    Thank you very much for you help!



  • @RBLL said in QTcpSocket : readRead() not being triggered when data is written on the socket:

    that the readyRead signal is only triggered once

    Again (although it may well be your code, leave you to investigate), just as readyRead might fire with just the first byte of a "message", it might also fire just once with hundreds of bytes across multiple messages if multiple messages are sent (while developing/debugging, see bytesAvailable from readyRead). That's why transactions can help you. You will need to play and see how it behaves in your environment.

    EDIT P.S. Also, ISTR readyRead will not fire again until you have done something like readAll to read the available bytes from the (internal) buffer.


  • Lifetime Qt Champion

    The edited part of @JonB is important, you have to read the data.