Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use QTcpSocket for high frequent sending of small data packages?

We have two Qt applications. App1 accepts a connection from App2 through QTcpServer and stores it in an instance of QTcpSocket* tcpSocket. App1 runs a simulation with 30 Hz. For each simulation run, a QByteArray consisting of a few kilobytes is sent using the following code (from the main/GUI thread):

    QByteArray block;
    /* lines omitted which write data into block */
    tcpSocket->write(block, block.size());
    tcpSocket->waitForBytesWritten(1);

The receiver socket listens to the QTcpSocket::readDataBlock signal (in main/GUI thread) and prints the corresponding time stamp to the GUI.

When both App1 and App2 run on the same system, the packages are perfectly in sync. However when App1 and App2 are run on different systems connected through a network, App2 is no longer in sync with the simulation in App2. The packages come in much slower. Even more surprising (and indicating our implementation is wrong) is the fact that when we stop the simulation loop, no more packages are received. This surprises us, because we expect from the TCP protocol that all packages will arrive eventually.

We built the TCP logic based on Qt's fortune example. The fortune server, however, is different, because it only sends one package per incoming client. Could someone identify what we have done wrong?

Note: we use MSVC2012 (App1), MSVC2010 (App2) and Qt 5.2.

Edit: With a package I mean the result of a single simulation experiment, which is a bunch of numbers, written into QByteArray block. The first bits, however, contain the length of the QByteArray, so that the client can check whether all data has been received. This is the code which is called when the signal QTcpSocket::readDataBlock is emitted:

    QDataStream in(tcpSocket);
    in.setVersion(QDataStream::Qt_5_2);

    if (blockSize == 0) {
      if (tcpSocket->bytesAvailable() < (int)sizeof(quint16))
         return; // cannot yet read size from data block

      in >> blockSize; // read data size for data block
    }

    // if the whole data block is not yet received, ignore it
    if (tcpSocket->bytesAvailable() < blockSize)
      return;

    // if we get here, the whole object is available to parse
    QByteArray object;
    in >> object;

    blockSize = 0; // reset blockSize for handling the next package

    return;
like image 817
c_k Avatar asked Nov 26 '14 11:11

c_k


1 Answers

The problem in our implementation was caused by data packages being piled up and incorrect handling of packages which had only arrived partially.

The answer goes in the direction of Tcp packets using QTcpSocket. However this answer could not be applied in a straightforward manner, because we rely on QDataStream instead of plain QByteArray.

The following code (run each time QTcpSocket::readDataBlock is emitted) works for us and shows how a raw series of bytes can be read from QDataStream. Unfortunately it seems that it is not possible to process the data in a clearer way (using operator>>).

    QDataStream in(tcpSocket);
    in.setVersion(QDataStream::Qt_5_2);

    while (tcpSocket->bytesAvailable())
    {
        if (tcpSocket->bytesAvailable() < (int)(sizeof(quint16) + sizeof(quint8)+ sizeof(quint32)))
            return; // cannot yet read size and type info from data block

        in >> blockSize;
        in >> dataType; 

        char* temp = new char[4]; // read and ignore quint32 value for serialization of QByteArray in QDataStream       
        int bufferSize = in.readRawData(temp, 4);
        delete temp;
        temp  = NULL;

        QByteArray buffer;

        int objectSize = blockSize - (sizeof(quint16) + sizeof(quint8)+ sizeof(quint32));

        temp = new char[objectSize];            
        bufferSize = in.readRawData(temp, objectSize);
        buffer.append(temp, bufferSize);
        delete temp;
        temp  = NULL;

        if (buffer.size() == objectSize)
        {
            //ready for parsing             
        }
        else if (buffer.size() > objectSize)
        {
            //buffer size larger than expected object size, but still ready for parsing
        }
        else
        {
            // buffer size smaller than expected object size
            while (buffer.size() < objectSize) 
            {               
                tcpSocket->waitForReadyRead();
                char* temp = new char[objectSize - buffer.size()];          
                int bufferSize = in.readRawData(temp, objectSize - buffer.size());
                buffer.append(temp, bufferSize);
                delete temp;
                temp  = NULL;
            }
            // now ready for parsing
        }
        if (dataType == 0) 
        {               
            // deserialize object               
        }

    }

Please not that the first three bytes of the expected QDataStream are part of our own procotol: blockSize indicates the number of bytes for a complete single package, dataType helps deserializing the binary chunk.

Edit For reducing the latency of sending objects through the TCP connection, disabling packet bunching was very usefull:

    // disable Nagle's algorithm to avoid delay and bunching of small packages
    tcpSocketPosData->setSocketOption(QAbstractSocket::LowDelayOption,1);
like image 169
c_k Avatar answered Nov 08 '22 15:11

c_k