--- 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
Segment 60B"] T --> I["+ IP Header 20B
Packet 80B"] I --> E["+ Ethernet HDR 14B + FCS 4B
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
SrcPort DstPort Seq Ack Flags..."] --> B["Payload"] end subgraph "UDP Datagram" C["UDP Header 8B
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 加密
+ 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 的详细原理与配置