澄清 Linux TCP 窗口大小和延迟

jla*_*aye 7 linux tcp tcpdump tcp-slow-start

通过我无法理解的 TCP 通道发送数据时出现延迟。该链路为 1Gb 链路,端到端延迟约为 40 毫秒。在我当前的设置中,延迟(一条消息从发送者用户空间到接收者用户空间的时间)可以达到 100 毫秒。

发送方套接字配置了 TCP_NODELAY 选项。发送缓冲区 (SO_SNDBUF) 配置为 8MB。接收缓冲区 (SO_RCVBUF) 也配置为 8MB。Tcp 窗口缩放被激活。

update-1:我使用 zeromq 3.1.1 中间件来承载数据。套接字配置,包括 TCP_NODELAY 标志由中间件执行。一些选项是可以访问的,比如 rx 和 tx 发出缓冲区大小,但不是 TCP_NODELAY。据我了解,TCP_NODELAY 被激活以确保数据尽可能发送。同时,实际的套接字发送和发送消息的决定在两个单独的线程中执行。如果在要发送批处理中的第一条消息时有几条消息可用,则完成正确的批处理。

我使用 tcpdump 进行了捕获,从中提取了以下帧。在初始 TCP 握手之后,发送方 (172.17.152.124) 开始发送数据。接收方的初始窗口大小为 5840 字节,发送方为 5792 字节。

我的问题是发送方发送了两个帧(#6 和 #7)然后停止,等待接收方返回 ack。就我所见,未达到接收器的窗口大小,传输不应停止(384 字节未完成,初始接收窗口大小为 5840 字节)。我开始认为我没有正确理解 TCP 是什么。有人可以帮忙澄清一下吗?

update-2:我的数据负载由一个幻数和一个时间戳组成。我通过将有效负载的时间戳与 tcpdump 放置的时间戳进行比较来隔离延迟的数据包。帧#9 的有效载荷 ts 非常接近帧#6 和#7 中的一个,并且明显小于帧#8 中接收到的ack 的时间戳。

update-1:帧#9 没有立即发送的事实可以用 TCP 通道的慢启动来解释。事实上,一旦连接运行了几分钟,问题也会出现,所以慢启动似乎不是一般的解释。

  1. 20:53:26.017415 IP 172.17.60.9.39943 > 172.17.152.124.56001:标志 [S],seq 2473022771,win 5840,选项 [mss 142219,e,800,142200,800,800,800 确认长度

  2. 20:53:26.017423 IP 172.17.152.124.56001> 172.17.60.9.39943:旗[S.],SEQ 2948065596,ACK 2473022772,赢得5792,选项[MSS 1460,sackOK,TS VAL 186598852 ECR 219180820,NOP,wscale 9 ], 长度 0

  3. 20:53:26.091940 IP 172.17.60.9.39943 > 172.17.152.124.56001:Flags [.], ack 1, win 23, options [nop,nop,TS val 42191808825] length,

  4. 20:53:26.091958 IP 172.17.60.9.39943 > 172.17.152.124.56001:标志 [P.],seq 1:15,ack 1,w in 23,options [nop,nop,152595951851851851851851851851851851818cr val 14

  5. 20:53:26.091964 IP 172.17.152.124.56001 > 172.17.60.9.39943:Flags [.], ack 15, win 12, options [nop,nop,TS val 1865984829275]

  6. 20:53:26.128298 IP 172.17.152.124.56001 > 172.17.60.9.39943:Flags [P.],seq 1:257,ack 15,win 12,options [nop,nop,1966152826195261952618261826182618261828285618282828282828528295828282828282829452829294282942942829829829829829829829829829829111篇好了文章在内好了就能就便便就就能就就能就能就一样了

  7. 20:53:26.128519 IP 172.17.152.124.56001 > 172.17.60.9.39943:标志 [P.],seq 257:385,ack 15,win 12,选项 [nop989816518165181818181818181839943

  8. 20:53:26.202465 IP 172.17.60.9.39943 > 172.17.152.124.56001:Flags [.], ack 257, win 27, options [nop,nop,TS val 42059189106]

  9. 20:53:26.202475 IP 172.17.152.124.56001 > 172.17.60.9.39943:Flags [.],seq 385:1833,ack 15,win 12,选项 [nop9913514514518556018579943

  10. 20:53:26.202480 IP 172.17.152.124.56001 > 172.17.60.9.39943:Flags [P.],seq 1833:2305,ack 15,win 12,options [nop595904100p]18000000000000000000

如果这样的话,两端都是Linux RHEL5盒子,内核是2.6.18,网卡用的是e1000e驱动。

update-3 /etc/sysctl.conf 的内容

[jlafaye@localhost ~]$ cat /etc/sysctl.conf | grep -v "^#" | grep -v "^$" 
net.ipv4.ip_forward = 0
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.default.accept_source_route = 0
kernel.sysrq = 0
kernel.core_uses_pid = 1
net.ipv4.tcp_syncookies = 1
kernel.msgmnb = 65536
kernel.msgmax = 65536
kernel.shmmax = 68719476736
kernel.shmall = 4294967296
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.rmem_default = 1048576
net.core.wmem_default = 1048576
net.ipv4.tcp_rmem = 65536 4194304 16777216
net.ipv4.tcp_wmem = 65536 4194304 16777216 
net.core.netdev_max_backlog = 10000 
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_mem = 262144 4194304 16777216
kernel.shmmax = 68719476736
Run Code Online (Sandbox Code Playgroud)

jla*_*aye 11

在对我的流量进行更多挖掘之后,我能够看到我的数据只不过是一系列小突发,它们之间的空闲时间很短。

使用有用的工具ss,我能够检索连接的当前拥塞窗口大小(请参阅cwnd输出中的值):

[user@localhost ~]$ /usr/sbin/ss -i -t -e | grep -A 1 56001

ESTAB 0 0 192.168.1.1:56001
192.168.2.1:45614 uid:1001 ino:6873875 sk:17cd4200ffff8804 ts sackscalable wscale:8,9 cvt_rt25r270Mbps:8,9 rto:at5r276Mbps send:1001 ino:6873875

我多次运行该工具,发现拥塞窗口大小会定期重置为初始值(在我的 Linux 机器上为 10 毫秒)。连接不断循环回到慢启动阶段。在慢启动期间,大量消息超过窗口大小的突发被延迟,等待与突发的第一个数据包相关的确认。

流量由一系列突发组成的事实可能解释了拥塞窗口大小的重置。

通过在空闲期后停用慢启动模式,我能够摆脱延迟。

[user@host ~]$ cat /proc/sys/net/ipv4/tcp_slow_start_after_idle 0