性能 – TCP接收窗口

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

我在两台相距很远的机器上有一对简单的客户端 – 服务器应用程序,两个250mSec延迟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,但吞吐量保持不变.
  – 我分析了数百个数据包之后的跟踪,给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上发送,此处未显示).
RWIN为100Mb,我希望服务器不会阻塞一段时间.

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

最佳答案
我可以从你提供的样本中猜出两件事:

>服务器的发送缓冲区大约为15k.
>您提供的转储是在服务器端完成的.

对于要扩展到特定大小的TCP连接窗口,发送方的发送缓冲区和接收方的接收缓冲区都必须足够大.

使用的实际窗口是接收器提供/请求的接收窗口的最小值和发送器的OS设置发送缓冲区大小.

简而言之,您需要在服务器上配置发送缓冲区大小.

为了清理,让我们按包分析您的样本包.

服务器发送另一堆数据:

 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个ACKS:

 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 = 14601和Ack = 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的情况下发送了多少字节.
在该延迟之前服务器收到的最后一个ACK seq没有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标头小于正在使用的窗口.

转载注明原文:性能 – TCP接收窗口 - 代码日志