发送方一层层加头,接收方一层层去头:每一层只关心自己那个头。 代价是「每层都要交一份税」:100 字节的数据上线要 158 字节。
6.1.1 讲了「传输层为什么存在」,这一节讲「它具体怎么加头去头」。 第四章讲过 IP 首部,这一节把它放回整条流水线上看。
一、发送方一层层加头
封装就是「套信封」: 应用把数据交给传输层,传输层在前面加一个头,交给网络层;网络层再加一个头,交给链路层。 每一层只看自己那个头,不看里面装的是什么。
| 层 | 加什么 | 长度 |
|---|---|---|
| 应用层 | 数据本身 | 看业务 |
| 传输层 | TCP 头或 UDP 头 | 20 或 8 |
| 网络层 | IP 头 | 20(v4) |
| 链路层 | 以太网头加帧尾 | 14 加 4 |
这个表最该记住的是「每层只管自己那一截」:传输层不知道下面是铜线还是光纤, 网络层不知道上面跑的是网页还是语音。 这种「互不知情」正是分层的价值——换一种链路,上面三层一行代码都不用改。
二、接收方一层层去头
接收方是反着来:链路层剥掉帧头,看类型字段决定交给谁; 网络层剥掉 IP 头,看协议号决定交给 TCP 还是 UDP; 传输层剥掉 TCP 头,看端口号决定交给哪个程序。 这三个「看什么决定交给谁」是关键:以太网看「类型」(0x0800 是 IPv4)、 IP 看「协议号」(6 是 TCP、17 是 UDP)、TCP 看「端口号」。 每一层都有一个「分用键」,这就是 6.1.1 说的分用的具体实现。 注意「去头」不是「删掉」:是「把指针往后挪」,数据本身一直没动。 所以「零拷贝」这种技术在底层能做——因为「加头」加的是新的一块内存,「去头」只是改偏移。
三、为什么要加这么多头
因为每一层的信息别人替不了: 传输层的端口只有端点知道,网络层的地址只有路由器关心,链路层的 MAC 只在一段链路上有效。 把它们合并成一个头是不行的——因为「谁需要什么」在不同层是不一样的。 这也是「为什么不能只要一层」的答案: 如果有人设计一个「一个头搞定所有」的协议,那这个头里就必须同时有端口、IP、MAC, 而这三样的有效期完全不同(MAC 换一段链路就变),放一起就会「一变全变」,没法增量处理。
四、算一下真实的开销
100 字节的应用数据,上线是 158 字节:TCP 20 加 IP 20 加以太网 14 加帧尾 4。 效率只有 63%。 而且这还没算完:以太网还有 8 字节前导码和 12 字节帧间隔, 那些不算在「帧长」里,但确实占线路时间。 所以「100 字节数据」在线路上占的,比 158 还多。 这个开销比例决定了很多工程选择: 为什么「小包业务要合并」、为什么「隧道每层都要扣 MTU」、为什么「语音包要短但也不能太短」—— 全是同一个算术。
五、动手 Demo:100 字节数据要走 158 字节的线路
把 TCP 和 UDP 两条路都算一遍,看有效载荷占多少。 这个比例就是「封装税」。
for name, th in (('TCP', 20), ('UDP', 8)):
total = 100 + th + 20 + 14 + 4
print(name, total, '%.1f%%' % (100.0 * 100 / total))
实跑输出:
--------------------------------------------------------------------
100 字节数据要走 158 字节的线路
--------------------------------------------------------------------
**每下一层都要加一个头**:**应用数据之外,传输层加、网络层加、链路层也加。**
**所以「有效载荷」永远小于「线路上的字节」。**
传输层 TCP/UDP 头 IP 头 以太网头 合计 效率
----------------------------------------------------------------
TCP 20 20 14 158 63.3%
UDP 8 20 14 146 68.5%
**最后一列是「有效载荷占比」**:**TCP 只有 63% 左右,三分之一都花在头上了。**
**所以「传小文件慢」不只是因为文件小,还因为「每个包都要付一份头」。**
**这也是 6.4.10 要讲的「Nagle 算法」想缓解的问题。**
**表格里已经算到帧尾的 4 字节 FCS,但线路上的开销还没完**:
**以太网还有 8 字节前导码和 12 字节帧间隔**,**那些不算在「帧长」里,但确实占线路时间。**
**所以「100 字节数据」在线路上真正占的,比 158 还要多一些。**
TCP 的 158 和 UDP 的 146 差 12 字节:正好是两种头部的长度差。 所以「UDP 更省」这个说法在小包上是真的,在大包上就无所谓了(6.4.10 会算)。 注意「帧尾 4 字节」也算进去了:那是 FCS,用来检测线路错误, 它不是「头」但是「必须付的成本」。 所以「开销」要看「整帧」而不是「只看头」。 最后一个提醒:这个 63% 是「以太网加 IPv4」的结果: 换成 IPv6 还要再多 20 字节(头从 20 变 40),效率降到 56% 左右。 所以「v6 头更大」不是抽象的缺点,是实打实的每包成本。
30 秒实验
- 算一下:1000 字节数据走 TCP 加以太网,效率是多少?
- 想一下:为什么各层的头不能合并成一个?
- 想一下:「去头」为什么可以做到不改数据?
常见误区
- 以为「头是浪费」。 每层的头解决不同层的问题,合并反而没法增量处理。
- 以为「去头是把头删掉」。 只是把偏移往后挪,所以能做到零拷贝。
- 以为「开销只算头部」。 帧尾的 FCS、前导码、帧间隔都要算,它们同样占线路时间。
自测三问(点开对答案)
1. 接收方靠什么决定「交给上一层哪个协议」? —— **以太网看类型字段、IP 看协议号、TCP 看端口号**,每层都有一个分用键。 2. 100 字节数据走 TCP 加以太网,一共多少字节? —— **158 字节**:20 加 20 加 14 加 4,效率约 63%。 3. 为什么各层的头不能合并? —— **因为它们的「有效期」不同**:MAC 换一段链路就变,IP 跨网才变,合成一个就会「一变全变」。下一节把两个主角请出来:TCP 和 UDP。
- ☐ (本节完,下一节 6.1.3 两种传输层协议TCP和UDP)