【问题标题】:TCP Receive windowTCP 接收窗口
【发布时间】:2012-04-23 23:18:09
【问题描述】:

我试图了解接收器窗口如何影响高延迟连接的吞吐量。

我在相距很远的两台机器上有一对简单的客户端-服务器应用程序,两者之间的连接为 250 毫秒延迟 RTT。我在 Windows(XP,7)和 Linux(Ubuntu 10.x)上运行了这个测试,结果相同,所以为简单起见,我们假设以下情况: 客户端接收数据:WinXP Pro 服务器发送数据:Win7 Pro 同样,延迟为 250 毫秒 RTT。

我在不更改客户端上的接收器缓冲区大小的情况下运行 TCP 测试(默认为 8Kb),我在线上看到(使用 Wireshark):

  • 客户端向服务器发送ACKS并且TCP数据包包含RWIN=65k
  • 服务器发送数据并报告 RWIN=65k

查看跟踪我看到了 3-4 个数据包的突发(有效负载为 1460 字节),紧接着是从客户端机器发送到服务器的 ACK,然后在大约 250 毫秒内什么都没有,然后是一个新的数据包突发从服务器到客户端。

因此,总而言之,服务器似乎在填满接收器窗口之前也没有发送新数据。

为了做更多的测试,这次我也运行了相同的测试,在客户端机器上更改接收器的缓冲区大小(在 Windows 上,更改接收器的缓冲区大小最终会影响机器通告的 RWIN)。我希望在阻塞 ACK 之前看到大量的数据包......并且至少有更高的吞吐量。

在这种情况下,我将 recv 缓冲区大小设置为 100,000,000。从客户端到服务器的数据包现在有一个 RWIN=99,999,744(嗯,这很好),但不幸的是,从服务器发送到客户端的数据模式仍然是相同的:短暂的突发,然后是长时间的等待。 为了确认我在网络上看到的内容,我还测量了从服务器向客户端发送大量数据的时间。我没有看到使用大 RWIN 或使用默认值的任何变化。

谁能帮我理解为什么更改 RWIN 并不会真正影响吞吐量?

几点注意事项: - 服务器使用 8Kb 块的 write() 尽可能快地发送数据 - 正如我之前所说,我在使用 Linux 时也看到了类似的效果。更改接收缓冲区大小会影响节点使用的 RWIN,但吞吐量保持不变。 - 我分析了几百包后的trace,给TCP慢启动机制足够的时间来扩大CWIN大小。


按照建议,我在此处添加了线迹的小快照

No.     Time        Source                Destination           Protocol Length Info
     21 2.005080    CCC.CCC.CCC.CCC       sss.sss.sss.sss       TCP      60     57353 > 21500 [ACK] Seq=1 Ack=11681 Win=99999744 Len=0
     22 2.005109    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=19305 Ack=1 Win=65536 Len=1460
     23 2.005116    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=20765 Ack=1 Win=65536 Len=1460
     24 2.005121    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=22225 Ack=1 Win=65536 Len=1460
     25 2.005128    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      946    21500 > 57353 [PSH, ACK] Seq=23685 Ack=1 Win=65536 Len=892
     26 2.005154    CCC.CCC.CCC.CCC       sss.sss.sss.sss       TCP      60     57353 > 21500 [ACK] Seq=1 Ack=14601 Win=99999744 Len=0
     27 2.007106    CCC.CCC.CCC.CCC       sss.sss.sss.sss       TCP      60     57353 > 21500 [ACK] Seq=1 Ack=16385 Win=99999744 Len=0
     28 2.007398    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=24577 Ack=1 Win=65536 Len=1460
     29 2.007401    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=26037 Ack=1 Win=65536 Len=1460
     30 2.007403    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=27497 Ack=1 Win=65536 Len=1460
     31 2.007404    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=28957 Ack=1 Win=65536 Len=1460
     32 2.007406    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=30417 Ack=1 Win=65536 Len=1460
     33 2.007408    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      946    21500 > 57353 [PSH, ACK] Seq=31877 Ack=1 Win=65536 Len=892
     34 2.007883    CCC.CCC.CCC.CCC       sss.sss.sss.sss       TCP      60     57353 > 21500 [ACK] Seq=1 Ack=19305 Win=99999744 Len=0
     35 2.257143    CCC.CCC.CCC.CCC       sss.sss.sss.sss       TCP      60     57353 > 21500 [ACK] Seq=1 Ack=22225 Win=99999744 Len=0
     36 2.257160    CCC.CCC.CCC.CCC       sss.sss.sss.sss       TCP      60     57353 > 21500 [ACK] Seq=1 Ack=24577 Win=99999744 Len=0
     37 2.257358    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=32769 Ack=1 Win=65536 Len=1460
     38 2.257362    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=34229 Ack=1 Win=65536 Len=1460
     39 2.257364    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=35689 Ack=1 Win=65536 Len=1460
     40 2.257365    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=37149 Ack=1 Win=65536 Len=1460

