文章目录
一、从应用消息到网络帧:先区分“单位”和“处理操作”
1. 分层协议的数据单位,不意味着每层都把许多上层单位合成一个本层单位
2. “应用层单位就是字节”为什么不准确
3. 为什么 TCP 需要组织报文段
二、IPv4 数据报与首部:完整结构和字段定位
1. 为什么 IPv4 需要单独的首部
2. 每个字段的位宽
3. Version:字段宽度、编码与格式选择
4. IHL:为什么首部最短 20 字节、最长 60 字节
5. Total Length:为什么不能与 IHL、TCP 数据长度或以太网帧长度混淆
6. 用一个完整封装案例推导长度
7. Protocol:先确定“里面是什么协议”,再由该协议找相应接收端点
8. 源 IP 与目的 IP:都是 32 位网络层地址
9. Identification、Flags、Fragment Offset:先理解结构与用途
10. TTL:限制持续转发,不表示 TCP 连接的存活时间
11. Header Checksum:只校验 IPv4 首部,算法不是 CRC
三、DSCP 和 ECN:IPv4 的服务分类与拥塞标记
1. 原来的 TOS 字节现在怎么理解
2. 中间设备标 CE 与发送端收到反馈,不是同一个动作
四、长度约束怎样跨层协作:TCP MSS、路径 MTU 与 IPv4 分片
1. 200000 字节不是一个巨型 IPv4 数据报
2. MSS 是 TCP 数据长度限制,不是 IP 数据报长度
3. IPv4 的 65535 与链路 MTU 的 1500,是不同约束
4. 路径 PMTU 的真实值与发送端已知值必须区分
5. TCP 分段与 IPv4 分片的根本区别
五、IPv4 分片首部和重组:本轮实际解释过的机制边界
1. 分片不会把原始 IPv4 首部切成几小块
2. IPv4 重组为何要用 Identification 与 Fragment Offset
六、两种顺序系统:TCP 的字节序列号与 IPv4 的分片偏移
1. TCP Sequence Number 不是“每个 TCP 包一个连续整数编号”
2. IPv4 Fragment Offset 不是 TCP Sequence Number
3. 分片乱序与 TCP 报文段乱序可以在同一次通信中同时发生
七、进程、JVM、Socket 与 TCP 端口:对象关系不能混在一起
1. 进程、线程、Java Socket 不是同一种对象
2. 新创建的 Socket 不一定立即拥有显式指定的本地端口
3. TCP 四元组为什么需要四项
4. 监听 Socket 与已建立连接 Socket 的职责不同
5. 两个进程能不能监听同一 TCP 端口:不要拿已连接四元组代替监听绑定规则
八、IP 交 TCP 的不只有数据字节:跨层元数据与准确解复用
1. TCP 首部只有端口号,TCP 怎样取得源 IP、目的 IP
2. IP 源地址不是最后一跳的网卡;目的地址也不必一一对应实际入站接口
九、输出接口、源 MAC、目的 MAC:路由与 ARP 分别负责什么
十、完整端到端过程:Java Socket 写入到另一端 read
十一、容易互相混淆的机制:按“谁处理什么”逐项对照
一、从应用消息到网络帧:先区分“单位”和“处理操作”
1. 分层协议的数据单位,不意味着每层都把许多上层单位合成一个本层单位
网络通信中常见的数据单位包括应用协议消息、TCP 报文段(segment)、IPv4 数据报(datagram)以及以太网帧(frame)。
它们分别属于不同协议的语义层次:应用协议定义消息的结构和边界;TCP 为应用提供双向、有序的字节流,并在传输时形成报文段;IPv4 用数据报封装相应载荷以进行网络层传递;以太网再用帧完成当前链路上的发送。
应用程序定义一条消息 ↓ 编码为字节 TCP 管理字节流,组织一个或多个 TCP 报文段 ↓ 典型 TCP/IPv4 封装 IPv4 首部 + 一个完整 TCP 报文段 = 原始 IPv4 数据报 ↓ 以太网封装 以太网首部 + IPv4 数据报 + FCS = 以太网帧(典型情形)各层定义本层数据单元,不代表各层必须执行固定的“多个上层包合成一个本层包”操作。
正常未分片 TCP/IPv4 封装中,一份 IPv4 数据报的载荷通常是一份 TCP 报文段。IPv4 分片则可能让一个原始数据报变为多个分片;这种操作并不等于把 TCP 报文段重新划分成几份独立的 TCP 报文段。
隧道、网卡卸载等特殊机制可以有不同的内部表示,不能因此修改普通线上协议封装模型。
2. “应用层单位就是字节”为什么不准确
应用程序确实会将待传输的内容编码为字节,但应用层协议往往有自己的消息单位,例如一份登录请求、一次查询及其字段结构。
一份应用消息可以被编码成很多字节;多份消息也可依次写入同一 TCP 连接。
TCP 所提供的服务不保存应用消息边界。
程序对同一 Socket 连续执行两次write(),接收端可能通过一次read()读到两次写入的字节,也可能通过几次read()才读取完一次写入的数据。
反过来,一个 TCP 报文段也未必恰好等于一条完整的应用层消息。
应用协议如需识别边界,应自行约定长度、分隔符或其他消息格式。
3. 为什么 TCP 需要组织报文段
TCP 管理的是连续的字节流,但网络不能直接发送“一条无限长的逻辑字节流”。
TCP 需要在发送时选择适当的字节范围,附加源/目的端口、序列号等 TCP 首部信息,形成报文段,再由 IP 承载。
发送端还必须考虑可发送数据量、接收端窗口、拥塞状态、MSS、适用的路径长度限制与实际待发送字节数。
这些机制服务于不同判断:
流量控制主要限制接收端允许发送的未完成接收数据量;
拥塞控制限制网络当前适合保持的在途数据量;
MSS 与路径 MTU 等影响单个 TCP 报文段的数据长度。
能继续发送一万字节,不等于应形成一个携带一万字节数据的 TCP 报文段;单段最多可以承载 1460 字节,也不意味着每个报文段都必须装满。
二、IPv4 数据报与首部:完整结构和字段定位
1. 为什么 IPv4 需要单独的首部
TCP 首部中的端口、序列号等字段不承担整个网络层的寻址和转发职责。
IPv4 为了让发送主机、路由器和接收主机依据共同格式处理数据报,需要自己的首部:指明格式版本、首部/总长度、服务及拥塞信息、分片相关状态、TTL、上层协议类型、首部校验和、源/目的 IPv4 地址,并可携带选项。
因此,承载 TCP 的普通 IPv4 数据报是:
┌─────────────────────────────────────────────────────────┐ │ IPv4 首部 │ ├─────────────────────────────────────────────────────────┤ │ IPv4 载荷:TCP 首部 + TCP 数据 │ └─────────────────────────────────────────────────────────┘从 IPv4 视角,整个 TCP 报文段是其载荷;从 TCP 视角,同一段字节又可分为 TCP 首部和 TCP 数据。
不要把 IPv4 载荷直接等同于应用层净数据。
2. 每个字段的位宽
一个字节等于 8 bit。4 位无符号字段可表示 0~15;8 位可表示 0~255;16 位可表示 0~65535。
但字段可编码多少个数字与协议允许哪些值是两件事,例如 IHL 小于 5 在标准 IPv4 首部中是不合法的。
IPv4 首部按每行 32 bit 的格式展示:
0 31 ┌───────┬───────┬──────────────┬──────────────────────┐ │Version│ IHL │ DSCP + ECN │ Total Length │ │ 4 bit │ 4 bit │ 8 bit │ 16 bit │ ├───────┴───────┴──────┬───────┴───┬──────────────────┤ │ Identification 16 bit│Flags 3 bit│Fragment Offset 13│ ├──────────────────────┼───────────┴──────────────────┤ │ TTL 8 bit│Protocol 8│ Header Checksum 16 bit │ ├──────────┴───────────┴──────────────────────────────┤ │ Source IPv4 Address 32 bit │ ├──────────────────────────────────────────────────────┤ │ Destination IPv4 Address 32 bit │ ├──────────────────────────────────────────────────────┤ │ Options + Padding(如果存在,长度可变) │ └──────────────────────────────────────────────────────┘ 随后才是 IPv4 载荷。上述前五行共5 × 32 bit = 160 bit = 20 字节,构成固定首部。
真实首部在其后可以包含可选项及必要填充,所以首部长度是可变的。
| 字段 | 宽度 | 本阶段已讲解的作用 |
|---|---|---|
| Version | 4 bit | 声明 IPv4 首部格式版本,IPv4 为二进制0100 |
| IHL | 4 bit | 以 4 字节为单位记录 IPv4 首部长度 |
| DSCP + ECN | 8 bit | 差分服务分类和显式拥塞通知 |
| Total Length | 16 bit | 整个 IPv4 数据报的字节数,含首部和载荷 |
| Identification | 16 bit | 用于分片身份识别的相关信息 |
| Flags | 3 bit | 保留位、DF 与 MF 等分片控制 |
| Fragment Offset | 13 bit | 分片载荷在原始 IPv4 载荷中的偏移,单位为 8 字节 |
| TTL | 8 bit | 限制数据报被持续转发 |
| Protocol | 8 bit | 指示载荷协议,例如 TCP、UDP、ICMP |
| Header Checksum | 16 bit | 对 IPv4首部进行差错检测 |
| Source Address | 32 bit | 当前 IPv4 数据报中的源地址 |
| Destination Address | 32 bit | 当前 IPv4 数据报中的目的地址 |
| Options、Padding | 可变 | 可选控制信息和对齐填充 |
3. Version:字段宽度、编码与格式选择
IPv4 的 Version 占 4 bit,值为二进制0100,即十进制 4。
字段宽度是 4 位,不表示“版本号必须等于字段位数”;这四位记录一个代表 IP 格式的编码。
IPv6 的版本值为 6,但将 IPv4 首部开头四位直接改成 6 并不能得到合法的 IPv6 数据报,因为后续首部布局及处理规则也不同。IPv6 的完整结构属于后续单独阶段。
4. IHL:为什么首部最短 20 字节、最长 60 字节
IHL 是 Internet Header Length,占 4 bit,其单位不是 1 字节,而是32 bit = 4 字节。
IPv4 首部长度(字节)=IHL×4
固定字段共 20 字节,所以最小有效 IHL 为 5;4 位最大无符号值是 15,所以最大首部长度是 60 字节。
合法长度是 20、24、28、……、60 字节。
没有 Options 时,通常 IHL=5,首部 20 字节。
假设 IHL=7:首部为7×4=28字节,其中在 20 字节固定区之外可有 8 字节选项与填充空间。
以数据报第一个字节的偏移量为 0,则 IPv4 载荷起始偏移量是 28(也就是第 29 个字节)。
接收设备若错按 20 字节解析,会把部分首部选项误当成载荷。
Options 与 Padding:可选项携带特定额外控制信息,例如 IPv4 曾定义记录路由、时间戳等选项;如果选项区尚未达到 4 字节对齐要求,需要填充。
假设固定首部 20 字节、选项 6 字节、填充 2 字节,总首部 28 字节,IHL=7。
最多 40 字节是 Options 与填充共同使用的剩余首部空间,并不是首部可以在 60 字节之外再追加填充。
5. Total Length:为什么不能与 IHL、TCP 数据长度或以太网帧长度混淆
Total Length 占 16 bit,以字节为单位,表示本次 IPv4 数据报首部与载荷的总和。
Total Length=IHL×4+IPv4 载荷字节数
因此,也可以由两个字段求出 IPv4 载荷:
IPv4 载荷字节数=Total Length−4×IHL
例如 IHL=6、Total Length=1200:首部 24 字节,载荷1200−24=1176字节;以 0 为起始偏移量,首部占 0~23,载荷占 24~1199。
Total Length 最大可编码2^16−1=65535字节,这是普通 IPv4 数据报的整体上限;若首部 20 字节,理论上的最大 IPv4 载荷为 65515 字节,不是 65535 字节。
它同样不会包含外层以太网首部、FCS、物理层前导码等开销。
例如普通无 VLAN 的 Ethernet II 以太网帧携带恰好 1500 字节的 IPv4 数据报:帧首部 14 字节、IPv4 数据报 1500 字节、FCS 4 字节,则从目的 MAC 字段到 FCS 为14+1500+4=1518字节;
IPv4 Total Length 仍为 1500,而非 1518。与物理层实际传送开销不能混算。
6. 用一个完整封装案例推导长度
假设源地址192.0.2.10、目的地址198.51.100.20,IPv4 的 Version=4、IHL=5、Protocol=6、Total Length=1500,TCP 不携带额外选项且 TCP 首部为 20 字节。
IPv4 数据报总长:1500 字节 ├── IPv4 首部:20 字节 ← IHL=5 └── IPv4 载荷:1480 字节 ← 1500−20 ├── TCP 首部:20 字节 └── TCP 数据:1460 字节 ← 1480−20这不代表 Java 程序一次write()必须恰好写 1460 字节,也不代表网上一定已经发送了 1500 字节应用业务数据。
这里 1460 仅是在给定长度与首部条件下计算出来的 TCP 数据量。
7. Protocol:先确定“里面是什么协议”,再由该协议找相应接收端点
IPv4 Protocol 占 8 bit,常见值是1=ICMP、6=TCP、17=UDP。
收到某份 IPv4 数据报后,IP 层先根据本地接收等规则决定能否交付,再根据 Protocol 把相应载荷交给合适的上层协议处理模块。
区分三种“告诉下一层是什么”的信息:
以太网 EtherType = 0x0800 → 以太网载荷采用 IPv4 IPv4 Protocol = 6 → IPv4 载荷是 TCP TCP 目的端口 = 8080 → 供 TCP 端点/连接处理使用EtherType、Protocol、TCP 端口存在于不同层的首部中,作用不同。
IP 层不依据 TCP 目的端口直接挑选 Java 进程。
8. 源 IP 与目的 IP:都是 32 位网络层地址
IPv4 地址通常写成四个以点分隔的十进制数,例如192.0.2.10,实际上是四组 8 bit 的组合:11000000 00000000 00000010 00001010。
它在 IPv4 首部中存放的是 32 位地址值,不是字符串字符本身。
普通不改写地址的 IP 转发中,源、目的 IPv4 地址通常保持其端到端网络层意义:目的 IP 参与转发和本地接收判定,源 IP 表示报文头中的源地址。
但 NAT 等机制可能修改相关地址;源/目的 IP 不能直接等同于应用进程、TCP 端口或一台物理设备永久不变的身份。
地址的前缀、掩码、CIDR 与完整路由匹配仍属于后续阶段。
9. Identification、Flags、Fragment Offset:先理解结构与用途
分片相关字段包括 Identification(16 bit)、Flags(3 bit:保留位、DF、MF)和 Fragment Offset(13 bit)。
Identification 是 IPv4 分片重组识别的相关信息,不是全互联网永久唯一编号;
DF 与是否允许相关分片有关;
MF 表示后面是否还有分片,通常最后一个分片 MF=0;
Fragment Offset 用 8 字节单位表示分片载荷在原始 IPv4 载荷中的位置。
分片载荷偏移(字节)=8×Fragment Offset
本阶段讲到了字段位置、用途、对象与简化分片示意;没有完成ID/DF/MF 的全部特殊规则、片长/对齐的逐数字演算、重组异常及丢片超时的正式系统教学。
10. TTL:限制持续转发,不表示 TCP 连接的存活时间
TTL(Time To Live)占 8 bit,通常在 IPv4 路由器转发数据报时被递减,用于限制数据报无限持续转发,尤其避免路由环路使其一直在网络中循环。
实际 IPv4 语境下它经常可以作为转发跳数限制来理解。
TTL 不是毫秒数、不是应用数据保留时间,也不是 TCP 连接寿命;各主机初始 TTL 不必相同。
TTL 耗尽、ICMP Time Exceeded、诊断工具的具体机制留待后续第四阶段完整推导。
11. Header Checksum:只校验 IPv4 首部,算法不是 CRC
Header Checksum 占 16 bit,基于 IPv4 首部按 16 位数据单元计算的反码求和校验机制,用于检测IPv4 首部的某些传输错误,不覆盖整个 TCP 数据部分。
由于转发中 TTL 等首部字段可能改变,IPv4 首部校验和也要相应更新,可重算或按等效方式增量调整。
校验通过不代表应用数据必然完整、可靠到达,也不意味着全部错误都能检测出来。
完整反码求和的逐步二进制演算留在第四阶段。
三、DSCP 和 ECN:IPv4 的服务分类与拥塞标记
1. 原来的 TOS 字节现在怎么理解
IPv4 首部中一个 8 位字段历史上称 Type of Service(TOS)。
现代解释常将该字节区分为6 位 DSCP + 2 位 ECN,不应把历史旧式优先权和服务位的划分原样当成今天一切设备通行的处理模型。
一个 8 bit 字节 ┌────────────────────────┬────────────┐ │ DSCP:6 bit │ ECN:2 bit │ └────────────────────────┴────────────┘DSCP(Differentiated Services Code Point)为网络设备选择相应差分服务处理提供分类信息,例如在支持 QoS 的环境中影响队列调度、丢弃策略等。
但 DSCP 并不自动保证最低延迟、某个确定带宽或“永不丢包”;网络设备要有相应配置和策略,且可重标记 DSCP。
目的 IPv4 地址和路由信息负责相应的转发判断;DSCP 与逐跳服务处理相联系,两者不可简单替代。
ECN(Explicit Congestion Notification)用于显式拥塞通知,2 位常见值是:
| ECN 位 | 含义 |
|---|---|
00 | Not-ECT:未声明相应 ECN 能力 |
10 | ECT(0):ECN-capable 标记之一 |
01 | ECT(1):ECN-capable 另一标记 |
11 | CE(Congestion Experienced):经历拥塞标记 |
2. 中间设备标 CE 与发送端收到反馈,不是同一个动作
假设 A 向 B 发送已经声明 ECN 能力的 IPv4 数据报,经过中间路由器 R。
R 的队列管理策略在符合相应条件时,可以将这份数据报的 ECN 字段改为 CE,然后让它继续沿原方向向 B 前进。
发送方 A 路由器 R 接收方 B │ │ │ ├──── IP 数据报 ECT ────►│ │ │ ├──── 原数据报标 CE ─────────────►│ │ │ │ │◄────────────── TCP 的相应拥塞反馈(反方向) ─────────────┤ │ │ 发送端 TCP 依协议规则对拥塞信号作出响应关键不是“R 自动另外创建一个发送给 A 的拥塞告警包”。
典型经典 TCP ECN 中,B 接收到带 CE 的数据报后,IP/TCP 协议栈配合,通过 TCP 相应确认/反馈机制向发送方反映拥塞(例如 ECE 信号),发送端再按协议规则调整拥塞控制行为。
发送端不需要直接知道是哪台具体路由器打了 CE;单凭 CE 也通常不能定位拥塞设备。
网络设备也不能不论条件都把 Not-ECT 数据报改为 CE;即使支持 ECN,数据报也仍可能被丢弃。
CE 标记不是“已经丢掉了包还在这份包里标记”,也不意味着可靠送达或免除 TCP 对丢包的处理。
完整 TCP ECN 协商、ECE/CWR 状态交互及主动队列管理算法超出本轮已经正式讲清的范围。
四、长度约束怎样跨层协作:TCP MSS、路径 MTU 与 IPv4 分片
1. 200000 字节不是一个巨型 IPv4 数据报
应用程序可以向一个 TCP 连接提供总量 200000 字节的待发送数据。
这是应用数据总量,不是单个 TCP 报文段、单个 IPv4 数据报的长度。
TCP 根据发送状态把字节流组织成若干 TCP 报文段;普通 IPv4 将各自报文段承载于相应数据报中。
假设为演示而设定:以太网路径有效 MTU 为 1500 字节、IPv4 首部 20 字节、TCP 首部 20 字节、没有其他额外首部开销、发送端满足其余发送条件,并尽量按 1460 字节数据形成满载段。于是:
1500−20−20=1460 字节 TCP 数据
200000=136×1460+1440
可以形成 136 个载荷 1460 字节的报文段,再加一个载荷 1440 字节的报文段,共 137 个。
但这只是指定条件下的组织示例。
TCP 不被要求每次都装满、也未必一次性把全部 137 个发送出去;网络上的段数量还可能因实现、选项、发送条件等不同而变化。
一次write(200000 bytes)也不对应一次 TCP 段或一次 IP 数据报。
TCP 允许连接累计传输远超过 IPv4 单报最大长度的字节流,不需要首先造一个超出 65535 字节的普通 IPv4 数据报再由 IP 去分片。
2. MSS 是 TCP 数据长度限制,不是 IP 数据报长度
MSS(Maximum Segment Size)是 TCP 报文段中TCP 数据部分的最大长度相关参数,不含 TCP 首部;双方可以在连接建立时分别使用 MSS 选项声明自身愿意接收的最大 TCP 数据段大小。
A 向 B 发数据,需要考虑 B 的接收 MSS 声明;B 向 A 发数据,则考虑 A 的声明,因此两个方向可以不同。
发送端实际组段还要考虑路径 MTU、IPv4/TCP 首部及其他开销、当前发送窗口、拥塞窗口和待发送数据。
假设 B 声明接收 MSS=1460,但 A 当前面临 IPv4 PMTU=1280 且两个首部均为 20 字节,那么不经分片的典型 TCP 数据长度上限为1280−20−20=1240字节,而不是硬按 1460 字节发出。
即使上限为 1240,一次实际发送仍可能只有几百字节。
3. IPv4 的 65535 与链路 MTU 的 1500,是不同约束
IPv4 Total Length 的 65535 字节是该数据报格式能表示的整体长度上限。
某条链路的 MTU 则限制通过该链路时一个网络层数据报能以多大尺寸直接承载。
一个 3000 字节的 IPv4 数据报可以符合 IPv4 自己的格式上限,却不能作为一个完整数据报直接通过 MTU 1500 的普通输出链路;在条件允许时可分片,不允许分片时需要采取其他处理,例如丢弃并在适用时发出 ICMP 错误反馈。
所以,“IP 层能否在内部容纳这份数据报”和“下一段链路可否不分片直接发出”不是一个问题。
TCP 尽量形成适合路径的报文段,既是利用下层网络能力,也能减少 IPv4 分片需求;TCP 并没有因此代替 IPv4 进行分片。
4. 路径 PMTU 的真实值与发送端已知值必须区分
路径 MTU(PMTU)指特定 IP 路径可不经分片通过的最大 IP 数据报尺寸,其真实约束受沿途链路等条件限制。
假设路径三段 MTU 分别为 1500、1400、1280,且无其他特殊封装,则该路径的实际 PMTU 为最小值 1280。
如果发送端准确知道当前未变化的这条路径PMTU 为 1280,并遵守它发包,则不会又在该路径上遇到比 1280 更小的下一跳链路;否则“已经准确知道”与“路径上还有更小者”相矛盾。
现实中可能出现发送端只知道过时或估计的 PMTU:路由改变、某段新增封装开销、探测信息不全或未及时更新,都可能让发送端认为可用 1500,实际路径却只能不分片承载 1280。
“可能遇到更小 MTU”必须带上“当前已知值与真实路径条件不一致或路径发生变化”等前提。
完整 PMTUD 如何发现并修正数值,是第三阶段的正式教学任务。
5. TCP 分段与 IPv4 分片的根本区别
- TCP 分段:以某个方向的 TCP 字节流为组织对象,创建具有各自 TCP 首部的 TCP 报文段;报文段的序列号标识自己所承载字节在 TCP 字节流中的起始位置。TCP 不需要按应用消息边界分段。
- IPv4 分片:以一份已经存在的 IPv4 数据报及其 IPv4 载荷为处理对象,按允许的分片规则形成多个带各自 IPv4 首部的 IP 分片。它不按 TCP 消息或 TCP 序列号重新创建多条 TCP 报文段;原来的载荷可能从 TCP 首部开始,后面的分片只含原 TCP 数据的后续字节。
因而 IPv4 分片并不是“先在 IP 层装入多份独立的 TCP 报文段,装不下了再拆”。
在典型 TCP/IPv4 模型中,IPv4 分片的来源是一个原始 IPv4 数据报,而其载荷可恰好是一份完整 TCP 报文段。
五、IPv4 分片首部和重组:本轮实际解释过的机制边界
1. 分片不会把原始 IPv4 首部切成几小块
假设:
原始 IPv4 数据报 ┌────────────────────────┐ │ 原始 IPv4 首部 │ ├────────────────────────┤ │ TCP 首部 │ │ TCP 数据全部字节 │ └────────────────────────┘在符合条件时发生 IPv4 分片,可以得到:
IPv4 分片 1 ┌────────────────────────┐ │ 分片 1 自己的 IPv4 首部│ ├────────────────────────┤ │ TCP 首部 + 部分 TCP 数据│ └────────────────────────┘ IPv4 分片 2 ┌────────────────────────┐ │ 分片 2 自己的 IPv4 首部│ ├────────────────────────┤ │ 另一部分 TCP 数据 │ └────────────────────────┘ IPv4 分片 3 ┌────────────────────────┐ │ 分片 3 自己的 IPv4 首部│ ├────────────────────────┤ │ 余下的 TCP 数据 │ └────────────────────────┘每个 IP 分片本身都需要足够完整的 IPv4 首部供相应网络层处理,不是原始 IPv4 首部被切碎成几份残缺的“小首部”。
原数据报无选项且首部为 20 字节时,各分片自己的 IPv4 首部也可以是 20 字节;变小的是各分片的数据报总长度。
不同分片的 Total Length、Offset、MF、首部校验和等字段可能不同,某些 Options 的复制规则也是特别条件,不能认定各分片首部逐字节完全一样。
2. IPv4 重组为何要用 Identification 与 Fragment Offset
分片可能乱序到达。
例如同一原始 IPv4 数据报的载荷被分为 A、B、C 三部分,网络实际先收到 B、再收到 A、最后 C。
接收端不能按分片到达顺序拼接,否则载荷字节位置错误。
IPv4 的相关标识信息帮助分辨属于同一原始数据报的分片,Fragment Offset 表明当前分片载荷对应原始载荷的起始位置,MF 等信息参与完整性判断。
重组的逻辑目标是恢复原始 IPv4 载荷的正确字节布局,并构造相应的完整网络层数据报表示。
不会把IPv4 首部1 + 载荷A + IPv4首部2 + 载荷B + ……简单首尾拼接;也不要求完整复原发送时首部全部原始比特,例如 TTL 在网络中可以合法变化。对重组结果还需处理相应总长度、分片状态和首部校验等信息。
通常在目的主机的 IP 层有效完成重组后,才能按照 Protocol 将完整载荷交给 TCP。
如果某必要分片始终缺失,就可能无法恢复该原始 IP 数据报;TCP 后续可能通过自己的确认/重传机制应对数据缺失,却不是直接替代 IP 重组缺失的分片。
此处只是分片对象、各片首部、重组对象及顺序的概念和实例。分片字节数/偏移 8 字节对齐逐步演算、Identification/DF/MF 全部判定、特殊 Options、分片丢失和超时及 PMTUD 的完整条件仍归第三阶段正式教学。
六、两种顺序系统:TCP 的字节序列号与 IPv4 的分片偏移
1. TCP Sequence Number 不是“每个 TCP 包一个连续整数编号”
TCP 为一条 TCP 连接中某一个传送方向的字节流管理序列号空间。
对于携带普通数据的 TCP 报文段,其首部 Sequence Number 表示这段数据第一个字节的序列号,而不是简单地给每个报文段编 1、2、3。
假设忽略 SYN 等控制位占用序列号空间的细节,一个方向准备发送 3000 字节,起始数据字节序号为 1000,且为了演示分成三段:
| TCP 报文段 | TCP 首部 SEQ | 数据字节序号范围 |
|---|---|---|
| A | 1000 | 1000~1999 |
| B | 2000 | 2000~2999 |
| C | 3000 | 3000~3999 |
假设 C、A、B 的到达顺序不同于发送顺序,TCP 可依据序列号、当前接收状态及缓冲信息识别乱序数据,并在满足接收条件时向应用程序提供按序、无重复的字节流。
它管理的是字节范围,不把应用一次write()、一个 TCP 段或者不同 TCP 连接统统纳入同一个全局编号。
2. IPv4 Fragment Offset 不是 TCP Sequence Number
IPv4 分片偏移描述的是一份原始 IPv4 数据报内部的 IPv4 载荷位置,而 TCP SEQ 描述的是一条 TCP 连接一个发送方向的字节流的位置。
一个 TCP 报文段可能被某份原始 IPv4 数据报承载;如果这份 IPv4 数据报分片,后续非首片可能完全没有 TCP 首部,更没有独立完整的 TCP 报文段起始序列号。
IPv4 也可以承载 UDP/ICMP 等没有 TCP 字节流 SEQ 机制的协议,因此 IP 分片重组不能依赖 TCP。
3. 分片乱序与 TCP 报文段乱序可以在同一次通信中同时发生
仍假设 TCP 报文段 A、B、C 的 SEQ 分别是 1000、2000、3000;B 所在的 IPv4 数据报在路径中分为 B1、B2。到达顺序可能是:
接收顺序 1:完整 IPv4 数据报 C 接收顺序 2:IPv4 分片 B2 接收顺序 3:完整 IPv4 数据报 A 接收顺序 4:IPv4 分片 B1- C 未分片,IP 可把完整 TCP 段 C 交给 TCP。TCP 看出数据在 3000~3999,前面尚有缺口,可以保留乱序数据等待。
- B2 是分片,尚没有 B1,IP 层按分片机制等待,不应将 B2 当成独立 TCP 报文段直接交 TCP。
- A 到来,IP 交给 TCP,TCP 得到 1000~1999 的数据。
- B1 到来,在假定分片合法且全到齐时,IP 层按照原始 IPv4 载荷偏移恢复 B 的完整载荷,交给 TCP;TCP 识别到 B 的 SEQ=2000。缺失的 2000~2999 字节区间得到补齐后,TCP 能按其规则使 1000~3999 的相应连续字节范围依次可供应用读取。
IPv4 的任务:B2 + B1 → 按 IP 片偏移恢复一个完整 TCP 报文段 B TCP 的任务: C、A、B → 按 TCP 字节序号恢复有序字节流 A、B、C所以,IPv4 首先在单个原始 IP 数据报范围内恢复载荷,TCP 随后在连接字节流范围内处理不同报文段的乱序、重复、缺失与按序交付。
两种位置机制的对象和层级不同,不能只保留其中一种。
若 B2 永远丢失,IP 可能无法重组 B;TCP 必须通过自己后续的可靠性处理机制应对缺失的 TCP 字节范围。
七、进程、JVM、Socket 与 TCP 端口:对象关系不能混在一起
1. 进程、线程、Java Socket 不是同一种对象
操作系统管理进程;通常一次java Main运行会启动一个 JVM 进程,其中有多个 Java 线程的可能。
一个 JVM 进程可以创建并使用多个 Java Socket 对象,各 Socket 可关联不同的操作系统套接字资源或 TCP 通信端点。
Socket 不是线程,它不会因为数量与线程相同才可以通信;多个线程可能访问一个 Socket,一个线程也能处理多个 Socket。
JavaSocket是供 Java 程序使用网络操作的对象;内核中的套接字及 TCP 连接状态是 OS 管理的实现资源。
TCP 端口则是 TCP 首部中的 16 位数值字段,不是额外存在于 Socket 与 TCP 之间、字节流必须穿过的一台“端口设备”。
2. 新创建的 Socket 不一定立即拥有显式指定的本地端口
客户端可以使用类似:
import java.net.Socket; try (Socket socket = new Socket("198.51.100.20", 8080)) { // 使用 socket.getInputStream()/getOutputStream() }这里8080表示远端目的 TCP 端口,并不要求客户端自己也使用本地 8080。
连接时系统通常可以为客户端自动选择适当的本地 IP 和临时端口,例如本地192.0.2.10:50000。
刚创建、还没有绑定或连接的 Socket,未必已经有确定的本地端口;因此“一创建 Socket 就手动绑定一个唯一端口”不是 TCP Socket 的普遍规则。
端口号与进程不是固定一一对应。
一个进程可以使用多个 Socket、多个本地端口;许多已经建立的 TCP 连接也可以共享一个服务器本地端口。
3. TCP 四元组为什么需要四项
典型一条已建立的 TCP/IPv4 连接可由两个端点信息标识:
(源 IP,源 TCP 端口,目的 IP,目的 TCP 端口)从正在观察 Socket 的主机而言,也可表述为(本地 IP,本地端口,远端 IP,远端端口)。
注意,“源/目的”随当前报文段发送方向改变;“本地/远端”则随观察者所在主机改变。
例如客户端192.0.2.10:50000与服务器198.51.100.20:8080:客户端发送请求时,源端点是客户端,目的端点是服务器;服务器发响应时,源端点变成服务器,目的端点变成客户端。
但在服务器 B 的 Socket 视角,本地始终指 B,远端始终指 A。
仅凭两个 TCP 端口无法区分所有连接。
设同一客户端192.0.2.10:50000同时连接服务器本地198.51.100.20:8080与198.51.100.21:8080,TCP 源/目的端口相同,但服务器目的 IP 不同,仍是不同 TCP 端点组合。
这也是 TCP 必须从 IP 处理路径获得相关地址信息的原因之一。
4. 监听 Socket 与已建立连接 Socket 的职责不同
典型 Java 服务器:
import java.net.ServerSocket; import java.net.Socket; try (ServerSocket server = new ServerSocket(8080)) { Socket client1 = server.accept(); Socket client2 = server.accept(); // 两个 client Socket 可在适当时机用于各自已建立连接的数据收发 }这是说明 API 角色的示意代码,accept()可以阻塞等待新的连接;此代码不是已执行的实验,也不是生产级并发服务器实现。
ServerSocket对应监听端点,本地可为198.51.100.20:8080。
TCP 三次握手的核心状态处理由操作系统协议栈进行;Javaaccept()通常取得一个已完成握手、可被应用接受的连接 Socket。
它不是返回一个尚未建立的 TCP 连接,让 Java 再手动从头执行三次握手。
监听 Socket 自身处于监听状态,不是与某个确定客户端已经建立的数据连接;已连接 Socket 则用于对应连接的双向数据收发。
例如:
监听 Socket:本地 198.51.100.20:8080,状态 LISTEN 已连接 Socket 1: 本地 198.51.100.20:8080 远端 192.0.2.10:50000 已连接 Socket 2: 本地 198.51.100.20:8080 远端 192.0.2.11:50001服务器可以同时服务两条不同 TCP 连接,却不需要给两个客户端分别增设 8081、8082 监听端口。
TCP 协议栈结合完整端点信息和连接状态,将收到的报文段交给相应连接及 Socket 接收路径。
5. 两个进程能不能监听同一 TCP 端口:不要拿已连接四元组代替监听绑定规则
已连接 Socket 能否共用某个服务器本地端口,与两个监听 Socket 能否独立绑定同一本地 IP+端口是不同判断:前者可以通过不同远端端点形成不同 TCP 连接;后者由本地地址、端口、监听状态、通配地址绑定、OS 及相关 Socket 选项决定。
例如同一主机的两个 Java 进程都以通常独占方式监听相同具体本地 IP 和 TCP 8080,后尝试者通常遇到地址已占用。
两个监听端点若采用不同具体本地地址,或操作系统允许并正确配置了特定复用机制(如适用的SO_REUSEPORT),可能产生不同结果;通配地址0.0.0.0也可能影响冲突判断。
TCP 与 UDP 有不同传输协议的端口命名空间。
不能只说“四元组不同就允许任何两个进程监听同一端口”,因为监听端点尚不与固定远端四元组相联系。
八、IP 交 TCP 的不只有数据字节:跨层元数据与准确解复用
1. TCP 首部只有端口号,TCP 怎样取得源 IP、目的 IP
IPv4 首部包含源/目的 IP、Protocol 等;TCP 首部包含源/目的 TCP 端口、序列号等。
接收端 IP 层确定本地接收并发现Protocol=6后,通常将 TCP 载荷交给内核 TCP 处理路径,并以协议栈内元数据、缓冲区上下文等方式使 TCP 获得所需 IP 地址信息。
IPv4 接收路径已知: 源 IP = 192.0.2.10 目的 IP = 198.51.100.20 Protocol = 6 交给 TCP 的处理上下文: 完整 TCP 报文段字节 + 所需 IPv4 相关元数据 TCP 进一步解析: 源端口 = 50000 目的端口 = 8080 结合完整端点信息 + TCP 连接状态 → 定位对应的接收连接不要求复制一整份 IPv4 首部到 TCP 报文段前面,也不能因 IP 层已决定本地接收,就认为 TCP 无须知道两个 IP 地址。
不同本地 IP/相同 TCP 端口可以标识不同的连接。
此外,TCP/IPv4 校验和计算涉及包含源 IP、目的 IP、协议号、TCP 长度等信息的伪首部;伪首部是校验计算的逻辑结构,并不是随 TCP 报文段多发送的一份真实 TCP 首部。
相关事实说明 TCP 可以依赖 IP 层提供的必要信息,但并不负责 IP 层的普通路由转发。
2. IP 源地址不是最后一跳的网卡;目的地址也不必一一对应实际入站接口
假设客户端 A 经 R1、R2 把数据报送至服务器 B,无 NAT 情况下报文头源 IP 可以始终是 A 的192.0.2.10,但最后一跳实际把帧交给 B 的是 R2 的某个接口。
IPv4 源 IP 不是最后一跳路由器的 IP/MAC,不单凭它确认数据报从 B 的哪张物理网卡进入,也不构成不可伪造的真实设备身份证明。
IPv4 目的 IP 参与判定本机是否应当接收及 TCP 本地端点定位,但它与物理入站接口也并非在所有网络配置中一一对应:一台多网卡主机可能有多个 IP/逻辑接口,接收策略因操作系统与设置而异。
实际收包接口由网卡驱动和 OS 接收路径上下文可知;其信息与 IP 源/目的地址属于不同维度。
服务器回复 TCP 数据时,TCP 的本地/远端端点决定相关回复端口与地址,IP 发送路径仍要依据路由策略决定当前输出接口及下一跳,不必因为原请求由某张网卡进入就沿同一张网卡送出。
九、输出接口、源 MAC、目的 MAC:路由与 ARP 分别负责什么
在典型以太网发送中,操作系统需要回答三个问题:从本机哪个接口出去?本帧源 MAC 用哪个?本帧目的 MAC 给哪个邻居?三者关联,但不能全部说成“通过 ARP 查询得到”。
假设主机 A 有:
eth0:IPv4=192.168.1.10,MAC=MAC-A0 eth1:IPv4=192.168.2.10,MAC=MAC-A1A 要向远端 B 发送,路由与适用的转发规则选定:
输出接口:eth0 下一跳 IPv4 地址:192.168.1.1于是普通以太网发送时:
- 输出接口依据路由等规则确定为
eth0,通常不是先由 ARP 决定选 eth0 还是 eth1。 - 源 MAC通常取本地输出接口 eth0 的已配置 MAC,即
MAC-A0,不需要通过 ARP 查询“自己的 IP 对应哪个 MAC”。 - 目的 MAC对应当前链路上要交付的下一跳邻居,可以通过 ARP/邻居缓存从
192.168.1.1获得例如MAC-R1。
因此发送的帧为:
以太网源 MAC = MAC-A0 以太网目的 MAC = MAC-R1 其内部 IPv4 数据报:源 IP 和最终目的 IP 按相应网络层语义表示下一台路由器转发时,一般在其输出链路重新形成相应链路层封装,源 MAC 改为本次路由器输出接口的 MAC,目的 MAC 则是当前下一跳的 MAC。
不能让起始链路的以太网帧首部一路不变地跨越所有路由器。
桥接、虚拟网卡、MAC 重写、非以太网链路等场景有不同实现,应按环境另行判断。
十、完整端到端过程:Java Socket 写入到另一端 read
假设 TCP 连接已建立:
客户端 A:192.0.2.10:50000 服务器 B:198.51.100.20:8080Java 客户端可以执行:
import java.net.Socket; import java.nio.charset.StandardCharsets; import java.io.OutputStream; try (Socket socket = new Socket("198.51.100.20", 8080)) { OutputStream out = socket.getOutputStream(); out.write("Hello".getBytes(StandardCharsets.UTF_8)); }在这个示例中,"Hello"对应五个 UTF-8 字节。write()把字节提交给关联 Socket 的 TCP 发送路径;返回不等于远端 Java 程序已经读取、处理或持久化该数据。
发送端 TCP 按连接状态形成带源端口 50000、目的端口 8080、相应序列号等字段的 TCP 报文段;IPv4 再添加源 IP、目的 IP、Protocol=6 等字段;根据路由和邻居解析决定当前以太网输出接口及 MAC 封装。
接收端:
链路层收到当前链路帧 ↓ IPv4 判断是否本地接收、首部合法与 Protocol=6 ↓ 如果该 IPv4 数据报发生分片,先按 IP 规则完成有效重组 ↓ TCP 获得载荷 + 必要源/目的 IP 元数据 ↓ 解析 TCP 源端口50000、目的端口8080等首部 ↓ 查找对应 TCP 连接,并根据状态、SEQ、确认等机制处理 ↓ 将当前符合交付条件的有序字节提供给相应 Socket ↓ Java InputStream.read() 读取这些字节服务端示意:
import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; try (ServerSocket server = new ServerSocket(8080); Socket client = server.accept()) { InputStream in = client.getInputStream(); byte[] buf = new byte[1024]; int n = in.read(buf); if (n > 0) { // 只处理本次实际读到的 n 个字节;不假设恰好等于客户端一次 write } }以上只是说明 Socket 与 TCP/IP 收发关系的概念代码,未在学习记录中进行实际编译、运行或抓包实验。
TCP 可分多次向应用交付字节;两次write("Hello")、write("World")可能在读取时呈现一次HelloWorld,也可能被分几次读出。
应用如果需要每条独立消息,就必须自己识别其消息边界。
这一链路还说明:端口号是 TCP 首部字段和连接状态的识别信息,没有Java Socket → 端口硬件 → TCP → IP这样的额外数据传输层。
IP 不直接选择 Java 进程;TCP/Socket 连接定位及操作系统资源关联机制参与了交付。
十一、容易互相混淆的机制:按“谁处理什么”逐项对照
| 容易混淆的问题 | 准确关系 |
|---|---|
| 每个协议层都把多个上层包聚合成一个本层包? | 没有普遍固定数量对应;按具体协议定义封装、分段或分片 |
| 应用层消息、应用编码字节、TCP 报文段是同一个单位? | 应用可定义消息;TCP 提供无应用消息边界的字节流;报文段是 TCP 传输结构 |
| TCP 想发多少数据只看拥塞窗口? | 还需接收窗口、MSS、路径限制、首部开销、实际待发送数据等 |
write()一次 200000 字节 → 立即发出固定 137 个满载段? | 137 只是给定 1460 载荷条件下的计算示例,真实行为受发送状态等影响 |
| IPv4 最大 65535,所以链路也必须能承载 65535? | IP 格式上限与每段链路 MTU 是不同限制 |
| 发送端已准确知道当前真实 PMTU,但还能遇到更小的同一路径 MTU? | 在路径与封装条件保持不变时不能这样推断;问题来自已知值偏差或路径变化等 |
| IPv4 分片是把原始 IP 首部切小? | 每片各有完整 IPv4 首部,原始载荷按 IP 分片规则拆分 |
| TCP SEQ 能替代 IPv4 Fragment Offset? | TCP SEQ 标记连接某方向的字节流位置;IP Offset 标记一个原 IP 数据报载荷内的位置 |
| IP 源地址就是最后一跳物理网卡? | 不是,源 IP 为头部逻辑地址,物理收包接口由 OS 接收路径上下文确定 |
| 目的 IP 必然等于实际物理入站接口上配置的 IP? | 不总成立,依主机多接口配置和接收规则而异 |
| IP 层看到 TCP 目的端口后就直接选择 Java 进程? | IP 根据 Protocol 交给 TCP,TCP 综合 IP 元数据/端口/连接状态找到 Socket 接收路径 |
| 同一 TCP 端口只能对应一个 Socket? | 监听 Socket 与多条已连接 Socket 可共享服务器本地端口;两个独立监听的绑定规则另判 |
| 两个已建连接的四元组不同,所以两个进程可以随便绑定相同监听端点? | 已建连接识别与监听端点绑定是两类不同机制 |
| ECN 路由器标 CE 后会自动直接向发送方报告? | 典型经典 TCP ECN 是中间设备正向标记,接收端观察后通过 TCP 向发送端反馈 |
| IPv4 首部校验通过就意味着整个 TCP 数据可靠送达? | 首部校验只检查 IPv4 首部,可靠字节流由其他机制提供,业务完成另行判断 |