Windows下的奇怪的tcp死锁

Joh*_*son 5 c++ sockets windows deadlock tcp

我们正在移动LAN上的大量数据,并且必须非常快速和可靠地进行.目前我们使用在C++中实现的Windows TCP.使用大(同步)发送比一堆较小(同步)发送更快地移动数据,但是经常会在大的时间间隔(.15秒)内死锁,导致整体传输速率骤降.这种僵局发生在非常特殊的情况下,这使我相信它应该完全可以预防.更重要的是,如果我们真的不知道原因我们真的不知道它不会发生在一段时间内使用较小的发送.谁能解释这个僵局?

死锁描述(好的,僵尸锁定,它没有死,但是.15秒左右停止,然后再次启动)

  1. 接收方发送ACK.
  2. 发送方发送包含消息结束的数据包(设置推送标志)
  3. 对socket.recv的调用大约需要.15秒(!)才能返回
  4. 关于呼叫返回的时间,接收方发送ACK
  5. 最后发送来自发送方的下一个数据包(为什么它在等待?tcp窗口很大)

关于(3)的奇怪之处在于,通常该调用不需要花费太多时间并且接收完全相同数量的数据.在2Ghz的机器上,有3亿条指令值得花时间.我假设呼叫没有(天堂禁止)等待收到的数据在返回之前被激活,所以ack必须等待呼叫返回,否则两者必须被其他东西延迟.

当第二个数据包(同一个消息的一部分)到达1到2之间时,问题永远不会发生.这部分非常清楚地说明它与Windows TCP不会发回的事实有关 - 数据ACK直到第二个数据包到达或200ms计时器到期.但是延迟小于200毫秒(更像是150毫秒).

第三个不合时宜的角色(在我看来真正的罪魁祸首)是(5).发送肯定是在那之前被调用好的.15秒,但是数据永远不会在ack返回之前点击线路.对我来说,这是这个僵局中最离奇的部分.它不是tcp阻塞,因为TCP窗口很大,因为我们将SO_RCVBUF设置为类似500*1460(仍然在meg以下).数据进入非常快(基本上有一个循环通过发送旋转数据)所以缓冲区应该几乎立即填充.Msdn提到在决定发送命中何时使用各种"启发式",并且已经挂起的发送+完整缓冲区将导致发送阻塞,直到数据到达线路(否则发送显然只是将数据复制到tcp发送缓冲区并返回).

不管怎样,为什么发送者实际上并没有发送更多数据.15秒暂停对我来说是最离奇的部分.上面的信息是通过wireshark在接收端捕获的(当然除了在文本文件中记录的socket.recv返回时间).我们尝试将发送缓冲区更改为零并关闭发送方上的nagel(是的,我知道nagel不会发送小数据包 - 但我们尝试关闭nagel,以防这是未声明的"启发式"的一部分,影响消息是否会从技术上来说,微软的nagel就是如果缓冲区已满并且有一个未完成的ACK,则不会发送一个小数据包,所以它似乎是一种可能性.

caf*_*caf 3

发送阻塞直到ACK收到前一个几乎可以肯定表明 TCP 接收窗口已满(您可以通过使用 Wireshark 分析网络流量来检查这一点)。

无论 TCP 窗口有多大,如果接收应用程序处理数据的速度不如数据到达的速度,那么 TCP 窗口最终将被填满。我们在这里说话有多快?接收方如何处理数据?(如果您将接收到的数据写入磁盘,那么您的磁盘很可能无法跟上全速千兆位网络的速度)。


好的,所以您有 730,000 字节的接收窗口,并且以 480Mbps 的速度传输数据。这意味着只需要 12 毫秒即可完全填满窗口 - 因此,当接收端发生 150 毫秒延迟时,接收窗口几乎立即填满并导致发送方停止。

所以根本原因是安排接收进程时出现了 150 毫秒的延迟。有很多因素可能会导致这种情况(可能很简单,比如内核需要将脏页刷新到磁盘以为应用程序创建更多可用页);您可以尝试增加进程调度优先级,但不能保证这会有帮助。