📚 第六章 TCP与UDP
📄 一句话概括

TCP 发的是「段」不是「字节」:应用写多少都行,TCP 按 MSS 切。 MSS 是协商出来的,跟着 MTU 走:以太网 1460,隧道里更小。

📝 本节和前面几节的关系

6.4.1 提过 MSS,这一节讲「它是怎么定下来、怎么影响性能的」。 4.5.1 讲了 MTU 因介质而异,这一节看它怎么传导到 TCP。

一、为什么按段发

应用调 write(10000) 的时候,TCP 不会发一个 10000 字节的包: 它会按 MSS 切成七段左右(1460 乘 7 是 10220)。 所以「应用写一次」和「网络发几个包」没有对应关系,这也是「字节流」的又一层含义。 切段的目的是「避免 IP 分片」:每个段加 40 字节头之后正好是 MTU, 所以每个段都能装进一个 IP 包,不需要分片。 而分片的代价 4.5.2 算过:丢一片整个包作废,重传成本翻倍。

二、MSS 怎么协商

握手的时候,双方在 SYN 里各带一个 MSS 选项:意思是「我最多能接收这么大的段」。 最后各自按「对方报的值」来发,所以两个方向的 MSS 可以不一样。 如果一方没带这个选项:默认按 536 字节算(这是早期规范里的保守值)。 所以「老设备拖慢新连接」是真事——一个不报 MSS 的对端,会把整条连接的段压到 536。 这里有个容易混的点:MSS 是「TCP 层」的概念,MTU 是「IP 层」的概念。 路径上的路由器只认 MTU,它改不了 MSS,所以「路径 MTU 变小」只能靠 ICMP 通知(4.5.3), 或者靠「钳 MSS」这种在网关上加的手段。

三、段大小怎么影响性能

段越大,头开销越小、中断越少,但「大段」在丢包时更容易被「整段重传」。 所以这里有个权衡:小段灵活、大段高效。

段大小 头开销占比 丢一个的代价
536 约 7% 重传 536 字节
1460 约 2.7% 重传 1460 字节
8960 约 0.4% 重传 8960 字节

第一行的 536 是「保底值」:任何链路都能过,所以它永远不会因为「太大」而被丢。 代价是开销大、吞吐低。 第三行的 8960 只适合「整条路径都支持巨型帧」的机房内部,出了机房就会被丢。 这也解释了「为什么跨公网的 TCP 性能调优空间有限」: 段大小受 MTU 限制,而 MTU 由路径决定,你改不了。

四、动手 Demo:一个段最多装多少数据

算六种链路的 MSS,IPv4 和 IPv6 各一列。 两列差 20,因为 v6 的头是 40 字节。

for name, mtu in (('以太网', 1500), ('PPPoE', 1492), ('隧道后', 1420),
                   ('IPv6 底线', 1280), ('回环', 65483), ('巨型帧', 9000)):
    print('%-10s %6d %6d %6d' % (name, mtu, mtu - 40, mtu - 60))

实跑输出:

--------------------------------------------------------------------
一个段最多装多少数据
--------------------------------------------------------------------
**MSS 等于 MTU 减去两个头**:**IP 头 20 字节、TCP 头 20 字节,一共 40。**
**所以以太网上 MSS 就是 1460。**

  链路          MTU       IPv4 的 MSS     IPv6 的 MSS
  ----------------------------------------------------
  以太网        1500      1460            1440
  PPPoE         1492      1452            1432
  隧道后        1420      1380            1360
  IPv6 底线     1280      1240            1220
  回环          65483     65443           65423
  巨型帧        9000      8960            8940

**两列 MSS 差 20,因为 IPv6 的头是 40 字节**:
**同样是 1500 的 MTU,v6 能装的数据比 v4 少 20 字节。**
**这个差别在「隧道里跑 v6」时会叠加**——**5.7 讲的四层隧道,最后剩下的 MSS 比想象的小得多。**

**最后两行是「反常识」的**:
**回环 MTU 有 64 KB 量级,所以本机传输的段可以非常大;**巨型帧的 MTU 是 9000,MSS 就有 8960。**
**所以「MSS 是 1460」只对「走以太网的 IPv4」成立**,
**换个链路就不成立了**——**这也解释了为什么「同样的程序,在机房内和跨公网性能差很多」。**

「隧道后 1420」这一行最实用:它是 5.7 算出来的四层隧道结果, 所以「VPN 里 MSS 只剩 1380」不是猜的,是算出来的。 最后两行是「反常识」的:回环 MTU 有 6 万多,巨型帧有 9000, 所以「MSS 是 1460」只对「走以太网的 IPv4」成立。 这也解释了为什么「同样的程序在机房内和跨公网性能差很多」。 最后一个提醒:表里的数是「上限」,不是「实际」。 实际用哪个,还要看「对方报的 MSS」和「路径 MTU」,取小的那个。 所以「配了巨型帧但没生效」很常见——因为对端不支持。

30 秒实验

  1. 算一下:MTU 9000 的链路上,IPv6 的 MSS 是多少?
  2. 想一下:为什么路径上的路由器改不了 MSS?
  3. 想一下:段越大越好吗?

常见误区

  1. 以为「write 一次就发一个包」。 按 MSS 切成多个段,数量和应用的写法无关。
  2. 以为「MSS 和 MTU 是一回事」。 MSS 是 TCP 层,MTU 是 IP 层,中间差 40 字节。
  3. 以为「段越大越高效」。 丢包时重传代价也大,要按场景权衡。
自测三问(点开对答案) 1. MSS 是怎么确定的? —— **握手时双方各报一个自己能接收的值**,各按对方的来发,可以不对称。 2. 对端不报 MSS 时默认多少? —— **536 字节**,这是早期规范里的保守值。 3. 为什么 TCP 要按 MSS 切段? —— **让每个段加头后正好装进一个 IP 包,避免 IP 分片。**

下一节讲「一次能发多少」——窗口控制。

相关笔记