网络传输的过程
一次网络传输是数据从发送端应用进程到接收端应用进程的完整旅程,跨越网络协议栈的各个层次。以一次 HTTP 请求为例:
分层模型
| 层次 | 代表协议 | 职责 |
|---|---|---|
| 应用层 | HTTP/HTTPS、DNS、SMTP | 为应用提供网络服务 |
| 传输层 | TCP、UDP | 端到端进程通信、端口管理、流量/拥塞控制、差错检测 |
| 网络层 | IP、ICMP | 主机寻址、分组转发与路由 |
| 链路层 | 以太网、Wi-Fi | 帧封装、MAC 寻址 |
| 物理层 | — | 比特级物理传输 |
一次 HTTP 请求的完整过程
- DNS 解析:应用层发起域名解析,将域名转换为 IP 地址(走 UDP 53 端口查询)。
- TCP 三次握手:建立可靠的传输连接,同步双方的初始序列号。sequenceDiagram participant C as Client participant S as Server C->>S: SYN (seq=x) S->>C: SYN+ACK (seq=y, ack=x+1) C->>S: ACK (ack=y+1)
- TLS 握手(HTTPS):协商密钥,建立加密通道。
- HTTP 请求发送:应用数据被交给传输层。
- 传输层处理:
- TCP 将数据流按 MSS 分段,加上序列号、校验和,交给网络层;通过确认与重传保证可靠性。
- UDP 不加可靠性机制,只做简单封装,交由网络层尽力传输。
- 网络层处理:IP 加上源/目的 IP 地址,路由到目标网络。
- 链路层封装:以太网帧加上 MAC 地址,通过物理介质传输,沿途经过交换机(链路层转发)、路由器(网络层转发)。
- 接收端逐层解封装,最终由应用层处理 HTTP 响应。
TCP vs UDP
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠:确认、重传、排序 | 尽力而为,不保证送达 |
| 流量/拥塞控制 | 有 | 无 |
| 传输单位 | 字节流(按 MSS 分段) | 数据报(应用自己分片) |
| 首部开销 | 20~60 字节 | 8 字节 |
| 典型应用 | HTTP、FTP、SSH、SMTP | DNS、音视频通话、游戏、QUIC(基于UDP) |
连接建立与复用
每次连接建立都要付出握手延迟的代价,优化方向一是减少握手往返,二是尽量复用已有连接。
握手延迟的构成
- TCP 三次握手:1 RTT。
- TLS 握手:TLS 1.2 需要 2 RTT,TLS 1.3 压缩到 1 RTT。
- 加起来一次 HTTPS 新连接最快 2 RTT(TLS 1.3),TLS 1.2 则是 3 RTT。
减少握手往返
- TCP Fast Open (TFO):在 TCP 握手的同时携带首包数据(
tcp_fastopensysctl 控制,取值 1~3 组合客户端/服务器端支持)。适用于短连接场景,节省 1 RTT。 - TLS 1.3 0-RTT / session resumption:客户端缓存会话票据(Session Ticket),下次连接跳过完整握手直接发数据。注意 0-RTT 数据有重放攻击风险,只适合幂等请求。
连接复用
| 技术 | 原理 | 效果 |
|---|---|---|
| HTTP keep-alive | 同一 TCP 连接上串行发送多个 HTTP 请求,避免反复握手 | 避免握手开销,但仍有队头阻塞 |
| 连接池 | 客户端维护一组空闲连接循环复用 | 消除建连延迟,控制并发 |
| HTTP/2 多路复用 | 单条 TCP 连接上并行交错传输多个流(frame 级交错) | 复用连接 + 并行请求 |
| HTTP/3 (QUIC) | 单个 QUIC 连接内多路复用多个流,无队头阻塞 | 进一步降低延迟 |
实践要点
- 使用 HTTP/2 时通常只保留少量连接(如浏览器与单域保持 1~2 条),大量短连接反而低效。
- 服务端注意连接空闲超时(keep-alive timeout)与最大连接数的平衡,避免耗尽资源。
- TLS 会话复用需配合合理的会话缓存策略(Redis 等集中式缓存可跨实例共享票据)。
MTU 与 MSS
MTU(Maximum Transmission Unit):链路层一次能承载的最大数据单元,即 IP 报文的最大长度。常见值:以太网 1500 字节、PPP 1492、巨型帧(Jumbo Frame)9000 字节。
MSS(Maximum Segment Size):TCP 一次能发送的最大数据段,由 MTU 推导:
MSS = MTU - IP首部(20) - TCP首部(20)
= 1500 - 40 = 1460 字节(以太网典型值)
为什么 MTU 重要
- IP 报文超过链路 MTU 时会被分片(fragmentation),分片增大开销、易引发丢包(丢失一片则整包重传)。
- 分片在接收端重组,需要额外内存和 CPU。
- 路径 MTU 不一致:各段链路的 MTU 可能不同,最受限制的是整条路径的最小值(Path MTU)。
PMTUD(Path MTU Discovery)
- IPv4 中通过 ICMP 的 “Destination Unreachable / Fragmentation Needed” 报文反馈更小的 MTU。
- 问题:许多网络屏蔽 ICMP,导致 PMTUD 失效,出现 “black hole”(黑洞),表现为连接建立但大包发不出去。
- 现代缓解:TCP 通过 MSS 钳制(clamping)——路由器改写 SYN 中的 MSS 字段,以及使用
tcp_min_mss/ 禁用分片的策略。
巨型帧
- 数据中心内部常用 Jumbo Frame(MTU 9000),减少报文数量、降低 CPU 中断与包头开销,提升吞吐。
- 需要整条路径所有设备(网卡、交换机)都支持并配置一致。
- 检查命令:
ping -M do -s 8972 <host>(9000 减去 ICMP 首部)。
TCP 可靠性机制与选项
TCP 的可靠性由一系列机制共同保证,合理的选项配置直接影响传输效率。
核心机制
- 确认与重传:接收方对收到的数据回 ACK,发送方对未确认的数据在超时(RTO)或连续收到重复 ACK 后重传。
- 快速重传(Fast Retransmit):收到 3 个重复 ACK 即认为丢包并重传,无需等待超时,明显缩短恢复时间。
- SACK(Selective Acknowledgment):接收方精确告知哪些段已收到,发送方只需重传真正丢失的段,避免"窗口回退"式低效重传。现代 Linux 默认开启(
net.ipv4.tcp_sack)。 - Window Scaling:默认 TCP 窗口字段只有 16 位(最大 64KB),Window Scaling 通过选项扩展窗口到最大 1GB,是**长肥管道(高带宽×高延迟)**实现高吞吐的前提。
- 时间戳选项(Timestamps):用于 RTT 测量、防序列号回绕(PAWS),提升拥塞控制与重传的精确性。
Nagle 与延迟权衡
- Nagle 算法:合并小包再发送,减少网络中微小报文数量,但会引入延迟——小请求可能等待前一个包 ACK 才发出(“延迟 ACK"叠加,最多 40ms)。
- TCP_NODELAY:关闭 Nagle,小包立即发送,适用于交互式应用(如 SSH、游戏、实时 API)。
- 权衡:吞吐优先(批量传输)用 Nagle,延迟优先(交互)用
TCP_NODELAY;两者结合还会出现"傻窗口综合症”(Silly Window Syndrome),需要发送端合并 + 接收端避免通告过小窗口(TCP_QUICKACK)。
调优参数(sysctl)
# 超时重传相关
net.ipv4.tcp_syn_retries # SYN 重传次数
net.ipv4.tcp_synack_retries
net.ipv4.tcp_retries1/2 # 放弃连接前的重传次数
# 可靠性选项
net.ipv4.tcp_sack = 1
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_timestamps = 1
# 延迟 ACK(0 关闭)
net.ipv4.tcp_ack_delay = 0
带宽-延迟积 (BDP)
BDP(Bandwidth-Delay Product)= 带宽 × RTT,表示"管道中能容纳的数据量"。
BDP = 带宽 × 往返时延
例: 1 Gbps × 100 ms = 12.5 MB(即至少需要 12.5MB 的在途数据才能跑满带宽)
为什么 BDP 重要
- 有效吞吐 ≈
在途数据 / RTT,而在途数据受 cwnd 与 rwnd 双重限制。 - 若缓冲区(rwnd/cwnd)远小于 BDP,即使带宽很高,吞吐也会被延迟"卡死"——这就是长肥管道问题。
- 发送缓冲/接收缓冲/拥塞窗口必须与 BDP 匹配才能利用满带宽。
实践要点
# 估算某个连接的 BDP(需要多大缓冲)
# 例如 RTT=100ms, 带宽=1Gbps → 需要 12.5MB 缓冲
# 适当增大 socket 缓冲区(默认往往偏小)
sysctl -w net.core.rmem_max=134217728 # 128MB
sysctl -w net.core.wmem_max=134217728
sysctl -w net.ipv4.tcp_rmem='4096 87380 134217728'
sysctl -w net.ipv4.tcp_wmem='4096 65536 134217728'
# 查看实际吞吐: iperf3 或 bmon
iperf3 -c <server> -R -t 30
TCP 拥塞控制
网络拥塞会导致丢包和延迟增大。拥塞控制的目标是在不压垮网络的前提下尽量利用带宽,通过调节拥塞窗口 cwnd(发送端允许在途的数据量)实现。
核心机制
cwnd 决定发送速率;有效吞吐 ≈ cwnd / RTT
在途数据 = min(cwnd, 接收端窗口 rwnd, 网络容量)
- 慢启动(Slow Start):连接建立后 cwnd 从初始值(如 10 个 MSS)开始,每收到一个 ACK 增长一倍(指数增长),直到 ssthresh。
- 拥塞避免(Congestion Avoidance):超过 ssthresh 后,每个 RTT 线性增长,试探网络容量上限。
- 拥塞事件处理:检测到丢包/ECN/超时后,降低 cwnd,再重新爬坡。
主要算法
| 算法 | 特点 | 适用场景 |
|---|---|---|
| Tahoe | 丢包即重置为 1,过于激进 | 早期参考实现 |
| Reno | 快速重传+快速恢复,乘法减半 | 经典实现,对随机丢包敏感 |
| NewReno | 改进多丢包处理 | 长肥管道改进 |
| CUBIC | 基于时间三次函数探测,带宽增长更快、更公平 | Linux 默认算法,高带宽高延迟网络 |
| BBR | 基于带宽与延迟测量而非丢包,不把丢包当拥塞信号 | 高吞吐长肥网络,Wi-Fi/蜂窝等丢包率高的链路 |
- 查看当前算法:
sysctl net.ipv4.tcp_congestion_control(Linux 默认cubic)。 - 切换算法:
sysctl -w net.ipv4.tcp_congestion_control=bbr(需内核支持tcp_bbr模块)。 - BBR 优势:不再用"填满缓冲区到丢包"的方式来探带宽,能显著降低延迟并提高吞吐,尤其适合无线网络和跨国链路。
- ECN(Explicit Congestion Notification):路由器通过 IP 头标记拥塞(而非丢包),TCP 提前降速,避免丢包带来的重传浪费。
Ubuntu 安装与配置 BBR 实战
BBR 从 Linux 4.9 起合入内核,Ubuntu 16.04 之后的内核均自带,一般无需编译内核,但 BBR 常以模块形式编译(CONFIG_TCP_CONG_BBR=m),首次使用前需要加载。以下以 Ubuntu 为例:
# 1. 检查内核版本(需 >= 4.9)
uname -r
# 2. 加载 tcp_bbr 模块(常见坑:不加载则 bbr 不可用)
sudo modprobe tcp_bbr
# 3. 验证已注册的拥塞控制算法(出现 bbr 即成功)
sysctl net.ipv4.tcp_available_congestion_control
# 4. 启用 BBR
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr
# 5. 验证生效
sysctl net.ipv4.tcp_congestion_control # 输出 bbr
持久化配置(重启后仍生效),写入 /etc/sysctl.d/99-bbr.conf:
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
然后 sudo sysctl --system 加载。其中 default_qdisc = fq(fq fair queueing)是 BBR 官方推荐配套的排队规则——BBR 依靠 pacing 平滑发包,fq 能提供每流带宽估算与 pacing 支持,二者配合效果最佳。
故障排查:若步骤 4 报
invalid argument,通常有两种原因——① 模块未加载(回到步骤 2);② 内核太老不支持(需升级内核)。用sysctl net.ipv4.tcp_available_congestion_control查看当前内核实际支持的算法列表即可定位。
相关参数调优(sysctl)
# 调整 socket 缓冲区(影响 rwnd 上限,配合窗口缩放)
net.core.rmem_max
net.core.wmem_max
net.ipv4.tcp_rmem
net.ipv4.tcp_wmem
# 初始拥塞窗口(部分发行版通过 ip route 设置 initcwnd)
ip route change default via <gw> dev <dev> initcwnd 20
# 启用 ECN
net.ipv4.tcp_ecn = 1
QUIC 协议
QUIC 是 Google 设计、IETF 标准化的新一代传输协议,基于 UDP 实现,是 HTTP/3 的底层传输协议,旨在解决 TCP+TLS 的固有问题。
解决的核心问题
- 连接建立延迟:
- TCP+TLS 1.3 最快需要 1-RTT(握手)+ 1-RTT(TLS)= 1~2 RTT。
- QUIC 首次连接 1-RTT,复用连接 0-RTT(客户端可立即发送数据)。
- 队头阻塞(Head-of-Line Blocking):
- HTTP/2 在单个 TCP 连接上多路复用多个流,但 TCP 是字节流——一个包丢失会导致后续所有流的数据阻塞等待重传。
- QUIC 每个流独立交付,丢包只影响该流,其他流不受影响。
- 传输层与加密耦合:
- TCP 由内核实现,无法快速迭代;QUIC 在用户态实现,内置 TLS 1.3,可像应用一样升级部署。
- 连接迁移:
- TCP 以四元组(源/目的 IP+端口)标识连接,切换网络(Wi-Fi→4G)会导致连接断开重建。
- QUIC 使用 64 位连接 ID 标识连接,IP 变化不影响连接,可无缝迁移。
QUIC 结构
- 多路复用流(Streams):单个连接内多个双向/单向流,互相独立、各自有序。
- 内置拥塞控制:默认类似 NewReno/CUBIC,可插拔(也支持 BBR),且比 TCP 收敛更快。
- 0-RTT 恢复:凭缓存的可信连接参数(TLS ticket),跳过握手直接发数据;需注意重放攻击风险。
QUIC vs TCP+TLS
| 维度 | TCP + TLS | QUIC |
|---|---|---|
| 底层 | TCP(内核态) | UDP(用户态) |
| 握手 | 1~2 RTT | 0~1 RTT |
| 队头阻塞 | 有(字节流) | 无(多流独立) |
| 连接迁移 | 不支持(IP 变即断) | 支持(连接 ID) |
| 加密 | 可选、后加 | 内置、强制 |
| 迭代部署 | 慢(内核升级) | 快(用户态升级) |
实践与注意
- HTTP/3 使用 UDP 443 端口,防火墙/NAT 需放行 UDP 流量。
- 由于基于 UDP,部分老设备/中间设备可能拦截 UDP 大包或限速。
- 查看是否使用 QUIC:浏览器开发者工具 Network 标签中 Protocol 显示
h3;或抓包观察 UDP 443 端口。
gRPC / HTTP/2 传输
gRPC 是 Google 开源的 RPC 框架,基于 HTTP/2,广泛用于微服务内部通信。它不是一个新传输协议,但把 HTTP/2 的传输特性用到了极致,其效率机制值得单独讨论。
HTTP/2 的传输特性
- 多路复用:单条 TCP 连接上并行交错传输多个流(stream),各流以 frame 为单位交织,避免"一个请求一个连接"的握手开销。
- 二进制分帧:所有数据以二进制帧传输,解析高效,无需文本解析。
- 流控(Flow Control):HTTP/2 有两级窗口——每个流一个窗口(per-stream window)和整个连接一个窗口(per-connection window),默认初始窗口 64KB。
- HPACK 头部压缩:头部字段用静态/动态索引表编码,重复 header 只传索引,大幅压缩冗余。
流控窗口与 BDP 的关系
HTTP/2 的流控窗口与 TCP 的 rwnd 类似,都限制"在途数据量"。默认 64KB 的初始窗口在长肥管道(高 BDP)下会成为吞吐瓶颈:
有效吞吐 ≈ min(HTTP/2 流控窗口, TCP cwnd/rwnd) / RTT
- 如果 HTTP/2 流控窗口(64KB)远小于 BDP,即使 TCP 层窗口调得很大,吞吐也上不去。
- gRPC 通过
WithInitialWindowSize/WithInitialConnWindowSize调整两级窗口(实践中常调到 2MB 量级),与 BDP 匹配。 - 流控窗口是动态的——发送端通过 WINDOW_UPDATE 帧补充信用,接收端根据自身处理能力通告。
连接管理(gRPC 实践)
| 机制 | 作用 | 对应传输优化点 |
|---|---|---|
| 连接池 | 复用已建立的 HTTP/2 连接 | 避免反复握手(对应「连接建立与复用」) |
| 预连接 / 预热 | 启动时提前建立连接 | 消除首个请求的握手延迟 |
| Keepalive | 周期性发送 ping 保持连接活跃 | 防止中间设备(LB/防火墙)超时回收空闲连接 |
| Backoff 重试 | 指数退避 + 抖动控制重连频率 | 避免重连风暴,节省资源 |
效率考量
- 连接过少 vs 过多:HTTP/2 多路复用后通常只需少量连接(gRPC 每个目标几条约 4 条),太多连接浪费资源;太少则受单连接窗口与拥塞状态影响。
- 长流 vs 短流:流控、优先级、依赖关系处理得当才能让多路复用真正高效,否则出现"大流饿死小流"。
- Head-of-Line Blocking:HTTP/2 仍跑在 TCP 字节流上,一个包丢失会阻塞同一连接上所有流——这正是 QUIC/HTTP/3 要解决的痛点(见前节)。
- 头部压缩:HPACK 在有丢包的 TCP 连接上可能因索引表不同步而失效,这是 QPACK(QUIC 版)重新设计的原因之一。
与 REST 对比
| 维度 | REST/HTTP/1.1 | gRPC/HTTP/2 |
|---|---|---|
| 数据格式 | JSON(文本、冗余) | Protobuf(二进制、紧凑) |
| 连接利用 | 短连接或串行复用 | 多路复用、帧级并行 |
| 头部开销 | 每请求完整重复 | HPACK 压缩,只传增量 |
| 流式通信 | 不支持(可轮询) | 支持 unary/server-stream/bidi-stream |
数据路径优化(内核与网卡)
传输效率不仅取决于协议,还取决于数据在内核-网卡路径上的搬运方式。减少拷贝、合并报文、并行队列都能显著提升吞吐。
零拷贝
传统路径:应用缓冲 → 内核缓冲 → 网卡,数据被多次复制。零拷贝技术让数据尽量少地被复制:
| 技术 | 原理 | 适用 |
|---|---|---|
sendfile() | 内核直接在文件与 socket 间传输,不经用户空间 | 静态文件服务(Nginx) |
splice() | 通过管道在内核中移动数据 | 通用内核间搬运 |
mmap + send | 用户态零拷贝写入 | 应用自定义路径 |
MSG_ZEROCOPY | 允许用户态缓冲直接交给网卡,避免拷贝 | 高吞吐发送场景 |
网卡卸载(Offload)
- TSO/GSO:让网卡(TSO)或内核(GSO)一次处理大块数据,由硬件/内核自动切分为 MTU 大小报文,降低每包中断与协议栈开销。
- GRO/LRO:接收方向把多个小包合并成一个大数据包上送协议栈,减少处理次数。
- RSS(Receive Side Scaling)/ 多队列:网卡把流量分散到多个接收队列,各队列绑定不同 CPU 核(RPS/RFS),实现多核并行处理。
中断与队列调优
# 查看网卡队列数与中断绑定
lspci | grep -i ethernet
cat /proc/interrupts | grep eth
# 查看/调整 ring buffer
ethtool -g eth0
ethtool -G eth0 rx 4096 tx 4096
# 合并中断(coalesce),降低中断频率换取吞吐
ethtool -C eth0 rx-usecs 50
高性能数据面
- XDP(eXpress Data Path):在网卡驱动层直接处理报文(BPF),绕开内核协议栈,用于 DDoS 过滤、负载均衡、观测。
- DPDK:用户态驱动 + 轮询模式,完全绕过内核网络栈,达到线速转发(L3/L4 负载均衡器常用)。
- 取舍:XDP/DPDK 提升性能但增加复杂度,通常只有高吞吐场景(10Gbps+)才需要。
应用层与 HTTP 层优化
传输效率最终由应用层决定能压榨多少。数据体积与传输次数是两大抓手。
数据压缩
- HTTP 内容压缩:
Content-Encoding: gzip / br(Brotli 压缩率更高),静态资源与文本收益明显。 - 协议头压缩:HTTP/2 用 HPACK、HTTP/3 用 QPACK 压缩 header,减少重复头部的传输量。
- 注意:压缩率低、CPU 敏感的内容(图片/视频)压缩反而得不偿失。
HTTP 缓存
- 强缓存:
Cache-Control: max-age/Expires,命中后完全不发请求。 - 协商缓存:
ETag/Last-Modified,验证后返回 304,省去 body 传输。 - 缓存配合 CDN,让大部分流量在边缘命中,极大降低回源传输。
增量同步与协议精简
- 大文件传输用增量同步(rsync 的 delta、git 的对象复用)而非全量重传。
- 避免"聊天式"请求:批量接口、协议 buffer/msgpack 等二进制格式比 JSON 更省字节。
- WebSocket/SSE 相比轮询显著减少无谓请求次数。
网络架构层面
端到端效率还受网络路径与部署方式影响。
CDN 与 Anycast
- CDN:把内容缓存到离用户近的边缘节点,缩短 RTT、降低源站压力。
- Anycast:多个节点共享同一 IP,路由协议自动选最近/最优节点(DNS 根服务器、负载均衡器常用)。
链路聚合与多路径
- 链路聚合(bonding/team,LACP):多条物理链路捆成一条逻辑链路,提升带宽与冗余。
- MPTCP(Multipath TCP):单个 TCP 连接同时使用多条路径传输,提高吞吐与可靠性(移动设备 Wi-Fi + 蜂窝并发)。
网络测量与诊断
ping/traceroute:看延迟与路径。iperf3:实测端到端带宽。ss -ti:查看连接重传、拥塞窗口等实况。tcpdump/bmon:抓包定位瓶颈层。
总结
网络传输效率的优化贯穿协议栈各层,可从"减少延迟、降低开销、利用满带宽"三个方向入手:
- 连接层:用 keep-alive/连接池/HTTP/2 多路复用连接,用 TFO、TLS 0-RTT、QUIC 减少握手往返。
- 协议与参数:根据网络特性选择 TCP 可靠性选项、窗口缩放、合理的 socket 缓冲区(匹配 BDP),以及 CUBIC 或 BBR 拥塞控制,配合 ECN 减少无效重传。
- HTTP/2 层:多路复用 + 流控窗口与 BDP 匹配 + HPACK 头部压缩,注意其仍受 TCP 队头阻塞限制。
- 链路层:合理的 MTU / 巨型帧 / MSS 钳制,减少分片开销。
- 数据路径:零拷贝(sendfile)、TSO/GSO/GRO 卸载、多队列/RSS,必要时上 XDP/DPDK。
- 应用与部署:压缩、缓存、增量同步减少传输量;CDN/Anycast 缩短路径;链路聚合与 MPTCP 提升带宽。
- 现代演进:QUIC/HTTP/3 在用户态重构传输层,解决 TCP 的握手延迟、队头阻塞与连接迁移问题。
参考
- RFC 793 (TCP)、RFC 9293 (TCP 更新)
- RFC 7540 (HTTP/2)、RFC 7541 (HPACK)
- RFC 9000 (QUIC)、RFC 9114 (HTTP/3)
- RFC 1191 / RFC 4821 (Path MTU Discovery)
- RFC 6182 (MPTCP Architecture)、RFC 7413 (TCP Fast Open)
- gRPC 官方文档
- BBR: Congestion-Based Congestion Control
- BBRv2 草案
- Linux kernel documentation: networking/ip-sysctl.txt
- XDP / DPDK 官方文档