4

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

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

我在不更改客户端上的接收器缓冲区大小(默认为 8Kb)的情况下运行 TCP 测试,并且在线上看到(使用 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 上发送,此处未显示)。使用 100Mb 的 RWIN,我希望服务器不会阻塞一段时间。

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

4

4 回答 4

8

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

  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 服务器在该延迟之前收到的Ack=19305,并且服务器在该点发送的最后一个数据包的 seq no 是Seq=30417

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

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

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

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

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

于 2012-04-24T02:26:26.947 回答
1

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

于 2012-04-24T01:37:32.213 回答
0

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

于 2012-04-23T23:24:30.340 回答
0

接收器窗口大小直接影响吞吐量。吞吐量 <= RWIN/RTT。

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

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

慢启动

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

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

于 2012-04-23T23:59:26.797 回答