【问题标题】:Qt QTcpSocket Reading Data Overlap Causes Invalid TCP Behavior During High Bandwidth Reading and WritingQt QTcpSocket读取数据重叠导致高带宽读写时TCP行为无效
【发布时间】:2020-11-23 19:55:01
【问题描述】:

总结: TCP 套接字中的一些内存将被其他传入数据覆盖。

应用: 一个使用Qt 中的TCP(QTcpSocket 和QTcpServer) 的客户端/服务器系统。客户端从服务器请求一个帧(只是一个简单的字符串消息),以及由该帧组成的响应(服务器->客户端)(614400字节用于测试目的)。帧大小是预先确定的并且是固定的。

实施细节: 根据 TCP 协议(​​服务器 -> 客户端)的保证,我知道我应该能够从套接字读取 614400 字节并且它们是有序的。如果这两件事中的任何一个失败,则连接一定失败。

重要代码: 假设套接字已连接。

此代码向服务器请求一个帧。称为GetFrame() 函数。

// Prompt the server to send a frame over
if(socket->isWritable() && !is_receiving) { // Validate that socket is ready
    is_receiving = true; // Forces only one request to go out at a time
    qDebug() << "Getting frame from socket..." << image_no;
    int written = SafeWrite((char*)"ReadyFrame"); // Writes then flushes the write buffer
    if (written == -1) {
        qDebug() << "Failed to write...";
        return temp_frame.data();
    }
    this->SocketRead();
    is_receiving = false;
}
qDebug() << image_no << "- Image Received";
image_no ++;
return temp_frame.data();

此代码等待刚刚请求的帧被读取。这是SocketRead() 函数

size_t byte_pos = 0;
qint64 bytes_read = 0;
do {
    if (!socket->waitForReadyRead(500)) { // If it timed out return existing frame
        if (!(socket->bytesAvailable() > 0)) {
            qDebug() << "Timed Out" << byte_pos;
            break;
        }
    }
    bytes_read = socket->read((char*)temp_frame.data() + byte_pos, frame_byte_size - byte_pos);
    if (bytes_read < 0) {
        qDebug() << "Reading Failed" << bytes_read << errno;
        break;
    }
    byte_pos += bytes_read;

} while (byte_pos < frame_byte_size && is_connected); // While we still have more pixels
qDebug() << "Finished Receiving Frame: " << byte_pos;

如上面的代码所示,我一直读取直到完全接收到帧(其中读取的字节数等于帧中的字节数)。

我遇到的问题是 QTcpSocket 读取操作以不符合 TCP 协议保证的方式跳过字节。由于我跳过字节,我最终没有到达 while 循环的结尾,而只是“超时”。 为什么会这样?

到目前为止我做了什么: 服务器发送的数据直接转换为 uint16_t(短)整数,用于客户端的其他部分。我已将服务器更改为简单地输出数据,这些数据只是为每个发送的数字加一个。由于数据类型是 uint16_t 并且字节数超过了该整数类型的最大数量,因此 int-16 将每 65535 循环一次。

这是一个数据可视化软件,所以这个调试配置(在客户端)会导致这样的结果:

我已经确定(您可以在图形底部看到一点)某些字节被跳过。在temp_frame的内存中可以看到内存跳过的确切点:

在正确的情况下,这应该按顺序计数。

从 Wireshark 并遵循这个特定的 TCP 连接,我确定 所有字节实际上都到达了(全部为 6114400),并且 所有数字都按顺序排列 (我使用了一个 python 脚本来确保计数是连续的)。

这是一个开源项目的工作,所以this 是客户端的整个代码库。

总的来说,我看不出在这个解决方案中我怎么会做错事,我所做的只是以标准方式从套接字读取。

【问题讨论】:

  • 请阅读您应用的标签的说明,Qt 是 C++ 而不是 C。另外,不要用图片来表示文本并考虑提取minimal reproducible example
  • 虽然内核和 NIC 可能能够处理高数据速率,但您可能会用完 kernel 套接字缓冲区来存储数据。考虑使用getsockoptsetsockoptSO_RCVBUF 来设置更大的空间。详情请见man 7 socket
  • 好的,感谢@CraigEstey 的建议,我试过了,但没有解决问题。
  • 具体来说,我使用setsockoptSO_RCVBUF将缓冲区大小设置为614400。
  • 澄清一下,您是说您看到了"Timed Out" 调试消息吗? SocketRead 中的评论 // If it timed out return existing frame 也有点奇怪。这本身就表明您可能仅仅因为您决定不再等待而最终处理部分帧数据。而不是使用waitForReadyRead,您应该连接到readyRead 信号并在数据到达时处理/调度数据。

标签: c++ qt sockets qtcpsocket


【解决方案1】:

警告:这不是对您问题的明确答案,但可以尝试一些事情(评论太大)。


使用(例如)GigE,您的数据速率约为 100MB/s。 kernel 缓冲区空间的 [total] 数量为 614400,每秒将重新填充约 175 次。 IMO,这仍然太小了。当我使用SO_RCVBUF [用于商业产品] 时,我至少使用了 8MB。这为任务切换延迟提供了更大的(更好的)余量。

尝试设置一些巨大的值,例如 100MB,以消除这个因素[在测试/启动期间]。


首先,验证内核和 NIC 驱动程序是否可以处理吞吐量/延迟非常重要。

您可能每秒收到太多中断,并且 ISR 序言/结语开销可能太高。网卡驱动可以用NAPI为以太网卡实现轮询vs中断驱动。

见:https://serverfault.com/questions/241421/napi-vs-adaptive-interrupts

见:https://01.org/linux-interrupt-moderation


您的进程/线程可能没有足够高的优先级来快速调度。

您可以使用带有sched_setschedulerSCHED_RR 和优先级(例如)8 的 R/T 调度程序。注意:高于 11 会杀死系统,因为在 12 及以上时,您的优先级更高比大多数内部内核线程 - 这不是一件好事。


您可能需要禁用 IRQ 平衡并将 IRQ 关联设置为单个 CPU 内核。

然后您可以将输入进程/线程设置为锁定到该核心 [使用sched_setaffinity 和/或pthread_setaffinity]。


您可能需要某种“零复制”来绕过内核从其缓冲区复制到您的用户空间缓冲区。

您可以使用PACKET_MMAP mmap 内核套接字缓冲区。见:https://sites.google.com/site/packetmmap/


我会小心qDebug 输出的开销。它看起来像一个 iostream 类型的实现。开销可能很大。它可能会显着减慢速度。

也就是说,您没有测量系统的性能。您正在测量系统的性能加上调试代码。

当我不得不调试/跟踪此类事情时,我使用了一个 [自定义]“事件”日志,该日志通过具有固定数量元素的内存环形队列实现。

调试调用如:

eventadd(EVENT_TYPE_RECEIVE_START,some_event_specific_data);

此处eventadd 使用事件类型、事件数据和招聘时间戳填充固定大小的“事件”struct(例如,struct timespec 来自 clock_gettime(CLOCK_MONOTONIC,...)

每个此类调用的开销都非常低。事件只是存储在事件环中。只记住最后 N 个。

在某些时候,您的程序会触发将此队列转储到文件并终止。

此机制类似于 [并以] 硬件逻辑分析仪为模型。也类似于dtrace

这是一个示例事件元素:

struct event {
    long long evt_tstamp;               // timestamp
    int evt_type;                       // event type
    int evt_data;                       // type specific data
};

【讨论】:

    猜你喜欢
    • 2013-12-31
    • 1970-01-01
    • 2015-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-12
    • 2017-10-15
    • 1970-01-01
    相关资源
    最近更新 更多