Files
cs-note/hhs/NETWORK/01-基础概念/02-数据封装与解封装.md
T
2026-05-27 00:12:00 +08:00

187 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [计算机网络, 数据封装, 网络分层]
create time: 2026-05-17 22:20
---
# 数据封装与解封装
## 概述
数据在发送端逐层加上头部(Header),就像装快递盒子:最里层是商品,外面一层一层套纸箱、贴面单、打托盘。到达接收端后,每一层剥掉自己的那层包装,只把原始内容交给上层处理。这个"套娃+拆箱"的过程就是 **封装(Encapsulation)** 和 **解封装(Decapsulation)**。
> [!QUESTION] 为什么每层都要加头?
> 因为每层需要知道如何管理自己的事务——传输层关心连接状态,网络层关心路由寻址,链路层关心物理介质。这些元信息必须跟在数据前面,让每一层都能正确解读并转发。
## 数据包尺寸变化轨迹
以发送一个 HTTP GET 请求为例,追踪尺寸变化:
| 层级 | 操作 | 数据结构 | 新增首部大小 | 累计大小 |
|------|------|---------|-------------|---------|
| 应用层 | HTTP GET 报文 | `GET / HTTP/1.1\r\nHost: example.com\r\n\r\n` | — | ~40 bytes |
| 传输层 (TCP) | + TCP Header | Segment | 20 bytes | ~60 bytes |
| 网络层 (IP) | + IP Header | Packet | 20 bytes (IPv4) | ~80 bytes |
| 链路层 (Ethernet) | + Eth Header + FCS | Frame | 14 + 4 = 18 bytes | ~98 bytes |
```mermaid
flowchart LR
H["HTTP GET 40B"] --> T["+ TCP Header 20B<br/>Segment 60B"]
T --> I["+ IP Header 20B<br/>Packet 80B"]
I --> E["+ Ethernet HDR 14B + FCS 4B<br/>Frame 98B"]
E --> B["98 个 Bit 在网线上传输"]
style H fill:#DDA0DD
style T fill:#FFD700
style I fill:#87CEEB
style E fill:#98FB98
```
## 发送端:逐层封装
```mermaid
sequenceDiagram
participant App as "HTTP 应用"
participant TCP as "TCP Socket"
participant IP as "网络层 IP"
participant ETH as "以太网驱动"
App->>TCP: "write GET / HTTP/1.1..."
Note over TCP: "分配 seq,ack, 连接已建立"
TCP->>IP: "Segment, daddr=x.x.x.x, sport=1234, dport=80"
Note over IP: "查路由表, 下一跳网关"
IP->>ETH: "Packet, src_ip=192.168.1.100, dst_ip=93.184.216.34"
Note over ETH: "ARP 解析, dst_mac=aabb.ccdd.eeff"
ETH->>ETH: "拼接 srcMAC,dstMAC,EtherType,Payload,FCS"
ETH-->>App: "sendto 返回, 帧已发出"
```
### Ethernet II 帧完整结构
```
Offset Size Field 说明
────── ──── ───────────────── ─────────────────────────────
0 6 Dest MAC Address 目标 MAC 地址
6 6 Source MAC Address 源 MAC 地址
12 2 EtherType 0x0800=IPv4 / 0x86DD=IPv6 / 0x0806=ARP
14 N Payload IP 包(最大 1500 字节)
14+N 4 FCS CRC-32 帧校验序列(接收端自动验证)
```
总长度范围:**64 ~ 1518 bytes**(不含 VLAN Tag;有 VLAN Tag 则上限 1522)。
> [!tip] 为什么有最小 64 字节的限制?
> 以太网使用 CSMA/CD 检测冲突,发送方需要在发完帧之前检测到碰撞信号。64 字节是为了保证在最大网络直径内,信号能往返一次。如果 IP 包太小导致帧不够 64 字节,链路层会自动填充 **Padding** 补齐到最小长度。接收端根据 IP 头部的 "Total Length" 字段识别真实载荷边界,剥离多余的填充。
> [!note] MTU = 1500 是什么意思?
> MTU(Maximum Transmission Unit)是链路层允许的最大 Payload 大小,即 IP 包(头部 + 载荷)的最大体积。超过 1500 字节的 IP 包会被路由器**分片(Fragmentation)**——拆成多个小片段分别传输,接收端再重组。
> 分片的代价:任何一个分片丢失都会导致整个包重传;每片都需要独立的 IP 头,浪费带宽。因此 TCP 在三次握手时会协商 **MSS**(Maximum Segment Size)来主动避分片,而 UDP 应用(如 DNS、QUIC)则通过程序层面控制载荷大小。更底层的 **PMTU Discovery** 机制还能动态探测路径上最小的 MTU。
## 接收端:逐层解封装
```mermaid
sequenceDiagram
participant ETH as "网卡收到 Frame"
participant IP as "剥离以太网头"
participant TCP as "剥离 TCP 头"
participant App as "提取 HTTP 数据"
ETH->>IP: "FCS 校验通过, EtherType=0x0800, 剥离以太网头"
IP->>TCP: "IP Checksum 正确, Protocol=6 TCP, 剥离 IP 头"
TCP->>App: "按 Sequence Number 重组, 剥离 TCP 头"
App->>App: "解析 GET / HTTP/1.1..."
```
每层的动作可以概括为三步:
1. **检查**:校验和是否正确?mac/ip/port 是否指向自己?
2. **剥离**:去掉本层的 Header(必要时 Tail 如 FCS)
3. **移交**:将剩余的 Payload 交给上层协议处理
## 各层 PDU 对照表
| OSI 层 | PDU 名称 | TCP/IP 对应 | 关键标识字段 |
|--------|---------|-------------|-------------|
| 应用层 | 消息 (Message) | 应用层数据 | HTTP 请求行、DNS 报文等 |
| 传输层 | 段/数据报 (Segment / Datagram) | 传输层 PDU | 源端口 + 目的端口 |
| 网络层 | 包 (Packet / Datagram) | 网络层 PDU | 源 IP + 目的 IP + Protocol |
| 链路层 | 帧 (Frame) | 链路层 PDU | 源 MAC + 目的 MAC + EtherType |
| 物理层 | 比特流 (Bits) | 物理层信号 | 电信号 / 光脉冲 / 无线电波 |
> [!QUESTION] TCP 叫 Segment,UDP 叫什么?
> UDP 的 PDU 通常叫 **Datagram**(数据报),因为它不建立连接、不做分段重组,每个数据报独立发送。所以同一层可以有不同的 PDU 名称,取决于使用的传输协议。
## NAT 场景下的封装变化
NAT 路由器修改了中间层的地址:
```mermaid
sequenceDiagram
participant Host as "内部主机"
participant NAT as "NAT 路由器"
participant Server as "外部服务器"
Host->>NAT: "Src=10.0.0.5:45678 Dst=203.0.113.1:80"
Note over NAT: "SNAT 改写源地址"
NAT->>Server: "Src=8.8.8.8:45678 Dst=203.0.113.1:80"
Server->>NAT: "Src=203.0.113.1:80 Dst=8.8.8.8:45678"
Note over NAT: "查 NAT 表, 反向翻译"
NAT->>Host: "Src=203.0.113.1:80 Dst=10.0.0.5:45678"
```
NAT **不触碰应用层载荷**,只修改 IP 头和 TCP/UDP 端口。但这也意味着**传输层的 Checksum 需要重新计算**,因为 TCP/UDP 的校验和覆盖了伪首部(Pseudo Header)中的源/目的 IP 地址。
## TCP vs UDP:封装有什么不同?
两者都在传输层,但封装风格截然不同:
| 对比项 | TCP | UDP |
|--------|-----|-----|
| 首部大小 | 最小 20 字节,最大 60 字节(含 Options) | 固定 8 字节 |
| 连接状态 | 三次握手建连接,携带 seq/ack 等状态 | 无连接,每个数据报独立 |
| 分段重组 | 传输层自动分段和重组 | 不分段,超过 MTU 则由 IP 层分片 |
| 校验和 | 强制(IPv4/IPv6) | IPv4 可选,IPv6 强制 |
```mermaid
flowchart LR
subgraph "TCP Segment"
A["TCP Header 20-60B<br/>SrcPort DstPort Seq Ack Flags..."] --> B["Payload"]
end
subgraph "UDP Datagram"
C["UDP Header 8B<br/>SrcPort DstPort Len Chksum"] --> D["Payload"]
end
style A fill:#FFD700
style C fill:#FFA07A
```
> [!QUESTION] UDP 头只有 8 字节,是不是"更好"?
> 更小的头部确实开销更低,但你失去的是可靠传输、流量控制、拥塞控制等保障。选择哪个取决于业务场景:文件传输用 TCP,游戏/视频/QUIC 用 UDP + 应用层自建可靠性。
## TLS 封装:在哪一层"套娃"?
TLS(Transport Layer Security)常被认为是"应用层协议",但它实际上在 **OSI 第 5-6 层(会话/表示层)** 工作,夹在应用层和传输层之间:
```mermaid
flowchart LR
subgraph "HTTPS = HTTP + TLS + TCP"
H["HTTP 报文"] --> T["TLS 加密<br/>+ TLS Record Header 5B"]
T --> C["TCP Segment"]
C --> I["IP Packet"]
end
style H fill:#DDA0DD
style T fill:#FF6347
style C fill:#FFD700
style I fill:#87CEEB
```
关键点:TLS Record 协议会在 HTTP 载荷前添加 **5 字节的 Record Header**(ContentType + Version + Length),然后整个 TLS Record 作为 TCP 的 Payload 继续向下封装。所以 HTTPS 比 HTTP 多了 TLS 的封装开销(握手阶段更大,数据阶段约 ~40 字节 overhead)。
## 关联笔记
- [[hhs/NETWORK/01-基础概念/01-OSI与TCP-IP模型对比]] — 分层模型的起源与对比
- [[hhs/NETWORK/03-网络层/01-IPv4首部与分段重组]] — IP 包的详细格式与分片机制
- [[hhs/NETWORK/04-传输层/01-TCP段结构与状态机]] — TCP 段的结构与状态管理
- [[hhs/NETWORK/04-传输层/06-UDP协议与SCTP]] — UDP 的无连接封装特点
- [[hhs/NETWORK/03-网络层/08-NAT原理与应用]] — NAT 的详细原理与配置