如您所见,服务器在数据包 #33 处停止发送数据。

客户端在旧数据包的数据包 #34 处发送 ACK(seq=19305,在数据包 #20 上发送,此处未显示)。 使用 100Mb 的 RWIN,我希望服务器不会阻塞一段时间。

在 20-30 个数据包之后,服务器端的拥塞窗口应该足够大,可以发送比我看到的更多的数据包...... 我假设拥塞窗口最终会增长到 RWIN...但是,即使在数百个数据包之后,模式仍然是相同的:数据数据然后阻塞 250 毫秒...

【问题讨论】:

  • 两台机器都是无线的吗?您能否在数据包和确认中打印从服务器/客户端发送回来和第四次发送的序列号。客户端或服务器端可能存在大量噪音,导致数据丢失。

标签: performance networking tcp latency throughput


【解决方案1】:

我可以从您提供的样本中猜出两件事:

  1. 服务器有一个大约 15k 的发送缓冲区。
  2. 您提供的转储是在服务器端完成的。

要使 TCP 连接的窗口缩放到一定大小,发送方的发送缓冲区和接收方的接收缓冲区都必须足够大。

实际使用的窗口是接收方提供/请求的接收窗口和发送方操作系统设置的发送缓冲区大小中的最小值。

长话短说,您需要在服务器上配置发送缓冲区大小。

为了清楚起见,让我们逐包分析您的示例数据包。

服务器又发送了一堆数据:

 22 2.005109    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=19305 Ack=1 Win=65536 Len=1460
 23 2.005116    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=20765 Ack=1 Win=65536 Len=1460
 24 2.005121    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=22225 Ack=1 Win=65536 Len=1460
 25 2.005128    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      946    21500 > 57353 [PSH, ACK] Seq=23685 Ack=1 Win=65536 Len=892

注意PSH。这是一个标志,指示其间的任何跃点已发送完整的数据块,请将其发送到另一端。 (在这种情况下,“完整”块是您的 8kb)

当服务器仍在发送时,它会收到 2 个 ACK​​S:

 26 2.005154    CCC.CCC.CCC.CCC       sss.sss.sss.sss       TCP      60     57353 > 21500 [ACK] Seq=1 Ack=14601 Win=99999744 Len=0
 27 2.007106    CCC.CCC.CCC.CCC       sss.sss.sss.sss       TCP      60     57353 > 21500 [ACK] Seq=1 Ack=16385 Win=99999744 Len=0
 

特别注意数字:Ack=14601Ack=16385。这些数字是接收方正在确认的数据包的序列号。

Ack=14601 的意思是“我已经收到了 seq no 14601 之前的所有内容”。

另请注意,这些是较旧的数据,不在您提供的示例中。

