序号和确认号到底在数什么?为什么 ACK 可以不是一包一回?32 位序号发满 4 GiB 之后会怎样?这篇逐个包核对序号和确认号的算术,并在回环上真的发 4.5 GiB 看序号回绕。
客户端 ISN = 1936706479,服务端 ISN = 2047459890(下面都用相对值:序号减去各自的 ISN)
# 方向 标志 相对序号 载荷 序号+载荷(下一个序号) 相对确认号
1 客户端→ SYN 0 0 1 0
2 ←服务端 ACK+SYN 0 0 1 1
3 客户端→ ACK 1 0 1 1
4 ←服务端 ACK 1 0 1 1
5 客户端→ ACK+PSH 1 100 101 1
6 ←服务端 ACK 1 0 1 101
7 客户端→ ACK+PSH 101 250 351 1
8 ←服务端 ACK 1 0 1 351
9 客户端→ ACK+PSH 351 30 381 1
10 ←服务端 ACK 1 0 1 381
11 客户端→ ACK+FIN 381 0 382 1
12 ←服务端 ACK 1 0 1 382
13 ←服务端 ACK+FIN 1 0 2 382
14 客户端→ ACK 382 0 382 2
3 个数据段的“序号 + 载荷”都出现在随后服务端的确认号里:3 / 3;最后一个确认号 382 = 1(SYN)+ 100 + 250 + 30 = 381(再加 FIN 占的 1 个序号就是 382)
规则:序号标的是这个段的第一个字节在字节流里的位置;确认号是“我期望收到的下一个字节的位置”;SYN 和 FIN 各占 1 个序号,所以握手之后的第一个数据字节序号是 ISN + 1序号 = 本段第一个字节在字节流里的位置;确认号 = 期望收到的下一个字节的位置。SYN 和 FIN 各占 1 个序号。
下载 4194304 字节:服务端发了 196 个数据段(最大载荷 16332 字节),客户端回了 45 个纯 ACK;平均每 4.4 个数据段回 1 个 ACK 相邻两个 ACK 之间确认号增加的字节数:最小 16332 / 中位 32664 / 最大 571620;总确认 4194305 字节 一个 ACK 确认的是“从头到这里的所有字节”(累积确认):中间少回几个 ACK 不会丢信息;ACK 越稀,开销越小,但发送方等 ACK 的时间可能更久
一个 ACK 确认从头到这里的所有字节,所以接收方可以隔几个段才回一个 ACK。
传输 4.50 GiB 用时 0.9 秒(4934 MiB/s);抓到服务端数据段 263593 个(其余因抓包丢包 / 去重没有统计) 服务端 ISN = 1355246696;相对序号的最大值 4294966465;绝对序号在 1 处从接近 2^32 回绕到小数:第 162573 个段后 4294960721 → 9757 回绕处:前一个段序号 4294960721(距 2^32 还差 6575),下一个段序号 9757 = (前一个序号 + 它的载荷) mod 2^32 = 9757:True 所以比较两个序号“谁在前”不能直接用大小比较,要看它们的差是否小于 2^31(TCP 实现里的 SEQ_LT 宏);时间戳选项的 PAWS(第 63 篇)就是为了在高速网络里防止回绕后的旧段被当成新段
序号是 32 位的,发满 4 GiB 会回绕,比较序号要用模 2^32 的差。高速网络里回绕很快,所以需要时间戳选项的 PAWS。
客户端:SYN 序号 737984981 → 发了 1000 字节 → FIN 的序号 737985982 = ISN + 1 + 1000 = 737985982:True;服务端最后确认客户端的 ACK 号 = 1002 = 1(SYN)+ 1000 + 1(FIN) = 1002 服务端:SYN-ACK 序号 2038976298 → 发了 500 字节 → FIN 的序号 2038976799 = ISN + 1 + 500 = 2038976799:True;客户端最后确认服务端的 ACK 号 = 502 = 1 + 500 + 1 = 502 一个连接里每个方向各有自己独立的序号空间:一个方向发了多少字节,和另一个方向无关
每个方向各有独立的序号空间。