第二章 TCP/IP 协议栈详解:数据包在协议栈中的旅程

这一章,咱们聊聊数据包在协议栈里到底是怎么“跑”的。很多搞网络的看了多年 TCP/IP 图,但真要分析吞吐瓶颈时,却说不清每一层到底干了什么、费了多少开销。

我个人习惯把协议栈想象成一台“包快递流水线”——每经过一层,就套一个信封,信封上有不同的地址和说明。到了接收端,再一层层拆开。每次套信封和拆信封,都有代价。下面我们就掰开揉碎看看每一层的开销。

2.1 数据包的封装与解封装

先看发送方向。应用层扔出一块数据(比如 HTTP 请求报文),然后传输层给它加上 TCP 头(或 UDP 头),变成段(Segment)。接着网络层加上 IP 头,变成包(Packet)。链路层再加上 以太网头 和尾部校验,变成帧(Frame)。最后物理层把帧编码成比特流,扔到线缆上。

接收方顺序反过来,每一层剥掉对应的头部,把数据往上送。整个过程就像俄罗斯套娃。

关键点:每层头部的大小直接影响有效载荷占比。你在网线上看到的所有比特,都不全是“有用数据”。

下面我用一张图把全过程串起来——

数据包在协议栈中的封装旅程 应用层数据(HTTP 请求) TCP头(20B) 应用数据 IP头(20B) TCP头 应用数据 以太网头(14B) IP头 TCP头 应用数据 FCS(4B) 物理层:编码后的比特流(含前导码+帧间隙) 各层头部开销速览 • 以太网头:14 字节(目的MAC 6B + 源MAC 6B + 类型/长度 2B),不含帧前导码和间隙 • IP 头:标准 20 字节,带选项时最大 60 字节 • TCP 头:标准 20 字节,带时间戳等选项时可达 60 字节 • UDP 头:固定 8 字节(比 TCP 轻量很多) • FCS(帧校验序列):4 字节 • 前导码(7B)+ 帧起始定界符(1B)+ 最小帧间隙(12B)——物理层固定开销,不计入帧长 影响:一个 64 字节的最小以太网帧,有效载荷只有 64-14-20-20-4 = 6 字节?不对,注意 TCP 头+IP 头已经 40 字节,再减去以太网头 14 和 FCS 4,剩下 6 字节?答案是有 padding 填充,实际上最小帧 64 字节,IP 数据报至少 46 字节(含 padding),所以 TCP 有效载荷可以到 0。这个问题很多工程师容易算错。

2.2 各层开销深度分析

记住这张图后,咱们看看每一层具体“吃”掉多少带宽。我直接给出一张对比表,你一眼就能看出大小差异——

协议层 PDU 名称 头部固定大小 常见选项附加 备注
应用层 消息 (Message) 无固定开销 HTTP/2 帧头 9B 等 应用协议自己定义的
传输层 (TCP) 段 (Segment) 20 字节 时间戳 12B, SACK 等 最常遇到的选项
传输层 (UDP) 数据报 (Datagram) 8 字节 无 极其轻量
网络层 (IPv4) 包 (Packet) 20 字节 IP 选项最大 40B 很少用选项
网络层 (IPv6) 包 (Packet) 40 字节 扩展头 固定头部比 IPv4 大
链路层 (以太网) 帧 (Frame) 14 字节 + FCS 4B VLAN 标签 4B, MPLS 等 VLAN 会让帧变大
物理层 (1000BASE-T) 编码前比特 前导码 8B + IPG 12B —— 每帧固定额外 20B 开销

我的小技巧:计算有效吞吐时,别只算头部,还要算前导码和帧间隙。我曾经帮一家视频监控公司优化 NFS 存储吞吐,发现他们抓着 MTU 调半天,结果瓶颈在协议栈的“小包太多”导致帧间隙比例过大。

2.3 实战:千兆以太网的理论与实测吞吐

千兆以太网,线速是 1 Gbps = 1,000,000,000 比特/秒(注意不是 1024 进制)。但在协议栈眼里,这 1 Gbps 包含了所有帧间隙、前导码、帧头和校验。真正能被应用层用到多少?

先算一个标准以太网帧的时长。假设帧长 L(不含前导码和间隙,但包括以太网头、IP 头、TCP 头、有效载荷、FCS),那么线缆上实际发送的“物理帧”长度是 L + 8(前导码)+ 12(IPG最小)。所以链路利用率 = L / (L + 20)。下面这个表很实用——

帧长 L (字节) 物理帧总长 (字节) 链路利用率 理论最大有效吞吐 (Mbps) 备注
64 (最小)8476.2%762ACK 包很多时接近这个值
25627692.8%928常见小文件请求
51253296.2%962一般混合流量
1024104498.1%981大块数据传输
1518 (标准最大)153898.7%987经典最大帧(不含 VLAN)
1522 (带VLAN)154298.7%987VLAN 标签多了 4B
9000 (巨型帧)902099.8%998前提是两端都支持巨型帧

看到没?只靠增大帧长,最多能从 762 Mbps 提升到 998 Mbps,差 236 Mbps 呢!我在一个数据中心项目中,就是因为 NFS 默认用巨型帧,吞吐从 820M 飙到 990M+。这也是为什么 iSCSI、NFS 这类协议强烈推荐 jumbo frames。

千万注意:上表的“理论最大有效吞吐”只是链路层的极限。你还要减掉 TCP/IP 头部开销。以 1460 字节 MSS(即 MTU 1500 下 TCP 最大段大小)为例:有效载荷 1460B,总帧长 1518B,物理帧 1538B,TCP+IP 头占 40B,所以应用层有效吞吐 = 1460/1538 × 1000 ≈ 950 Mbps。再减掉协议栈处理延迟、中断、CPU 拷贝,实际你能看到的 iperf 结果一般 940-960 Mbps 就很好了。

2.4 避坑指南:你可能忽略的开销

  • TCP 选项膨胀:时间戳 12B,SACK 许可 2B,窗口缩放 3B……如果两端的 TCP 栈用了很多选项,MSS 会被压缩。我在某云厂商排查时发现,Linux 默认开了 TCP 时间戳,每个包额外多 12 字节,当时还没用巨型帧,一下子有效吞吐掉了近 1%。
  • VLAN 标签:给帧额外加 4 字节,如果你在接入交换机口用 trunk,帧长变成 1522,有些老旧交换机就丢弃了,造成丢包重传。我曾经因为配置不当,导致跨 VLAN 的 iSCSI 流量吞吐只有 400M。
  • IP 分片:当 IP 包超过 MTU 且未设置 DF 位时,中间网络设备会分片。分片导致额外头部开销和重组负担。嗯,这个在千兆网里很少见,但万一遇到,吞吐直接腰斩。
  • 以太网最小帧限制:如果大量小包(例如 64B ACK 包),物理层利用率降到 76%,CPU 还需要每秒处理 14.88 万个小包,硬件中断暴涨。这时你看到网卡跑不满 1G,可能不是带宽不够,而是包率爆了。

我曾经踩过的坑:调优时只盯着 iperf 测大包,结果一跑真实业务(大量短连接+小包),吞吐掉到 300M 不到。最后发现是中断负载不均:只有一个 CPU 核在收包。改用 RPS/RSS 后,吞吐直接翻倍。