所以服务器处理这些 ACK 并继续发送数据:

 28 2.007398    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=24577 Ack=1 Win=65536 Len=1460
 29 2.007401    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=26037 Ack=1 Win=65536 Len=1460
 30 2.007403    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=27497 Ack=1 Win=65536 Len=1460
 31 2.007404    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=28957 Ack=1 Win=65536 Len=1460
 32 2.007406    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=30417 Ack=1 Win=65536 Len=1460
 33 2.007408    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      946    21500 > 57353 [PSH, ACK] Seq=31877 Ack=1 Win=65536 Len=892

这里我们有一个完整的数据块:1460*5+892 == 8192。

然后,在发送最后一个数据包 0.443 毫秒后,它又收到一个 ACK​​:

 34 2.007883    CCC.CCC.CCC.CCC       sss.sss.sss.sss       TCP      60     57353 > 21500 [ACK] Seq=1 Ack=19305 Win=99999744 Len=0

然后有一个几乎正好是 250 毫秒的延迟,在此期间服务器什么也不发送,然后才收到这些:

 35 2.257143    CCC.CCC.CCC.CCC       sss.sss.sss.sss       TCP      60     57353 > 21500 [ACK] Seq=1 Ack=22225 Win=99999744 Len=0
 36 2.257160    CCC.CCC.CCC.CCC       sss.sss.sss.sss       TCP      60     57353 > 21500 [ACK] Seq=1 Ack=24577 Win=99999744 Len=0
 

然后继续发送:

 37 2.257358    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=32769 Ack=1 Win=65536 Len=1460
 38 2.257362    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=34229 Ack=1 Win=65536 Len=1460
 39 2.257364    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=35689 Ack=1 Win=65536 Len=1460
 40 2.257365    sss.sss.sss.sss       CCC.CCC.CCC.CCC       TCP      1514   21500 > 57353 [ACK] Seq=37149 Ack=1 Win=65536 Len=1460

这里有两件非常有趣的事情需要注意。
首先,服务器在没有等待 ACK 的情况下发送了多少字节。 Te last ACK seq no server 在该延迟前接收到的是Ack=19305,此时服务器发送的最后一个数据包的seq no 是Seq=30417

因此,在暂停期间,服务器已发送 11112 个字节尚未被客户端确认。

其次,这是服务器在发送一堆数据后立即收到的一个 ACK​​,这并没有触发它发送更多数据。好像那个ACK不够好。

在此之前收到的 ACK 是 Ack=16385,给出了服务器发送的 30417-16385=14032 字节,此时服务器未确认。只有在收到 seq no 24577 的 ACK 后,将该计数减少到 30417-24577=5840,服务器才开始再次发送。

因此,与 16k 的有效窗口大小相比,8k 的缓冲区大小较大这一事实意味着吞吐量实际上会有所降低,因为服务器不会发送任何 8k 块,直到有空间容纳所有块。

最后,对于那些想知道的人,有一个称为窗口缩放的 TCP 选项,它允许连接的一端声明窗口大小实际上是 TCP 标头中数字的倍数。见RFC 1323。该选项在 SYN 数据包中传递,因此它们在连接中不可见 - 仅提示窗口缩放有效,因为窗口大小 TCP 标头小于正在使用的窗口。

【讨论】:

  • Michael,该跟踪的服务器发送缓冲区为 8kb(默认窗口大小)。我相信的跟踪是在客户端捕获的。但你没看错:问题是限制窗口的 SEND 缓冲区。我刚刚在服务器端使用更大的发送者窗口运行了一个新测试,我可以清楚地看到阻塞前更长的数据突发。 Wireshark 中的图表清楚地显示了每次收到 ACK 时拥塞窗口如何翻倍。
【解决方案2】:

连接套接字后,您无法将接收缓冲区大小设置为 >= 64k。你必须先做。在服务器的情况下,这意味着在侦听套接字上设置接收缓冲区大小:接受的套接字从接受它们的套接字继承它。如果不这样做,则无法协商 TCP 窗口缩放选项,因此对等方无法相互告知超过 64k 的大小。

