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

5
我们有两个Qt应用程序。App1通过QTcpServer接受来自App2的连接,并将其存储在QTcpSocket* tcpSocket实例中。App1以30 Hz运行模拟。对于每个模拟运行,使用以下代码(来自主/GUI线程)发送由几千字节组成的QByteArray
    QByteArray block;
    /* lines omitted which write data into block */
    tcpSocket->write(block, block.size());
    tcpSocket->waitForBytesWritten(1);

接收器套接字监听QTcpSocket :: readDataBlock信号(在主/ GUI线程中),并将相应的时间戳打印到GUI上。
当App1和App2在同一系统上运行时,数据包完全同步。但是,当App1和App2在通过网络连接的不同系统上运行时,App2与App2中的模拟不再同步。数据包进入得更慢。更令人惊讶的是(并表明我们的实现有误),当我们停止模拟循环时,就不会再收到数据包了。这让我们感到惊讶,因为我们期望TCP协议会最终收到所有数据包。
我们基于Qt的fortune example构建了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;

2
TCP没有数据包 - 它只是一个字节流。你是如何切分数据的?也就是说,你的接收应用程序如何知道你自己的数据“数据包”何时开始和结束?此外,TCP具有流量控制功能,因此如果停止读取数据,则TCP堆栈中的缓冲区将填满,并且不会发送更多数据(并且在尝试发送时发送方将被阻塞,除非您的套接字处于非阻塞模式)。但是,当我们不知道您在做什么,或者您的仿真工作方式或者它如何变得“不同步”时,很难猜测您做错了什么。 - nos
我尝试通过编辑我的原始问题来回答你的问题,请看一下。 - c_k
如果例如您的块大小为1024,但tcpSocket->bytesAvailable()恰好有1222个字节可用,则似乎存在读取比所需更多数据的风险。从外观上看,您会同时读取下一个块的开头-因此可能需要改用in.readBytes(),在那里您可以指定要读取的数量(并在之后验证它实际上读取了那么多数据)。但是这也取决于您的协议是如何的。例如,如果它是一个简单的请求/响应,其中您的发送方永远不会在收到前一个数据包的回复之前发送任何数据,则当前代码似乎没有问题。 - nos
可能是[使用QTcpSocket发送TCP数据包]的重复问题(https://dev59.com/YoXca4cB1Zd3GeqPECaO)。 - TheDarkKnight
@nos:我检查了一下,你说得对,tcpSocket->bytesAvailable()确实大于blockSize。 @Merlin069:这个问题并不是完全重复的,答案或多或少是相同的。但我不能将答案应用到我的情况中,因为我们使用的是无法像QByteArray那样解析和追加的QDataStream。我必须使用QDataStream,因为App1和App2在不同的平台上。我尝试过从https://dev59.com/YoXca4cB1Zd3GeqPECaO中的等待循环中使用`in.device()->seek()`进行重新解析,但是由于设备不是随机访问的,所以这是不允许的。 - c_k
显示剩余3条评论
1个回答

5
我们实现中的问题是由于数据包堆积和部分到达的包处理不正确引起的。
答案走向Tcp packets using QTcpSocket。然而,我们使用的是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               
        }

    }

请注意,期望 QDataStream 的前三个字节是我们自己协议的一部分: blockSize 表示完整单个数据包的字节数,dataType 有助于反序列化二进制块。 编辑为减少通过TCP连接发送对象时的延迟,禁用数据包批处理非常有用:
    // disable Nagle's algorithm to avoid delay and bunching of small packages
    tcpSocketPosData->setSocketOption(QAbstractSocket::LowDelayOption,1);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接