如何使用QTcpSocket高频率发送小数据包?

c_k*_*c_k 5 sockets qt tcp qtcpsocket

我们有两个Qt应用程序.App1接受来自App2的连接,QTcpServer并将其存储在一个实例中QTcpSocket* tcpSocket.App1运行30 Hz的模拟.对于每次模拟运行,QByteArray使用以下代码(来自主/ GUI线程)发送包含几千字节的数字:

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

接收器套接字侦听QTcpSocket :: readDataBlock信号(在主/ GUI线程中)并将相应的时间戳打印到GUI.

当App1和App2在同一系统上运行时,这些包完全同步.但是,当App1和App2在通过网络连接的不同系统上运行时,App2不再与App2中的模拟同步.包裹的速度要慢得多.更令人惊讶的是(并且表明我们的实现是错误的)事实是当我们停止模拟循环时,不再接收到包.这让我们感到惊讶,因为我们期望从TCP协议中最终到达所有包.

我们基于Qt的财富示例构建了TCP逻辑.然而,财富服务器是不同的,因为它每个传入的客户端只发送一个包.有人可以确定我们做错了什么吗?

注意:我们使用MSVC2012(App1),MSVC2010(App2)和Qt 5.2.

编辑:对于一个包我的意思是单个模拟实验的结果,这是一堆数字,写入QByteArray block.但是,第一位包含QByteArray的长度,以便客户端可以检查是否已收到所有数据.这是发出信号QTcpSocket :: readDataBlock时调用的代码:

    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;
Run Code Online (Sandbox Code Playgroud)

c_k*_*c_k 5

我们实现中的问题是由于数据包被堆积和错误处理只是部分到达的包.

答案是使用QTcpSocket指向Tcp数据包的方向.然而,这个答案不能以直截了当的方式应用,因为我们依赖QDataStream而不是简单QByteArray.

以下代码(每次QTcpSocket::readDataBlock发出运行)适用于我们,并显示如何读取原始字节序列QDataStream.不幸的是,似乎无法以更清晰的方式处理数据(使用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               
        }

    }
Run Code Online (Sandbox Code Playgroud)

请注意,预期的前三个字节QDataStream是我们自己的procotol的一部分:blockSize表示完整的单个包的字节数,dataType有助于反序列化二进制块.

编辑 为了减少通过TCP连接发送对象的延迟,禁用数据包聚合非常有用:

    // disable Nagle's algorithm to avoid delay and bunching of small packages
    tcpSocketPosData->setSocketOption(QAbstractSocket::LowDelayOption,1);
Run Code Online (Sandbox Code Playgroud)