【讨论】:

    【解决方案3】:

    在发送机器收到接收方的ack的那一刻,发送机器排队发送的数据有多少?因为 TCP 是基于流的协议,在数据流中没有数据包中断,所以 TCP 发送方无法知道何时应该发送部分数据包,以及何时应该等待更多数据到达。通常,如果 TCP 实现接收到一个数据包的 ACK,它将决定是否值得传输它所拥有的任何内容,直到传输缓冲区为空,但如果数据在此之后排队等待传输,它可能会等到它收到另一个 ACK​​ 后再发送另一个批处理。

    【讨论】:

    • 相信我,发件人缓冲区总是满的。我的应用程序尽可能快地写入。这是一个for循环,连续调用一个8Kb缓冲区的write()。
    【解决方案4】:

    接收器窗口大小直接影响吞吐量。吞吐量

    一些事情也会降低吞吐量。标头中的 ECN 位是否设置为 1?你知道两边是否有丢包吗?好像服务器超时了。能否在客户端打印传入数据包和传出 ACK 的序列号,并在服务器端打印类似信息。如果接收者的序列号是 5 并且接收到 6,7,8,9,它将 ACK 6,7,8,9。但是如果 6 丢失,它会在收到数据包 7、8、9 时 ACK 5。序列号可以揭示很多信息。

    250 毫秒的暂停似乎是超时。

    Slow-Start

    该算法从指数增长阶段开始,最初拥塞窗口大小 (cwnd) 为 1 或 2 个分段,并为每个收到的 ACK 增加 1 个分段大小 (SS)。由于接收器通常每两个段发送一个 ACK​​,因此这种行为有效地使网络每次往返的窗口大小加倍。这种行为一直持续到拥塞窗口大小 (cwnd) 达到接收方通告的窗口大小或发生丢失为止。

    可能发生的事情是
    服务器发送 1 个数据包,获得 1 个确认
    服务器发送 2 个数据包,得到 2 个确认(2,3)
    服务器发送 4 个数据包,得到 4 个确认(4,5,6,7)
    服务器发送 8 个数据包,得到 4 个确认(数据包在客户端收到之前丢弃)(8,9,10,11)(超时 12)
    服务器发送 4 个数据包,获得 4 个确认 (12,13,14,15)
    服务器发送 5 个数据包,获得 4 个确认(16,17,18,19)(超时 20)
    服务器发送 3 个数据包,得到 3 个确认 (20,21,22)
    服务器发送 4 个数据包,获得 4 个确认 (23,24,25,26)
    服务器发送 5 个数据包,得到 4 个确认(27,28,29,30)(超时 31)
    服务器继续3,4,5循环

    【讨论】:

    • 贾斯汀。没有丢包。我已经验证过了。我将尝试从 Wireshark 获取可打印的跟踪信息并将其发布在此处。谢谢!!!
    • ECN位可以由路由器设置。这将降低发送数据的速率。客户端可能会发送 5 个数据包,而路由器正在设置 ECN 位或丢弃数据包。 ECN 位是一种在网络变得拥塞时通知任一方的方法。 en.wikipedia.org/wiki/Explicit_Congestion_Notification
    • 如果它是一个高延迟的网络,很可能在此过程中有很多跃点。接收器窗口可以与介于两者之间的瓶颈一样大。但如果这个瓶颈是丢包,接收者永远不会知道它们被发送了。因此,它会尽可能多地确认数据包,而服务器将超时等待接收可能 10 个确认,而不是它收到的 4 个。
    • 我可以尝试重新运行测试并在两端捕获跟踪并尝试手动重建实际发生的情况......但这需要一些时间。您会推荐什么工具来帮助我追踪内部状态?
    • 好的,RWIN 可以最大为 65k,但是有一个扩展 RFC-1323,它基本上允许在 TCP 数据包的可选标头中传输 SYN 数据包中的缩放因子。这基本上可以将 RWIN 扩展到旧的传统 65Kb 之外。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-18
    相关资源
    最近更新 更多