4. MSS 与 MTU:最大报文段长度、最大传输单元、分片与重组
聊到 TCP 粘包拆包,有个绕不开的话题就是 MSS 和 MTU。很多新手搞混这两个概念,甚至以为它们是一回事。我刚开始做网络开发时也踩过这个坑——明明应用层发了 5000 字节的数据,对端死活收不全,抓包一看,中间被路由器切成了好几片。
说白了,MTU 是链路层的限制,MSS 是 TCP 层的协商结果。它们之间的关系,决定了你的数据在网络里到底怎么走。
4.1 MTU:最大传输单元
MTU(Maximum Transmission Unit),指的是网络接口层能承载的最大数据包大小。以太网的标准 MTU 是 1500 字节。这个值不是随便定的,跟硬件、传输介质都有关系。
你想想看,如果 IP 层要发一个 3000 字节的包,网卡一次只能发 1500 字节,怎么办?分片。IP 层会把大包切成多个小片,每个片单独发送,到达目的地后再重组。
我记得有一次排查一个跨机房传输慢的问题,两边都是千兆网卡,但中间经过了一个 VPN 隧道。隧道封装后包变大了,超过了 1500,结果大量分片导致性能骤降。后来把 MTU 调成 1400,问题就解决了。
4.2 MSS:最大报文段长度
MSS(Maximum Segment Size),是 TCP 层的一个概念。它表示 TCP 数据段中实际载荷的最大长度,不包括 TCP 头部和 IP 头部。
MSS 是怎么来的?在 TCP 三次握手的时候,双方会交换 MSS 选项。计算公式很简单:
MSS = MTU - IP头部长度 - TCP头部长度
以标准以太网为例:
MSS = 1500 - 20 (IP头) - 20 (TCP头) = 1460 字节
如果启用了 TCP 时间戳等选项,TCP 头部会变成 32 字节,MSS 就变成:
MSS = 1500 - 20 - 32 = 1448 字节
4.3 分片与重组
分片发生在 IP 层,重组发生在接收端的 IP 层。这里有个容易混淆的点:TCP 层不做分片,TCP 只负责把数据切成 MSS 大小的段,然后交给 IP 层。如果 IP 层发现包太大,它自己会分片。
分片的细节:
- 每个 IP 分片都有相同的 Identification 标识符
- 通过 Fragment Offset 字段记录偏移量
- More Fragments (MF) 标志位表示是否还有后续分片
举个例子,假设 TCP 层交下来一个 3000 字节的数据段(含 TCP 头),加上 IP 头 20 字节,总共 3020 字节。以太网 MTU 1500,需要分片:
| 分片 | 数据长度 | 偏移量 | MF 标志 |
|---|---|---|---|
| 第 1 片 | 1480 字节 | 0 | 1 |
| 第 2 片 | 1480 字节 | 1480 | 1 |
| 第 3 片 | 60 字节 | 2960 | 0 |
注意,每个分片都包含 IP 头,但只有第一个分片包含 TCP 头。如果第一个分片丢了,接收端无法解析 TCP 端口号,整个包都得重传。
4.4 MSS 与 MTU 的关系图
下面这张图展示了从应用层数据到物理帧的完整封装过程,以及 MSS 和 MTU 的边界:
4.5 路径 MTU 发现
现实网络里,两端之间的路径上可能有不同的链路,MTU 不一定都是 1500。比如 PPPoE 拨号会把 MTU 降到 1492,某些 VPN 隧道可能更低。
TCP 协议有个机制叫 Path MTU Discovery (PMTUD),它通过发送设置了 Don't Fragment (DF) 标志的 IP 包来探测路径上的最小 MTU。如果中间路由器发现包太大且 DF 置位,就会返回一个 ICMP 消息:Fragmentation Needed,并告诉发送方该链路的 MTU 值。
4.6 粘包拆包与 MSS 的关系
回到粘包拆包的话题。MSS 决定了 TCP 层一次最多能发多少数据。如果应用层一次写入的数据超过 MSS,TCP 会分多次发送。这本身不是粘包,但会影响接收方的处理逻辑。
举个例子:
- 应用层发送 3000 字节的 JSON 数据
- TCP 层按 MSS=1460 切分成 3 个段:1460 + 1460 + 80
- 接收方可能一次 recv 读到 1460 字节,也可能一次读到 3000 字节(如果网络延迟低,多个段同时到达)
这就是粘包拆包的根源之一——应用层不知道 TCP 段边界在哪里。MSS 只是 TCP 层的分段单位,应用层必须自己定义消息边界。
4.7 抓包实战:观察 MSS 和分片
用 Wireshark 抓包时,怎么看 MSS 和分片?
- 查看 MSS 协商:找到三次握手的 SYN 包,展开 TCP 选项,找到 "Maximum Segment Size" 字段。客户端和服务器各自声明自己的 MSS。
- 观察分片:在 IP 层,如果看到多个 IP 包有相同的 Identification 值,且 Fragment Offset 不为 0,说明发生了分片。
- DF 标志:如果 IP 头部的 Flags 字段显示 "Don't fragment" 置位,说明发送方启用了 PMTUD。
我习惯在 Wireshark 里加一列 "MSS",方便快速查看每个 TCP 会话的 MSS 值。操作很简单:右键列头 → Column Preferences → 添加 "tcp.options.mss_val"。
嗯,MSS 和 MTU 这块内容就讲到这里。记住一句话:MTU 是链路层的硬限制,MSS 是 TCP 层的软协商。理解了它们的关系,你就能更好地分析网络传输中的各种奇怪现象了。