2. TCP/IP协议栈详解
这一章,咱们聊聊数据包在协议栈里到底是怎么“跑”的。很多搞网络的看了多年 TCP/IP 图,但真要分析吞吐瓶颈时,却说不清每一层到底干了什么、费了多少开销。
我个人习惯把协议栈想象成一台“包快递流水线”——每经过一层,就套一个信封,信封上有不同的地址和说明。到了接收端,再一层层拆开。每次套信封和拆信封,都有代价。下面我们就掰开揉碎看看每一层的开销。
2.1 数据包的封装与解封装
先看发送方向。应用层扔出一块数据(比如 HTTP 请求报文),然后传输层给它加上 TCP 头(或 UDP 头),变成段(Segment)。接着网络层加上 IP 头,变成包(Packet)。链路层再加上 以太网头 和尾部校验,变成帧(Frame)。最后物理层把帧编码成比特流,扔到线缆上。
接收方顺序反过来,每一层剥掉对应的头部,把数据往上送。整个过程就像俄罗斯套娃。
关键点:每层头部的大小直接影响有效载荷占比。你在网线上看到的所有比特,都不全是“有用数据”。
下面我用一张图把全过程串起来——
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 (最小) | 84 | 76.2% | 762 | ACK 包很多时接近这个值 |
| 256 | 276 | 92.8% | 928 | 常见小文件请求 |
| 512 | 532 | 96.2% | 962 | 一般混合流量 |
| 1024 | 1044 | 98.1% | 981 | 大块数据传输 |
| 1518 (标准最大) | 1538 | 98.7% | 987 | 经典最大帧(不含 VLAN) |
| 1522 (带VLAN) | 1542 | 98.7% | 987 | VLAN 标签多了 4B |
| 9000 (巨型帧) | 9020 | 99.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 后,吞吐直接翻倍。