


文章目录
-
- [一、从完整的 TCP/IPv4/以太网封装建立长度模型](#一、从完整的 TCP/IPv4/以太网封装建立长度模型)
-
- [1. 分清四层协议的数据对象](#1. 分清四层协议的数据对象)
- [2. IPv4 协议长度上限与链路 MTU 不是同一个边界](#2. IPv4 协议长度上限与链路 MTU 不是同一个边界)
- [3. 路径 MTU 是整条路径的尺寸约束](#3. 路径 MTU 是整条路径的尺寸约束)
- [二、TCP 为什么分段:发送许可和单个报文段长度是两类限制](#二、TCP 为什么分段:发送许可和单个报文段长度是两类限制)
-
- [1. TCP 面向字节流,而非固定的应用消息边界](#1. TCP 面向字节流,而非固定的应用消息边界)
- [2. 当前允许发送多少数据,和每个报文段装多少数据,回答不同问题](#2. 当前允许发送多少数据,和每个报文段装多少数据,回答不同问题)
- [3. 5000 字节发送许可与 1460 字节单段上限的例子](#3. 5000 字节发送许可与 1460 字节单段上限的例子)
- [4. MSS 是面向接收方向声明的,并非 TCP 永远固定 1460](#4. MSS 是面向接收方向声明的,并非 TCP 永远固定 1460)
- [5. 200000 字节为什么不等于一个巨型 TCP 或 IPv4 数据报](#5. 200000 字节为什么不等于一个巨型 TCP 或 IPv4 数据报)
- [三、IPv4 分片需要满足哪些触发条件](#三、IPv4 分片需要满足哪些触发条件)
-
- [1. 对象:一个已经存在的 IPv4 数据报](#1. 对象:一个已经存在的 IPv4 数据报)
- [2. DF、MF 与保留位](#2. DF、MF 与保留位)
- [3. 两种尺寸不匹配的处理结果](#3. 两种尺寸不匹配的处理结果)
- [四、Fragment Offset 的 8 字节单位:怎样从字段宽度推导出对齐要求](#四、Fragment Offset 的 8 字节单位:怎样从字段宽度推导出对齐要求)
-
- [1. 13 位的偏移字段究竟表示什么](#1. 13 位的偏移字段究竟表示什么)
- [2. 为什么非末片载荷通常必须按 8 字节对齐](#2. 为什么非末片载荷通常必须按 8 字节对齐)
- [3. 为什么设计成 8 字节单位](#3. 为什么设计成 8 字节单位)
- [五、完整数字例题:4000 字节的 IPv4 数据报经过 MTU 1500 链路](#五、完整数字例题:4000 字节的 IPv4 数据报经过 MTU 1500 链路)
-
- [1. 列出全部前提](#1. 列出全部前提)
- [2. 计算每个分片的最大载荷](#2. 计算每个分片的最大载荷)
- [3. 第一片](#3. 第一片)
- [4. 第二片](#4. 第二片)
- [5. 第三片](#5. 第三片)
- [6. 完整表格与自检](#6. 完整表格与自检)
- [7. 为什么分片之后所有 IPv4 数据报的总长度相加反而增大](#7. 为什么分片之后所有 IPv4 数据报的总长度相加反而增大)
- [六、Identification、MF 与 Offset 怎样协作识别和恢复原始数据报](#六、Identification、MF 与 Offset 怎样协作识别和恢复原始数据报)
-
- [1. Identification 不是全网永久唯一的数据报编号](#1. Identification 不是全网永久唯一的数据报编号)
- [2. MF=0 告诉接收端在哪里结束,但不证明数据已经收齐](#2. MF=0 告诉接收端在哪里结束,但不证明数据已经收齐)
- [七、分片后 IPv4 首部会怎样变化](#七、分片后 IPv4 首部会怎样变化)
-
- [1. 新分片有自己的完整 IPv4 首部](#1. 新分片有自己的完整 IPv4 首部)
- [2. 通常相同的字段与必须重新确定的字段](#2. 通常相同的字段与必须重新确定的字段)
- [3. 如果原始 IPv4 首部包含 Options](#3. 如果原始 IPv4 首部包含 Options)
- [八、原来的 IPv4 分片仍可能再分片:Offset 不会重新从零计数](#八、原来的 IPv4 分片仍可能再分片:Offset 不会重新从零计数)
-
- [1. 再次遇到更小的 MTU](#1. 再次遇到更小的 MTU)
- [2. 第一原分片被分成 1A 与 1B](#2. 第一原分片被分成 1A 与 1B)
- [3. 第二原分片被分成 2A 与 2B](#3. 第二原分片被分成 2A 与 2B)
- [4. 第三原分片无需再分](#4. 第三原分片无需再分)
- [九、IPv4 重组:数据报身份、载荷位置、完整性与异常处理](#九、IPv4 重组:数据报身份、载荷位置、完整性与异常处理)
-
- [1. 在哪里执行重组](#1. 在哪里执行重组)
- [2. 重组步骤:识别、定位、判断是否齐全](#2. 重组步骤:识别、定位、判断是否齐全)
- [3. 乱序到达的具体过程](#3. 乱序到达的具体过程)
- [4. IP 首部如何参与重组](#4. IP 首部如何参与重组)
- [5. 分片丢失意味着什么](#5. 分片丢失意味着什么)
- [6. 为什么不能无限等待,以及重复/重叠的危险](#6. 为什么不能无限等待,以及重复/重叠的危险)
- [十、TCP 序列号与 IPv4 片偏移:两套排序为什么都必要](#十、TCP 序列号与 IPv4 片偏移:两套排序为什么都必要)
-
- [1. TCP 的编号对象是一个连接的单向字节流](#1. TCP 的编号对象是一个连接的单向字节流)
- [2. IPv4 分片的编号对象是一份原始 IP 载荷](#2. IPv4 分片的编号对象是一份原始 IP 载荷)
- [3. 为什么不能让 TCP 完全接管 IPv4 分片重组](#3. 为什么不能让 TCP 完全接管 IPv4 分片重组)
- 十一、PMTUD:发送端如何获知更合适的路径长度
-
- [1. 真正的 PMTU 并不总在一开始就被发送端准确知道](#1. 真正的 PMTU 并不总在一开始就被发送端准确知道)
- [2. DF=1 与 ICMP Destination Unreachable 的协作](#2. DF=1 与 ICMP Destination Unreachable 的协作)
- [3. 为什么 PMTU 可能需要逐步调整](#3. 为什么 PMTU 可能需要逐步调整)
- [4. TCP 如何按更小 PMTU 重新组织需要发送的字节](#4. TCP 如何按更小 PMTU 重新组织需要发送的字节)
- [5. PMTUD black hole:没有收到 ICMP 时会发生什么](#5. PMTUD black hole:没有收到 ICMP 时会发生什么)
- [6. 是否一定要依赖 ICMP](#6. 是否一定要依赖 ICMP)
- [十二、IPv4 分片的开销与风险:为什么通常尽量减少不必要的分片](#十二、IPv4 分片的开销与风险:为什么通常尽量减少不必要的分片)
-
- [1. 分片增加网络层与链路层封装开销](#1. 分片增加网络层与链路层封装开销)
- [2. 丢失一个必要分片可能导致整份原始数据报不能完成重组](#2. 丢失一个必要分片可能导致整份原始数据报不能完成重组)
- [3. 还存在接收端资源与异常处理负担](#3. 还存在接收端资源与异常处理负担)
- [4. 为什么也不能简单认为 TCP 分得越小越好](#4. 为什么也不能简单认为 TCP 分得越小越好)
- [十三、把发送、分片、重组与 TCP 接收放进一条真实的逻辑链](#十三、把发送、分片、重组与 TCP 接收放进一条真实的逻辑链)
- [十四、课堂计算练习:3200 字节数据报的分片推导](#十四、课堂计算练习:3200 字节数据报的分片推导)
- 十五、常见混淆:哪些看似相近的结论实际不能互相替代
一、从完整的 TCP/IPv4/以太网封装建立长度模型
1. 分清四层协议的数据对象
应用程序可以将应用协议消息编码成字节。TCP 向应用提供面向连接的有序字节流服务;发送时,TCP 将字节流中的相应字节组织成 TCP 报文段(TCP Segment)。在普通 TCP/IPv4 封装中,一份 TCP 报文段由一份 IPv4 数据报承载;以太网再将 IPv4 数据报封装成以太网帧。不能因为每层都在"包装数据",就断言每层都必须把多个上层协议数据单元合并成一个本层数据单元。
text
以太网帧
├── 以太网首部
├── IPv4 数据报
│ ├── IPv4 首部
│ └── IPv4 载荷:TCP 报文段
│ ├── TCP 首部
│ └── TCP 数据:字节流中的一段字节
└── 以太网 FCS
这些对象的"大小"并不相同。TCP 数据长度 只统计 TCP 首部后面的数据;TCP 报文段长度 包括 TCP 首部和 TCP 数据;IPv4 数据报总长度 包括 IPv4 首部和整个 IPv4 载荷;以太网帧长度还包括相应链路层的封装开销。
在普通无 IPv4/TCP 选项、无 VLAN 标签的 Ethernet II 示例中:
| 部分 | 长度 |
|---|---|
| TCP 数据 | 1460 字节 |
| TCP 首部 | 20 字节 |
| IPv4 首部 | 20 字节 |
| 以太网首部 | 14 字节 |
| 以太网 FCS | 4 字节 |
由此依次算出:
TCP 报文段 = 20 + 1460 = 1480 字节 IPv4 数据报 = 20 + 1480 = 1500 字节 以太网帧 = 14 + 1500 + 4 = 1518 字节 \begin{gathered} \text{TCP 报文段}=20+1460=1480\ \text{字节}\\ \text{IPv4 数据报}=20+1480=1500\ \text{字节}\\ \text{以太网帧}=14+1500+4=1518\ \text{字节} \end{gathered} TCP 报文段=20+1460=1480 字节IPv4 数据报=20+1480=1500 字节以太网帧=14+1500+4=1518 字节
最后的 1518 字节是上述普通以太网帧从目的 MAC 字段到 FCS 的长度,不包括物理层前导码等额外发送开销。不能将 1518 写进 IPv4 的 Total Length,也不能将 1500 当成 TCP 的纯数据长度。
2. IPv4 协议长度上限与链路 MTU 不是同一个边界
IPv4 首部的 Total Length 是 16 位、以字节为单位 的字段,涵盖 IPv4 首部和载荷,因此单份普通 IPv4 数据报的协议总长度上限为:
2 16 − 1 = 65535 字节 2^{16}-1=65535\ \text{字节} 216−1=65535 字节
这只说明数据报是否符合 IPv4 自身的长度编码边界。某段链路是否能将它作为一个完整网络层数据报直接承载,还受这段链路的 MTU 约束。
在本篇的普通 IPv4 以太网语境里,MTU(Maximum Transmission Unit,最大传输单元)可以理解为链路/接口一次允许承载的网络层数据报总长度,不含该以太网帧的首部和 FCS。
例如,一份 Total Length=4000 字节的 IPv4 数据报没有超过 IPv4 协议的 65535 字节上限,但无法作为一个完整的 4000 字节网络层数据报直接经由 MTU=1500 字节的普通链路发出。是否能分片、应丢弃还是改用其他处理,取决于 IPv4 分片规则和发送条件。
要分别问:
- 协议格式是否允许这个数据报长度? 看 IPv4 首部及 Total Length 等合法性要求。
- 能否不经分片通过当前输出链路? 看当前链路 MTU。
- 能否不经分片通过整条路径? 看当前实际路径的 PMTU。
- 超长时允许采取哪种处理? 看 DF 等控制条件与适用规则。
这四个问题不能相互替代。
3. 路径 MTU 是整条路径的尺寸约束
PMTU 是 Path MTU(路径 MTU)。在没有额外封装变化的简化路径上,它由沿途最严格的链路 MTU 决定。
text
A ──链路1,MTU 1500──► R1
R1 ─链路2,MTU 1400──► R2
R2 ─链路3,MTU 1280──► B
因此此例的真实路径 MTU 为:
PMTU = min ( 1500 , 1400 , 1280 ) = 1280 字节 \operatorname{PMTU}=\min(1500,1400,1280)=1280\ \text{字节} PMTU=min(1500,1400,1280)=1280 字节
如果 A 准确知道当前这条路径的真实 PMTU 是 1280 ,并且路径与封装条件没有变化,那么按不超过 1280 字节发送,就不会在这条路径上再遇到"比真实 PMTU 更小"的下一跳限制。实际障碍往往出在发送端已知或估计的 PMTU 不一定等于当前真实 PMTU:路由可能变更、信息可能过时、封装开销可能改变,路径 MTU 发现机制也可能暂时没有成功更新估计值。
也就是说,"已经按 PMTU 发包,但后面还遇到更小的 MTU"只有在明确区别已知/估计 PMTU 与实际 PMTU、路径变化等条件下才成立,不能无条件使用这个说法。
二、TCP 为什么分段:发送许可和单个报文段长度是两类限制
1. TCP 面向字节流,而非固定的应用消息边界
应用程序通过 Socket 向 TCP 提供一批字节,不表示"一次 write() 就产生一个 TCP 报文段";也不表示 TCP 必须把一次写入中的全部字节立即一次性发送。TCP 根据自身发送路径和当前状态,将字节流中可发送的部分组织成报文段。
TCP 分段的对象是 TCP 字节流中的字节,不是逐个识别、切割应用层的语义消息。 一个应用消息可以跨多个 TCP 报文段,多个应用写入的字节也可能由一个报文段承载。接收端应用读取的次数,同样不对应发送端的 write() 次数或网络上的报文段个数。
2. 当前允许发送多少数据,和每个报文段装多少数据,回答不同问题
第三阶段的阅读截图特别框选了这两个关系:
TCP 流量控制与拥塞控制,影响当前允许发送的数据量和发送进度。
MSS、路径 MTU 及 IP/TCP 首部开销等条件,影响单个报文段适合承载多少 TCP 数据。
它们必须同时考虑,却绝不能合并为同一个长度:
| 限制/状态 | 它主要回答什么 |
|---|---|
| TCP 流量控制 | 考虑接收方的接收窗口,发送端还能保有多少允许的在途数据范围? |
| TCP 拥塞控制 | 根据网络拥塞控制状态,发送端还能让多少数据进入网络并保持在途? |
| MSS(Maximum Segment Size) | 一个 TCP 报文段的TCP 数据部分可有多大? |
| PMTU 与首部开销 | 一个 TCP 报文段加上其 TCP/IP 首部后,是否适合不经分片通过当前路径? |
| 当前待发送字节与发送策略 | 虽有发送许可,是否已有足够的数据,以及何时、如何实际形成报文段? |
窗口限制通常涉及允许保有多少尚未确认的在途字节,不等于一条固定的"每秒发送多少字节"速率。 确认的返回、发送进度、往返时间及发送状态可能使可新增发送额度不断变化。
3. 5000 字节发送许可与 1460 字节单段上限的例子
假设某一时刻根据流量控制、拥塞控制以及已有未确认字节的状态计算,当前还允许新增发送 5000 字节 。这是一张瞬时的简化发送状态快照,而非"每秒固定发送 5000 字节",也不是每个 TCP 报文段必须承载 5000 字节。
再假设根据 MSS、PMTU、首部开销等条件,本次单个 TCP 报文段的数据上限为 1460 字节。如果待发送字节充足、没有其他限制,且为了演示选择尽量满载,可组织为:
text
报文段 1:TCP 数据 1460 B
报文段 2:TCP 数据 1460 B
报文段 3:TCP 数据 1460 B
报文段 4:TCP 数据 620 B
--------------------------------
合计: 5000 B
1460 + 1460 + 1460 + 620 = 5000 1460+1460+1460+620=5000 1460+1460+1460+620=5000
它说明这两个限制能同时得到满足,却不意味着实际 TCP 必须精确形成四个报文段、必须立刻发完,也不意味着这四个报文段属于同一次"同时在物理链路上发出"的操作。
另一种满足条件的组织方式是分为五个各承载 1000 字节的报文段;如果当前只有 500 字节的新发送许可,即使单段上限仍是 1460,也不能只凭上限就发送 1460 字节新数据。
当已有的在途字节获得确认、接收窗口与拥塞窗口发生变化时,发送端之后可能获得新的发送额度。因而应区分:
- 总额度:当前允许新增发送的数据总量,是一个会随状态变化的许可;
- 单段长度上限:当前单个 TCP 报文段的数据部分的长度边界;
- 实际发送:还取决于等待发送的数据、状态、时间与发送策略。
这是本阶段 FP-000015 追问真正建立的对象关系,不能仅仅记住算式。
4. MSS 是面向接收方向声明的,并非 TCP 永远固定 1460
TCP 建立连接时,通信端点可以使用 MSS 选项声明自己愿意接收的最大 TCP 报文段数据长度。两个方向可以分别声明:
text
A 声明接收 MSS = 1460
B 声明接收 MSS = 1200
于是 A 向 B 发送时需要考虑 B 声明的 1200;B 向 A 发送时需要考虑 A 声明的 1460。但实际发送还必须满足 PMTU、首部开销和其他发送约束。
例如,若接收端允许的 MSS 是 1460,实际可用路径 MTU 为 1280,且 IPv4 首部与 TCP 首部各 20 字节,则为避免分片,当前相应单段 TCP 数据上限至多为:
1280 − 20 − 20 = 1240 字节 1280-20-20=1240\ \text{字节} 1280−20−20=1240 字节
不能因为接收端允许 1460,就无视路径限制必然发送满 1460 数据;如果首部还有额外选项等开销,也需要重新计算。
流控与拥塞控制不是 MSS;MSS 不是 PMTU;PMTU 不是 IPv4 Total Length 的协议上限。 TCP 考虑 IP 路径限制,并不意味着 TCP 自己在执行 IPv4 分片。它仍然在按自己的协议职责组织 TCP 报文段。
5. 200000 字节为什么不等于一个巨型 TCP 或 IPv4 数据报
此前使用过一个辅助例子:应用程序一次向 TCP 提供 200000 字节。这表示一批 TCP 字节流数据,而非一个必须完整放入单份 IPv4 数据报的上层数据单元。
在假设有效单段 TCP 数据上限为 1460、所有相关发送条件允许、纯为数学演示而始终尽量满载的情况下,可组织为:
200000 = 136 × 1460 + 1440 200000=136\times1460+1440 200000=136×1460+1440
即 136 个满载报文段加一个载荷为 1440 字节的报文段,共 137 个。这不构成 TCP 实际必然发送恰好 137 个报文段或一次 write() 后立即全部发出的承诺。
三、IPv4 分片需要满足哪些触发条件
1. 对象:一个已经存在的 IPv4 数据报
分片发生时,IPv4 的输入对象是一份已经形成的 IPv4 数据报,其格式为:
text
IPv4 首部 + IPv4 载荷
IPv4 分片是将这份原始 IPv4 载荷按分片规则分成若干部分,再为每个分片形成自己的合法 IPv4 首部。它既不是把多个独立 TCP 报文段汇集后再拆开,也不是把原来的 IPv4 首部切成几个残缺小块。
2. DF、MF 与保留位
IPv4 首部的 Flags 共 3 位:
text
┌──────────┬──────┬──────┐
│ 保留位 0 │ DF │ MF │
└──────────┴──────┴──────┘
- 保留位按协议要求为 0。
- DF(Don't Fragment):DF=1 禁止对该数据报进行分片;DF=0 允许在满足 IPv4 规则时分片。
- MF(More Fragments):MF=1 表示在原始载荷的当前位置之后还有后续分片;MF=0 表示最后一部分,或表示这份数据报根本没有分片。
完整未分片数据报通常 Offset=0、MF=0,但它的 DF 可以按实际发送控制需要为 0 或 1。
3. 两种尺寸不匹配的处理结果
假设一个 4000 字节 IPv4 数据报要经由 MTU=1500 的链路继续发送:
text
总长度 4000 > 链路 MTU 1500
- 如果
DF=0,在满足有关协议条件下,IPv4 可以将其分成多个符合链路 MTU 的分片。 - 如果
DF=1,不允许这样分片。不能把完整 4000 字节数据报强行送入 MTU 为 1500 的链路;相应发送/转发路径需要丢弃或进行适用的错误处理。对不能继续转发的中间 IPv4 路由器,会涉及相应 ICMP 差错反馈。
IPv4 分片可能由发送主机产生,也可能在允许的情况下由中间 IPv4 路由器因更小的下一段链路 MTU 产生。IP 分片能够存在,并不意味着所有实际 TCP 发送都应该让中间路由器分片。 发送端使用合适的报文段和 PMTU 信息通常可以减少不必要的分片。
四、Fragment Offset 的 8 字节单位:怎样从字段宽度推导出对齐要求
1. 13 位的偏移字段究竟表示什么
IPv4 首部中与分片直接相关的三个字段是:
| 字段 | 位宽 | 描述对象 |
|---|---|---|
| Identification | 16 bit | 参与识别属于同一原始 IPv4 数据报的分片 |
| Flags | 3 bit | 是否允许分片、原始载荷后面是否还有数据等 |
| Fragment Offset | 13 bit | 该分片载荷相对于原始 IPv4 载荷起点的位置 |
Fragment Offset 的单位不是 1 字节,而是 8 字节 :
实际载荷起点偏移(字节) = Fragment Offset 字段值 × 8 \boxed{\text{实际载荷起点偏移(字节)}=\text{Fragment Offset 字段值}\times8} 实际载荷起点偏移(字节)=Fragment Offset 字段值×8
例如 Offset=185 表示其载荷从原始 IPv4 载荷的偏移量 1480 字节处开始,而不是从原始 IPv4 首部起点的第 185 字节开始。原始 IPv4 首部有 20 字节,也不能把这 20 字节额外加进字段所记录的偏移量。
2. 为什么非末片载荷通常必须按 8 字节对齐
假设一个分片还有后续分片,它承载了 1001 字节载荷。后面的下一片应从原始载荷偏移 1001 字节处开始,但:
1001 / 8 = 125.125 1001/8=125.125 1001/8=125.125
一个 13 位整数偏移字段不能精确填写 125.125。因而,当前分片后面还有其他载荷时,其载荷长度必须满足相应的 8 字节整数倍要求,使下一片能用整数 Offset 表示起点。最后分片由于后面不再有下一片的起点待编码,其载荷长度不必凑成 8 的整数倍。
对于本篇从原始未分片数据报开始进行的普通分片:前面每一片的载荷长度均选为 8 的整数倍;最后一片可为其他长度。
3. 为什么设计成 8 字节单位
13 位无符号字段一共有:
2 13 = 8192 2^{13}=8192 213=8192
种取值。如果一个单位只能表示一字节,就只能表示约 8 KiB 范围的字节起点,远小于普通 IPv4 数据报接近 64 KiB 的最大长度范围。将单位扩展为 8 字节后,编码能够覆盖相应大小的数据位置范围:
8192 × 8 = 65536 字节范围(按单位计) 8192\times8=65536\ \text{字节范围(按单位计)} 8192×8=65536 字节范围(按单位计)
这个设计用较少的字段位数换取更大的位置表达范围,代价就是要求合法分片的起点满足 8 字节对齐。这不是 TCP Sequence Number 的单位;TCP Sequence Number 描述的是 TCP 字节流中的字节位置。
五、完整数字例题:4000 字节的 IPv4 数据报经过 MTU 1500 链路
1. 列出全部前提
假设一份合法的原始 IPv4 数据报已形成:
text
IPv4 原始 Total Length = 4000 字节
原始 IPv4 首部 = 20 字节(IHL=5)
无 IPv4 Options
Identification = 0x1234
DF = 0
下一段输出链路 MTU = 1500 字节
为了合理呈现这一场景,可假设它此前经过能容纳较大数据报的链路,现到达一段 MTU 更小的链路;不应理解为一台 MTU=1500 的发送网卡此前就能不经分片把这份 4000 字节数据报发上同一链路。
第一步,计算真正需要被分片的原始 IPv4 载荷 :
4000 − 20 = 3980 字节 4000-20=3980\ \text{字节} 4000−20=3980 字节
注意被划分的是 3980 字节载荷,而不是连同原来 20 字节首部一起机械切割 4000 字节。
2. 计算每个分片的最大载荷
每个分片都有自己的 IPv4 首部,本例均为 20 字节。
每片载荷最大长度 = 1500 − 20 = 1480 字节 \text{每片载荷最大长度}=1500-20=1480\ \text{字节} 每片载荷最大长度=1500−20=1480 字节
检查 8 字节对齐:
1480 / 8 = 185 1480/8=185 1480/8=185
1480 正好是 8 的整数倍,可用于后面仍有载荷的非末片。
3. 第一片
从原始 IPv4 载荷偏移 0 开始,取出 1480 字节。
text
分片 1:
IPv4 首部长度 = 20
IPv4 载荷长度 = 1480
Total Length = 1500
Fragment Offset = 0
MF = 1
ID = 0x1234
取完还剩:
3980 − 1480 = 2500 字节 3980-1480=2500\ \text{字节} 3980−1480=2500 字节
MF=1 是因为后面确实还有原始载荷部分。
4. 第二片
继续取出 1480 字节。
text
分片 2:
IPv4 首部长度 = 20
IPv4 载荷长度 = 1480
Total Length = 1500
Fragment Offset = 185
MF = 1
ID = 0x1234
其原始载荷起始字节偏移为:
1480 / 8 = 185 1480/8=185 1480/8=185
此时原始载荷还剩:
2500 − 1480 = 1020 字节 2500-1480=1020\ \text{字节} 2500−1480=1020 字节
5. 第三片
把剩余的 1020 字节装入最后一片。
text
分片 3:
IPv4 首部长度 = 20
IPv4 载荷长度 = 1020
Total Length = 1040
Fragment Offset = 370
MF = 0
ID = 0x1234
第三片起点为先前两片载荷之和:
( 1480 + 1480 ) / 8 = 2960 / 8 = 370 (1480+1480)/8=2960/8=370 (1480+1480)/8=2960/8=370
虽 1020/8 不是整数,但它是原始载荷最后一部分,因此不需要为了生成下一片偏移而把该长度向下凑齐。
6. 完整表格与自检
| 分片 | IPv4 首部 | 本片 IPv4 载荷 | Total Length | Offset | MF | 原始载荷字节偏移范围 |
|---|---|---|---|---|---|---|
| 1 | 20 B | 1480 B | 1500 B | 0 | 1 | 0~1479 |
| 2 | 20 B | 1480 B | 1500 B | 185 | 1 | 1480~2959 |
| 3 | 20 B | 1020 B | 1040 B | 370 | 0 | 2960~3979 |
检验分三类:
内容守恒 :
1480 + 1480 + 1020 = 3980 字节 1480+1480+1020=3980\ \text{字节} 1480+1480+1020=3980 字节
每一分片满足输出链路 MTU :
20 + 1480 = 1500 , 20 + 1480 = 1500 , 20 + 1020 = 1040 20+1480=1500,\quad20+1480=1500,\quad20+1020=1040 20+1480=1500,20+1480=1500,20+1020=1040
原始载荷位置连续、无重叠遗漏:
text
0~1479 → 1480~2959 → 2960~3979
这里每个 Offset 字段是 8 字节单位,不应误认为它们分别是 0、1480、2960。
7. 为什么分片之后所有 IPv4 数据报的总长度相加反而增大
三个分片的 Total Length 之和为:
1500 + 1500 + 1040 = 4040 字节 1500+1500+1040=4040\ \text{字节} 1500+1500+1040=4040 字节
但原始 IPv4 数据报只有 4000 字节。原因是原来只有一个 20 字节 IPv4 首部,分片后有三个 20 字节 IPv4 首部:
( 3 − 1 ) × 20 = 40 字节额外开销 (3-1)\times20=40\ \text{字节额外开销} (3−1)×20=40 字节额外开销
载荷仍只有 3980 字节,并没有凭空增减。多出的 40 字节是新增的两份首部。此外,各分片在链路上传输时,还需要各自的链路层封装,实际发送开销还会增加。
六、Identification、MF 与 Offset 怎样协作识别和恢复原始数据报
1. Identification 不是全网永久唯一的数据报编号
在上述三个分片中,Identification=0x1234 相同,是因为它们关联到同一份原始 IPv4 数据报。对于需要分片重组的 IPv4 数据报,接收端典型地联合考虑以下信息:
text
源 IPv4 地址
目的 IPv4 地址
Protocol
Identification
例如:
text
源 IP = 192.0.2.10
目的 IP = 198.51.100.20
Protocol = 6(TCP)
Identification = 0x1234
这组信息有助于把当前重组时段中属于同一原始数据报的分片关联起来。不能只看 Identification 相同就把两个分片拼在一起:不同源、不同目的地址或不同 Protocol 等条件下,可以出现数值相同的 Identification。ID 只有 16 位,不可能是覆盖互联网所有 IPv4 数据报的永久唯一身份编号。
此外,关于 IPv4 ID 的现代规范还有细节:对 DF=1、MF=0、Offset=0 的所谓 atomic datagram,ID 不需要具有分片重组意义。不能把"每一份 IPv4 数据报的 ID 都必须按全网统一规则持续增长且唯一"当成普遍协议要求。
2. MF=0 告诉接收端在哪里结束,但不证明数据已经收齐
对于末片:
text
Offset = 370
载荷长度 = 1020 B
MF = 0
接收端知道末片起始处为 370×8=2960,因此原始载荷应有:
2960 + 1020 = 3980 字节 2960+1020=3980\ \text{字节} 2960+1020=3980 字节
这给出了原始载荷尾端 。但是,如果中间偏移 1480~2959 的内容尚未到达,即使已经收到了 MF=0 的分片,也不能完成重组。
相反,即使先收到 Offset=0、MF=1 的首片,也无法由此判断整个原始数据报究竟有多长;它只说明该片后面还有数据。
因此三个信息分别回答:
| 信息 | 回答的问题 |
|---|---|
| 源/目的 IP、Protocol、Identification 等 | 这个分片应归属于哪一个重组对象? |
| Fragment Offset + 本片载荷长度 | 这个分片携带原始载荷的哪段字节范围? |
| MF=0 的末片 | 原始载荷最终应该在哪里结束? |
属于谁、放在哪里、从哪里算齐,缺一不可。它们还必须与长度合法性、完整性等条件一起检查。
七、分片后 IPv4 首部会怎样变化
1. 新分片有自己的完整 IPv4 首部
分片处理包含两个独立部分:划分原始 IPv4 载荷;为每一份产生合法的、可继续转发的 IPv4 首部。绝不是:
text
错误模型:将 20 字节原始 IPv4 首部切成三个小块,
再给三个 TCP 数据片各塞一小块。
正确模型是:
text
原始:
[IPv4 首部][ 原始 IPv4 载荷 ]
分片后:
[本片完整 IPv4 首部][载荷第 1 部分]
[本片完整 IPv4 首部][载荷第 2 部分]
[本片完整 IPv4 首部][载荷第 3 部分]
这里"本片完整首部"是"符合当前分片要求的 IPv4 首部",不意味着每片的首部字节都与原始首部完全相同。
2. 通常相同的字段与必须重新确定的字段
在没有地址转换、没有 IPv4 Options 的普通示例中,同一原始数据报的分片通常保留:
- Version,以及该分片所需的基础格式;
- 原始源 IPv4 地址和目的 IPv4 地址;
- Protocol(例如 6 表示所承载的原始载荷属于 TCP);
- 对同一原始分片重组对象使用的 Identification。
但下列字段需要按每片实际内容重新确定或正确处理:
Total Length:本片首部长度加上本片载荷长度;Fragment Offset:本片载荷在原始 IPv4 载荷中的位置,单位 8 字节;MF:按原始载荷后面是否还有剩余部分决定;Header Checksum:必须与这份分片自己的 IPv4 首部内容一致,不能直接无条件沿用原始首部的校验值;- 发生正常转发时,
TTL等转发字段需遵循相应转发处理规则。
TTL 的变化与路由转发行为相关,不能把"产生三片"误认为"同一次转发下 TTL 必须连续额外减三次"。关于 TTL 耗尽、ICMP 和 IPv4 首部校验和的完整计算,则属于下一阶段的正式教学。
3. 如果原始 IPv4 首部包含 Options
没有 Options 时,IHL=5、20 字节首部的例子可以用于所有分片。但 IPv4 首部也可能有可选项。IPv4 Options 存在与分片复制有关的控制位:有些选项需要复制到全部分片,有些则无需复制到每片。于是不同分片的 IHL/首部长度可能不同。
例如从概念上可以出现:
text
某分片首部:28 B
另一分片首部:24 B
它们是否实际出现,要由原始的具体 Options 和分片规则决定,不能任意挑几个首部长度就声称一定存在这种报文。
一旦各片首部不同,长度计算就需要分别进行:
本片载荷可用空间 = 当前链路 MTU − 本片 IPv4 首部长度 \text{本片载荷可用空间}=\text{当前链路 MTU}-\text{本片 IPv4 首部长度} 本片载荷可用空间=当前链路 MTU−本片 IPv4 首部长度
而非机械给每一片都减 20。还必须满足每个分片自身的 Total Length 与偏移对齐约束。本轮已经解释到"复制规则会造成不同分片的首部和数据空间可能不同";并没有逐个研究所有历史 IPv4 Option 的二进制格式,不能把那些细节假作已学习成果。
八、原来的 IPv4 分片仍可能再分片:Offset 不会重新从零计数
1. 再次遇到更小的 MTU
继续沿用 4000 字节原始 IPv4 数据报:其载荷 3980 字节,先经 MTU=1500 链路形成:
| 原分片 | Offset | 载荷 | Total Length | MF |
|---|---|---|---|---|
| 1 | 0 | 1480 | 1500 | 1 |
| 2 | 185 | 1480 | 1500 | 1 |
| 3 | 370 | 1020 | 1040 | 0 |
如果继续遇到下一段 MTU=1400 的链路,且仍允许分片,前两片的 Total Length 1500 均超过下一段 MTU;第三片总长度 1040,在长度上无须再分片。
假设首部均为 20 字节,则理论可用载荷空间为:
1400 − 20 = 1380 字节 1400-20=1380\ \text{字节} 1400−20=1380 字节
非末片为了 8 字节对齐,选取最大整数倍:
⌊ 1380 / 8 ⌋ × 8 = 172 × 8 = 1376 \left\lfloor 1380/8\right\rfloor\times8=172\times8=1376 ⌊1380/8⌋×8=172×8=1376
因此,每个原来有 1480 字节载荷的分片,可再按 1376 字节和 104 字节划分;104/8=13,也符合需要的对齐关系。
2. 第一原分片被分成 1A 与 1B
text
1A:载荷 1376 B,Total Length 1396 B
Offset=0,MF=1
1B:载荷 104 B,Total Length 124 B
Offset=172,MF=1
因为第二段载荷的原始数据报起点已经是:
1376 / 8 = 172 1376/8=172 1376/8=172
最容易误判的是 1B 的 MF 。尽管 1B 是"原分片 1 自己的最后部分",它却不是"最初原始数据报的最后部分";原始载荷之后还有原分片 2 和分片 3。所以 1B 的 MF 仍是 1,不能改成 0。
3. 第二原分片被分成 2A 与 2B
原分片 2 的起始 Offset 为 185,因此:
text
2A:载荷 1376 B,Total Length 1396 B
Offset=185,MF=1
2B:载荷 104 B,Total Length 124 B
Offset=357,MF=1
2B 的 Offset 为:
185 + 1376 / 8 = 185 + 172 = 357 185+1376/8=185+172=357 185+1376/8=185+172=357
4. 第三原分片无需再分
其原先为:
text
分片 3:载荷 1020 B,Total Length 1040 B
Offset=370,MF=0
1040 不超过 MTU=1400,所以在本例中无需进一步分片。
最终重组视角下的全部分片是:
| 最终分片 | 载荷起始字节偏移 | Offset(8 字节单位) | 载荷长度 | Total Length | MF |
|---|---|---|---|---|---|
| 1A | 0 | 0 | 1376 | 1396 | 1 |
| 1B | 1376 | 172 | 104 | 124 | 1 |
| 2A | 1480 | 185 | 1376 | 1396 | 1 |
| 2B | 2856 | 357 | 104 | 124 | 1 |
| 3 | 2960 | 370 | 1020 | 1040 | 0 |
这些载荷依次覆盖:
text
0~1375
1376~1479
1480~2855
2856~2959
2960~3979
仍然完整覆盖原始 3980 字节。再次分片时没有把 Offset 的计数基准改为"原分片自己的起点";所有最终分片仍相对于最初原始 IPv4 载荷起点定位。因此,接收端不需要先把 1A+1B 合成原分片 1、2A+2B 合成原分片 2,然后再另做一次从头开始的二级重组;根据原始载荷位置即可恢复完整内容。
九、IPv4 重组:数据报身份、载荷位置、完整性与异常处理
1. 在哪里执行重组
普通 IPv4 网络中,分片可以由中间路由器分别进行转发,不要求每一个普通路由器先收齐所有分片、重组完整数据报,再继续转发。典型的正常重组位置是最终目的主机的 IP 层。 某些隧道、安全或特殊中间网络功能可能有自己的处理,不可把这些特例写成普通路由器每跳必须重组的规则。
text
发送端 IP:一份 IPv4 数据报
↓
若干 IPv4 分片
↓
路由器分别转发
↓
目的端 IPv4:将有效分片关联、校验、重组
↓
原始完整 IPv4 载荷
↓
按 Protocol=6 等标识交给 TCP 或其他协议
2. 重组步骤:识别、定位、判断是否齐全
第一步,关联到正确的原始数据报。 接收端结合源 IP、目的 IP、Protocol、Identification 等相关信息,寻找对应的重组上下文,而不是把任意收到的三个分片拼起来。
第二步,记录每片原始载荷范围。 对每片计算:
载荷起始字节偏移 = Offset × 8 \text{载荷起始字节偏移}=\text{Offset}\times8 载荷起始字节偏移=Offset×8
然后结合该片实际 IPv4 载荷长度,确定所覆盖的字节范围。
第三步,识别末尾。 末片 MF=0 告诉接收端原始载荷最后到哪里结束;但末片可以最先到达,收到它不等于之前全部字节已经到齐。
第四步,检查完整性和合法性。 除确认范围被覆盖,还要检查分片的相关尺寸、偏移与其他合法性条件;不能只数"我收到了三片"就宣布成功。
第五步,形成正确的完整数据报表示。 载荷根据偏移恢复正确排列,IPv4 首部信息按照重组规则形成与完整数据报相适应的表示。不能把每片的 IPv4 首部一股脑拼接到载荷中间。
3. 乱序到达的具体过程
沿用 4000 字节原始数据报的三片:
| 分片 | Offset | 载荷范围 | MF |
|---|---|---|---|
| 1 | 0 | 0~1479 | 1 |
| 2 | 185 | 1480~2959 | 1 |
| 3 | 370 | 2960~3979 | 0 |
假设真实到达顺序是 3 → 1 → 2:
- 先收到 3:知道原始载荷应到偏移 3979 截止,却没有前面的数据。
- 再收到 1:知道起始范围 0~1479 已到齐,但 1480~2959 尚缺。
- 最后收到 2:三段合法且无缺口时,才可以恢复完整 3980 字节载荷。
在无 Options 的当前例子中,完整逻辑 IPv4 数据报仍由 20 字节首部与 3980 字节载荷组成,其 Total Length 为 4000 字节。重组不是"把三个分片的 Total Length 相加"得到 4040 字节,更不是保留三份 IPv4 首部当作完整 IP 载荷的内容。
4. IP 首部如何参与重组
原始数据报传输过程中,路由器可能已合法改变 TTL 等字段;某些分片首部字段还因分片位置和长度不同而变化。因此,接收端要形成合适的重组后首部/数据报表示 ,并不意味着它必须逐位复原发送主机发出瞬间的原始 IP 首部。
对于当前无 Options 示例,至少要理解最终应有正确的总长度、分片已完成处理后的相关首部状态,以及匹配有效重组结果的网络层信息。具体内核可能以内存缓冲、元数据等形式表示重组后的报文,并非必须实现为某个唯一的内存复制流程。
5. 分片丢失意味着什么
假设 1 和 3 到达,但 2 永远没有到达:
text
已有:0~1479
缺少:1480~2959
已有:2960~3979
IP 层无法由这两段恢复中间缺失的实际字节,因此不能正常交付完整原始 IPv4 数据报。IP 层也不提供 TCP 那样的端到端可靠重传服务。
如果原始载荷属于 TCP,TCP 可能因为相关字节没有成功到达,随后通过其确认与重传机制恢复缺失的 TCP 字节;但这不是"TCP 直接命令 IP 只重传第 2 个分片",也不能把几个尚未完整重组的分片直接交给 TCP 代替 IP 重组。
6. 为什么不能无限等待,以及重复/重叠的危险
未完整的数据报会占用重组状态和缓冲资源。若缺失分片长时间不到,协议栈需要按照相应的计时和资源规则放弃不完整重组并释放资源。在符合条件的情形下,重组超时可能涉及 ICMP Time Exceeded 反馈,但并不保证每一次分片重组失败都可靠地向发送端应用程序报告。
此外,还可能发生:
- 重复分片到达;
- 不同分片覆盖相同载荷范围(分片重叠),甚至内容互相矛盾;
- 分片长度或偏移不合法;
- 分片标识冲突、资源不足;
- 利用分片重叠造成解析歧义或其他安全问题。
因此"能把片按偏移排序"还不够;还必须满足完整覆盖、协议合法性以及实现中的安全检查。不同历史 IPv4 实现对某些重叠分片的处理可能有差异,不能据此声称所有系统在冲突时都采用同一拼接方案。
十、TCP 序列号与 IPv4 片偏移:两套排序为什么都必要
这个关系在前一阶段追问中已经形成基础,本阶段正式分片和重组教学使它成为可计算的具体机制。
1. TCP 的编号对象是一个连接的单向字节流
TCP Sequence Number 指示携带普通数据的某份 TCP 报文段,其数据部分第一个字节在相应 TCP 发送方向的字节序列空间中的位置;它不是为每一个 TCP 报文段简单编一个"第 1 段、第 2 段"的序号。
假设某方向上依次发送三个各携带 1000 字节的 TCP 报文段:
text
TCP 报文段 A:SEQ=1000,字节 1000~1999
TCP 报文段 B:SEQ=2000,字节 2000~2999
TCP 报文段 C:SEQ=3000,字节 3000~3999
这是忽略 SYN 等控制标志对序列号空间影响的简化例子。即使这些报文段以 C、A、B 的顺序抵达,TCP 仍需要根据序列号和接收状态管理连续字节范围,按协议规则向应用提供有序、不重复的字节流。
2. IPv4 分片的编号对象是一份原始 IP 载荷
如果承载 TCP 报文段 B 的原始 IPv4 数据报发生分片:
text
IPv4 分片 B1:
[IPv4 分片首部][TCP 首部 + 一部分 TCP 数据]
IPv4 分片 B2:
[IPv4 分片首部][另一部分 TCP 数据]
B2 可能根本没有独立的 TCP 首部,也就不是一个可以直接凭自己的 TCP Sequence Number 识别的完整 TCP 报文段。IPv4 的 Fragment Offset 描述的是 B 这一份原始 IPv4 数据报载荷内部的位置,不是"TCP 连接累计传输字节数"或"应用所有消息的位置"。
3. 为什么不能让 TCP 完全接管 IPv4 分片重组
假设接收顺序为:
text
首先:承载 TCP C 的完整 IPv4 数据报
然后:IPv4 分片 B2
随后:承载 TCP A 的完整 IPv4 数据报
最后:IPv4 分片 B1
IP 层可以将完整的 TCP C、TCP A 报文段陆续交给 TCP;但对 B2,因它是原始数据报的非首片,无法仅凭 B2 的内容把它作为完整 TCP 报文段交付。等 B1 与 B2 到齐并有效重组后,IP 层才能把原始 TCP B 报文段交给 TCP。随后 TCP 再根据 A、B、C 中的字节序列范围管理整个连接方向上的字节流。
因此存在两个次序恢复过程:
text
同一个原始 IPv4 数据报内部:
B2 与 B1 片乱序到达
↓
IP 按 Fragment Offset 重组完整原始载荷
↓
恢复完整 TCP 报文段 B
不同 TCP 报文段之间:
报文段 C、A、B 不按预期次序到达
↓
TCP 按相应方向的 Sequence Number 和连接状态处理
↓
向应用提供按序字节流
进一步说,IPv4 还可能承载 UDP、ICMP 等协议,这些协议未必具有 TCP 一样的字节流序列号。因此 IPv4 分片重组必须是本层能够承担的机制,不可能普遍依赖 TCP Sequence Number 完成。
共同涉及位置,不等于可以用同一套位置字段替代两套协议职责。
十一、PMTUD:发送端如何获知更合适的路径长度
1. 真正的 PMTU 并不总在一开始就被发送端准确知道
本机通常能得知自身输出网络接口的 MTU,却未必知道通往远端的所有下一段链路当前 MTU。为了减少不必要的分片,发送端可以维护对路径 MTU 的估计,并利用反馈或探测机制调整发送大小。
经典 IPv4 PMTUD(Path MTU Discovery)的基本思路依赖 DF 与有关 ICMP 差错反馈。此处是为了讲 PMTUD 而使用 ICMP;ICMP 协议与网络诊断的完整体系仍属于后续阶段。
2. DF=1 与 ICMP Destination Unreachable 的协作
假设路径:
text
A --MTU 1500--> R1 --MTU 1400--> R2 --MTU 1280--> B
A 当前以为可用 1500 字节,发送:
text
IPv4 Total Length=1500
DF=1
第一段链路允许其不分片通过。但 R1 转发到下一段 MTU=1400 的链路时发现:
text
需要转发的 IPv4 数据报:1500 B
当前下一段链路 MTU:1400 B
DF=1:禁止分片
因此不能一边保留整份 1500 字节数据报,一边无视 MTU 强行转发;也不能违反 DF 直接拆成更小的 IPv4 分片。相应路由转发处理会丢弃该报文,并按照适用的规则返回 IPv4 ICMP 差错报告。
相关经典 ICMP 消息为:
text
ICMP Destination Unreachable
Type = 3
Code = 4
含义:需要分片才能继续转发,但 DF 禁止分片
在支持相应格式的情况下,反馈中可携带下一跳 MTU 信息。注意 ICMP 报文仍需要经过网络传输,它不是路由器直接向 Java 进程内存写入一条通知;中途也可能被过滤或丢失。
3. 为什么 PMTU 可能需要逐步调整
收到有关反馈后,发送端可以修正自己对当前路径的 MTU 估计,例如由 1500 调整为 1400。此时路径后面仍有 MTU=1280 的链路,因此后续尝试仍可能遇到更严格限制并更新估计。
这不是要求 A 先获得全网每台路由器完整 MTU 表、再一次性计算。发送端可以逐步利用可获得的协议反馈调整估计。
4. TCP 如何按更小 PMTU 重新组织需要发送的字节
假设原先某个 TCP 报文段携带了 1460 字节数据;系统后来确定当前发送路径适合的 PMTU 是 1280。假设 IPv4 和 TCP 首部各 20 字节:
1280 − 20 − 20 = 1240 字节 TCP 数据 1280-20-20=1240\ \text{字节 TCP 数据} 1280−20−20=1240 字节 TCP 数据
原先需要重发或继续发送的 1460 字节,可以在示例中组织为:
text
新的 TCP 报文段 A:1240 B TCP 数据
新的 TCP 报文段 B: 220 B TCP 数据
它们的 IPv4 Total Length 分别是:
20 + 20 + 1240 = 1280 字节 20 + 20 + 220 = 260 字节 \begin{gathered} 20+20+1240=1280\ \text{字节}\\ 20+20+220=260\ \text{字节} \end{gathered} 20+20+1240=1280 字节20+20+220=260 字节
这里表达的是发送字节可以按新的发送条件重新组织,不是说 TCP 必须机械地拿之前那个报文段对象切成恰好两半、完全保留原分段边界。TCP 的可靠传输管理基础是字节序列,而不是要求每次重传都恰好复现旧的报文段排列。
5. PMTUD black hole:没有收到 ICMP 时会发生什么
假设发送端不断发送 1500 字节、DF=1 的 IPv4 数据报,实际路径却只能不经分片通过 1280 字节,而相应 ICMP 差错消息又被丢弃或过滤。发送端可能无法及时获知过大的尺寸,导致较小报文能通过而较大报文持续失败。这是经典 PMTUD 黑洞(PMTUD black hole)问题的一种典型表现。
但不能反过来认定:只要大报文失败、小报文成功,就一定是 PMTUD 黑洞。 其他网络拥塞、策略或设备故障也可能产生类似现象,仍需进一步区分条件。
6. 是否一定要依赖 ICMP
不一定。还存在 Packetization Layer Path MTU Discovery(PLPMTUD)等由报文组织层利用探测报文与结果逐步判断合适尺寸的机制,能够在一定程度上减少对 ICMP 必然送达的依赖。
但"发一个大包,没有收到确认"并不能单独证明它因 MTU 过大而失败,因为拥塞丢包等其他原因也可能导致未收到确认。真正的探测需要相应的探测控制、判定与恢复逻辑。
目前已系统解释的是经典 DF/ICMP PMTUD 的机制链、TCP 如何利用路径信息调整段长,以及 PLPMTUD 为什么有必要、为何不能粗暴归因;没有逐项教学特定操作系统 PMTU 缓存的数据结构、所有探测状态机与底层实现。
十二、IPv4 分片的开销与风险:为什么通常尽量减少不必要的分片
1. 分片增加网络层与链路层封装开销
原来一份 IPv4 数据报有一份首部,分片后每个分片都需要自己的 IPv4 首部。前面 4000 字节变成三片的例子,三个分片 Total Length 总和为 4040 字节,多出 40 字节网络层首部。各片还需要分别进行相应链路层封装和处理。
2. 丢失一个必要分片可能导致整份原始数据报不能完成重组
例如:
text
原始数据报 → 分片 1、2、3
↓
1到达、2丢失、3到达
↓
IP 重组无法完成
↓
原始上层载荷不能完整交付
即使分片 1 和 3 均已到达,它们也不能代替缺失分片 2 的实际载荷字节。对于 TCP,可能最终触发相应字节的重新发送,但那是 TCP 的可靠传输层处理,不意味着网络层有"只重发第 2 个 IPv4 分片"的可靠保障。
3. 还存在接收端资源与异常处理负担
IPv4 重组需要保存未完成报文的状态、片段、位置和时间等信息,资源消耗随着待重组数据报数量和复杂程度增加。重叠分片、不完整分片及重组超时也会引入额外的正确性、安全和性能要求。
4. 为什么也不能简单认为 TCP 分得越小越好
形成更多更小 TCP 报文段本身通常会增加 TCP/IP 首部和相关处理开销。合适的长度需要综合路径承载条件、首部开销、窗口与拥塞控制、待发送数据量和实际调度,而非将所有报文段一律缩小到最少字节。
所以:减少不必要的 IPv4 分片有实际价值,但不能把"越小的 TCP 报文段效率必然越高"写成协议定律。
十三、把发送、分片、重组与 TCP 接收放进一条真实的逻辑链
假设 Java 客户端 A 与服务器 B 已建立 TCP 连接;A 通过 Socket 交给 TCP 5000 字节数据。以下是概念性的协议处理流程,不是对特定 Java 调用会产生多少网卡实际帧的实验报告。
- 应用程序提交字节 :
Socket写入把相应字节交给操作系统 TCP 发送路径;这不保证一次写入对应一个段或一次物理发送。 - TCP 计算当前发送许可:结合接收窗口、拥塞控制状态和已在途数据等,判断本次能继续新增发送多少字节。
- TCP 决定适当报文段长度:还需要考虑当前 MSS、已知 PMTU、IP/TCP 首部以及发送策略;形成一个或多个各有 TCP 首部的报文段。
- IPv4 封装:普通 TCP/IPv4 形式下,一份 TCP 报文段成为相应 IPv4 数据报的载荷;IP 首部携带目的地址、源地址、Protocol、长度及必要控制字段。
- 发送链路尺寸判断:考虑输出链路 MTU、DF 等条件。能够直接承载则发送;需要且允许时进行 IPv4 分片;不能合法分片又无法直接通过时进入相应失败/错误处理路径。
- 中间 IPv4 路由器:按路由与链路规则转发完整数据报或分片;在满足规则时可对已分片的载荷部分继续分片。普通路由器不要求每跳先重组。
- 目的主机 IP 层:判定本机接收;对合法分片关联身份、偏移范围、末片与完整性,满足条件时重组完整 IPv4 载荷。
- 交付 TCP :根据
Protocol=6等信息,将完整 TCP 报文段及所需网络层上下文交给 TCP;TCP 根据连接、序列号与协议状态处理收发。 - 应用程序读取 :从相关 Socket 获取按 TCP 语义提供的字节流;不会自动获得 IPv4 分片边界或原始
write()消息边界。
text
应用程序字节 → TCP发送许可判断 → TCP报文段长度决策
→ IPv4封装与 MTU/DF 判断
├─ 可直接承载:发送
├─ 合法且允许分片:分片后发送
└─ 不能分片又过大:失败处理/可能差错反馈
→ 路由器逐跳转发(必要时仍可再分片)
→ 目的主机 IP 层有效重组
→ 完整上层载荷交 TCP
→ TCP识别连接、管理字节顺序与可靠交付
→ 应用程序从 Socket 读取
这条逻辑链中,每个层次都必须明确自己的处理对象:TCP 处理字节流及 TCP 报文段;IPv4 处理 IP 数据报及其分片;链路层处理本段链路的帧。层间可以为发送限制与接收定位交换必要信息,但并不因此让一层无条件接管另一层的具体协议职责。
十四、课堂计算练习:3200 字节数据报的分片推导
题目:已知 Total Length=3200 字节,IHL=5,Identification=0x5678,DF=0,下一段链路 MTU 为 1500 字节,无 IPv4 Options。按符合 8 字节对齐要求并尽量满载的方式,求每片载荷、Total Length、Fragment Offset 与 MF。
第一步:原始 IPv4 载荷长度
3200 − 5 × 4 = 3200 − 20 = 3180 字节 3200-5\times4=3200-20=3180\ \text{字节} 3200−5×4=3200−20=3180 字节
第二步:非末片可以采用的最大载荷
⌊ 1500 − 20 8 ⌋ × 8 = 1480 字节 \left\lfloor\frac{1500-20}{8}\right\rfloor\times8 =1480\ \text{字节} ⌊81500−20⌋×8=1480 字节
第三步:划分原始载荷
3180 = 1480 + 1480 + 220 3180=1480+1480+220 3180=1480+1480+220
第四步:计算偏移与标志
- 第一片从偏移 0 开始:Offset=0,MF=1。
- 第二片从偏移 1480 开始:Offset=1480/8=185,MF=1。
- 第三片从偏移 2960 开始:Offset=2960/8=370,MF=0。
| 分片 | 首部 | 载荷 | Total Length | Offset | MF | Identification |
|---|---|---|---|---|---|---|
| 1 | 20 B | 1480 B | 1500 B | 0 | 1 | 0x5678 |
| 2 | 20 B | 1480 B | 1500 B | 185 | 1 | 0x5678 |
| 3 | 20 B | 220 B | 240 B | 370 | 0 | 0x5678 |
验证:1480+1480+220=3180;所有片的 Total Length 都不超过 1500;对应原始载荷偏移 0~1479、1480~2959、2960~3179,连续而不重叠。
十五、常见混淆:哪些看似相近的结论实际不能互相替代
| 容易混淆的说法 | 准确的边界 |
|---|---|
| "TCP 当前能发送 5000 字节,所以会形成一个 5000 字节报文段。" | 5000 是当前可新增发送总额的简化条件;1460 等是单段数据上限。两个条件要同时满足。 |
| "能发 5000 字节就是每秒发 5000 字节。" | 窗口/在途字节约束不是固定传输速率;发送进度还与确认、RTT 和状态相关。 |
| "一条 TCP 连接接收端声明 MSS=1460,发送端就总会使用 1460。" | 还要考虑已知 PMTU、首部长度、流量和拥塞控制、待发数据及具体调度。 |
| "IPv4 Total Length 小于 65535,所以任何链路都能直接发送。" | 协议长度合法 ≠ 满足当前链路 MTU 或整条路径 PMTU。 |
| "如果已经知道真实 PMTU,还可能遇到同一路径更小 MTU。" | 在路径和条件均不变、估计准确时不成立;可能出现的是估计过时/不准确或路径变化。 |
| "IPv4 分片将一个 TCP 报文段切成多个新 TCP 报文段。" | 它划分的是 IPv4 原始载荷;后续片可能没有 TCP 首部,不产生新的独立 TCP 报文段。 |
| "IPv4 首部也跟着被切成更小的首部。" | 每个片拥有相应完整 IPv4 首部;部分字段变化,Options 还可能使各片 IHL 不同。 |
| "Offset 记录的是离最初 IPv4 首部有多远。" | Offset 记录相对原始 IPv4 载荷起点的距离,单位为 8 字节。 |
| "末片 MF=0,就证明整份数据报已到齐。" | 它帮助确定原始载荷尾端;其他必要范围还可能缺失。 |
| "再分片之后的 Offset 要从当前分片开头重新算。" | 仍以最初原始 IPv4 载荷为参照,不能重置偏移基准。 |
| "IPv4 把片排好后,TCP 的序列号就没用了。" | IP 恢复单个原始数据报 ,TCP 处理整个连接某方向的字节流,对象不同。 |
| "丢失某个 IPv4 分片,TCP 就直接重发那个 IPv4 分片。" | TCP 可靠性机制面对的是未确认/缺失的字节,重发时原始报文段边界也可能改变。 |
| "PMTUD 必然收到 ICMP 并且可以精确定位故障。" | 经典 PMTUD 依赖适用 ICMP 反馈,但消息可能过滤或丢失;需区分估计、探测与网络其他故障。 |
