Read from QTcpSocket using QDataStream

sashoalm picture sashoalm · Oct 30, 2013 · Viewed 17.7k times · Source

I need to send binary data through a QTcpSocket. I was thinking about using QDataStream, but I've encountered a problem - it silently fails if no data has arrived at the time I try to read.

For example if I have this code:

QString str;
stream >> str;

It will fail silently if no data is currently there in the socket. Is there a way to tell it to block instead?

Answer

Marek R picture Marek R · Oct 30, 2013

The problem is a bit more serious. Socket can receive data in chunks, so even if you will wait for waitForReadyRead it can fail since there is not enough data to immediately read some object.
To solve this problem you have to send a size of data first then actual data. Send code:

QByteArray block;
QDataStream sendStream(&block, QIODevice::ReadWrite);
sendStream << quint16(0) << str;

sendStream.device()->seek(0);
sendStream << (quint16)(block.size() - sizeof(quint16));

tcpSocket->write(block);

On receiver you have to wait until size of available data is meets requirement. Receiver code looks more or less like that:

void SomeClass::slotReadClient() { // slot connected to readyRead signal of QTcpSocket
    QTcpSocket *tcpSocket = (QTcpSocket*)sender();
    QDataStream clientReadStream(tcpSocket);

    while(true) {
        if (!next_block_size) {
            if (tcpSocket->bytesAvailable() < sizeof(quint16)) { // are size data available
                break;
            }
            clientReadStream >> next_block_size;
        }

        if (tcpSocket->bytesAvailable() < next_block_size) {
            break;
        }
        QString str;
        clientReadStream >> str;

        next_block_size = 0;
    }
}


small update, based on documentation it is possible to read QString without adding extra size information, since QString passed to QDataStream contains size information. Size can be verified like that:

void SomeClass::slotReadClient() { // slot connected to readyRead signal of QTcpSocket
    QTcpSocket *tcpSocket = (QTcpSocket*)sender();
    while(true) {
        if (tcpSocket->bytesAvailable() < 4) {
           break;
        }
        char buffer[4]
        quint32 peekedSize;
        tcpSocket->peek(buffer, 4);
        peekedSize = qFromBigEndian<quint32>(buffer); // default endian in QDataStream
        if (peekedSize==0xffffffffu) // null string
           peekedSize = 0;
        peekedSize += 4;
        if (tcpSocket->bytesAvailable() < peekedSize) {
           break;
        }
        // here all required for QString  data are available
        QString str;
        QDataStream(tcpSocket) >> str;
        emit stringHasBeenRead(str);
     }
}