TCP 客户端在继续发送之前等待 ack

TCP client waits for ack before continuing sending

我想了解为什么 tcp 客户端停止发送数据并且 等待服务器响应。我读过 receive and congestion windows 并且在两个端点上都将 initcwnd 设置为 400。我也设置 net.ipv4.tcp_window_scaling 到 1。并且两个套接字都使用 TCP_NODELAY 选项打开以禁用 Nagle 算法。 端点之间的 RTT 延迟约为 35 毫秒。

从下面的 tcpdump 跟踪可以清楚地看出,在 14:02:46.310155 客户端发送了它的最后一个数据包,然后它等待大约 31 毫秒后到达的服务器的确认。到达后,它会继续发送数据。

14:02:46.268179 IP 客户端 > 服务器:标志 [S],seq 2645621234,win 28400,选项 [mss 1420,sackOK,TS val 6178563 ecr 0,nop,wscale 9],长度 0
14:02:46.305282 IP server > client: Flags [S.], seq 339254367, ack 2645621235, win 28160, options [mss 1420,sackOK,TS val 4865788 ecr 6178563,nop,wscale 9], 长度 0
14:02:46.305343 IP 客户端 > 服务器:标志 [.],ack 1,win 56,选项 [nop,nop,TS val 6178573 ecr 4865788],长度 0
14:02:46.305592 IP client > server: Flags [P.], seq 1:44, ack 1, win 56, options [nop,nop,TS val 6178573 ecr 4865788], 长度 43
14:02:46.305954 IP client > server: Flags [.], seq 44:1452, ack 1, win 56, options [nop,nop,TS val 6178573 ecr 4865788], 长度 1408
14:02:46.306023 IP client > server: Flags [.], seq 1452:2860, ack 1, win 56, options [nop,nop,TS val 6178573 ecr 4865788], 长度 1408
14:02:46.306258 IP client > server: Flags [.], seq 2860:4268, ack 1, win 56, options [nop,nop,TS val 6178573 ecr 4865788], 长度 1408
14:02:46.306445 IP client > server: Flags [.], seq 4268:5676, ack 1, win 56, options [nop,nop,TS val 6178573 ecr 4865788], 长度 1408
14:02:46.306586 IP client > server: Flags [.], seq 5676:7084, ack 1, win 56, options [nop,nop,TS val 6178573 ecr 4865788], 长度 1408
14:02:46.306914 IP client > server: Flags [.], seq 7084:8492, ack 1, win 56, options [nop,nop,TS val 6178573 ecr 4865788], 长度 1408
14:02:46.307082 IP client > server: Flags [.], seq 8492:9900, ack 1, win 56, options [nop,nop,TS val 6178573 ecr 4865788], 长度 1408
14:02:46.307251 IP client > server: Flags [.], seq 9900:11308, ack 1, win 56, options [nop,nop,TS val 6178573 ecr 4865788], 长度 1408
14:02:46.307411 IP client > server: Flags [.], seq 11308:12716, ack 1, win 56, options [nop,nop,TS val 6178573 ecr 4865788], 长度 1408
14:02:46.307620 IP client > server: Flags [.], seq 12716:14124, ack 1, win 56, options [nop,nop,TS val 6178573 ecr 4865788], 长度 1408
14:02:46.307760 IP client > server: Flags [.], seq 14124:15532, ack 1, win 56, options [nop,nop,TS val 6178573 ecr 4865788], 长度 1408
14:02:46.307931 IP client > server: Flags [.], seq 15532:16940, ack 1, win 56, options [nop,nop,TS val 6178573 ecr 4865788], 长度 1408
14:02:46.308059 IP client > server: Flags [.], seq 16940:18348, ack 1, win 56, options [nop,nop,TS val 6178573 ecr 4865788], 长度 1408
14:02:46.308216 IP client > server: Flags [.], seq 18348:19756, ack 1, win 56, options [nop,nop,TS val 6178573 ecr 4865788], 长度 1408
14:02:46.308373 IP client > server: Flags [.], seq 19756:21164, ack 1, win 56, options [nop,nop,TS val 6178573 ecr 4865788], 长度 1408
14:02:46.309622 IP client > server: Flags [.], seq 21164:22572, ack 1, win 56, options [nop,nop,TS val 6178574 ecr 4865788], 长度 1408
14:02:46.309852 IP client > server: Flags [.], seq 22572:23980, ack 1, win 56, options [nop,nop,TS val 6178574 ecr 4865788], 长度 1408
14:02:46.310023 IP client > server: Flags [.], seq 23980:25388, ack 1, win 56, options [nop,nop,TS val 6178574 ecr 4865788], 长度 1408
14:02:46.310155 IP client > server: Flags [.], seq 25388:26796, ack 1, win 56, options [nop,nop,TS val 6178574 ecr 4865788], 长度 1408
14:02:46.341579 IP server > client: Flags [.], ack 44, win 55, options [nop,nop,TS val 4865797 ecr 6178573], 长度 0
14:02:46.341612 IP client > server: Flags [.], seq 26796:28204, ack 1, win 56, options [nop,nop,TS val 6178582 ecr 4865797], 长度 1408

造成这种行为的主要原因有两个:

  1. Slow-start。刚建立连接或检测到拥塞后发生。
  2. Nagle algorithm。发送短于 MSS 的 TCP 段时发生。

在您的场景中,客户端发送 20 个 TCP 段并等待确认。收到一个(第一段)后,它发送更多(您的跟踪只显示一个段)。

从您的 tcp 通信转储中,我看到以下内容:

-我们不能保证 Nagle 已关闭,但由于客户端正在发送完整的 MSS 消息,这似乎不是问题

-连接正在使用window缩放:您可以在SYN和SYN+ACK消息的选项中看到wscale为9

但是,我看到就在传递 28160 字节(window 由服务器发布)之前,客户端停止并等待服务器 ACK。这可能是因为它没有考虑 window 比例或 initcwnd 为 20(你说你将其设置为 400)或应用程序发送了 26796 字节,然后发送了其余字节。

在三次握手中,为接收方协商了 2^9 的 window 比例,接收方通告了 55 = 55*2^9 = 28160 字节的 window。

发送方然后发送一个 43 字节的数据包,紧接着是 19 1408 字节,总共 26795 字节。

显然,默认初始拥塞window已被修改,否则20个数据包将不会在没有收到ACK的情况下发送。

然而,26795 字节几乎填满了广告中的接收器window;没有足够的空间来发送另一个完整的 MTU。

当来自接收方的 ACK 最终到达,确认收到 43 字节的数据包并通告 window 为 55 时,我们知道初始数据包中的 43 字节已被消耗,我们现在计算足够的空间可以再发送一个 1408 字节的数据包 (28160-26795+43=1408)。

所以问题是您的接收器没有通告 window 大到足以容纳您初始拥塞 window 中的 400*1408 字节。您必须类似地调整接收器接收 window.

请注意,如果您一直在查看 Wireshark 中的捕获,接收器的 "zero window" 条件将会突出显示。

(实际上,它比这复杂一点。我无法完全解释为什么它不发送部分 MTU 来完全填充广告接收 window。如果启用 Nagle 算法,这解释了它;您可以通过设置套接字选项 TCP_NODELAY 将其关闭。如果 Nagle 关闭,它可能反映了您的 TCP 堆栈的慢启动的实现细节。)