---
tags: [计算机网络, TCP流量控制, 拥塞控制, BBR, Cubic]
create time: 2026-05-18 02:00
---
# 流量控制与拥塞控制
## 概述
TCP 有两个"刹车"机制:流量控制(防止接收方被淹没)和拥塞控制(防止网络被撑爆)。前者是**端到端**的,后者是**全网协同**的。
## 流量控制(Flow Control)
### 滑动窗口原理
```mermaid
flowchart LR
Sender["发送方"] -->|"seq 100..299"| Recv["接收方
rwnd = 300"]
Recv -->|"ACK=100, window=300"| Sender
Note over Sender:"可用窗口 = min(cwnd, rwnd)"
Note over Recv:"rwnd = RecvBufSize - UnackedData"
```
| 概念 | 含义 |
|------|------|
| **rwnd** (Receive Window) | 接收方的剩余缓冲区大小,告诉发送方"我还能收多少" |
| **cwnd** (Congestion Window) | 拥塞窗口,由发送方根据网络状况自行估算 |
| **实际可用窗口** | `min(rwnd, cwnd)` — 取两者较小值 |
### 零窗口问题
当接收方缓冲区满时,会通告窗口大小为 0:
```
发送方收到 ACK with window=0 → 停止发送数据 → 进入 persist timer 探测状态
```
### Zero Window Probe (ZWP)
Linux 内核的 ZWP 定时发送一个字节的数据来探测窗口是否恢复:
```bash
$ sysctl net.ipv4.tcp_no_metrics_save
net.ipv4.tcp_no_metrics_save = 1 # 避免缓存错误的 RTT 值
```
> [!warning] 死锁场景
> 如果 ZWP 丢失且对端没有响应,连接会永久僵死。解决方案:应用层实现超时重连。
## 拥塞控制(Congestion Control)
### 四种核心算法
```mermaid
flowchart TD
subgraph "慢启动阶段 Slow Start"
A["cwnd = 1 MSS"] -->|"每个RTT翻倍(指数增长)"| B[cwnd ≥ ssthresh?]
end
B -->|"是"| C["进入拥塞避免
(线性增长)"]
subgraph "拥塞避免阶段 Congestion Avoidance"
C -->|"每个RTT+1 MSS
(线性增长)"| D[检测到丢包? ]
end
D -->|"是"| E["ssthresh = cwnd / 2
cwnd = 1 (或 2 MSS)"]
E --> F["回到慢启动
(快速恢复)"]
F --> G["快速恢复后
进入拥塞避免"]
style A fill:#DDA0DD,color:#000
style C fill:#FFD700,color:#000
style E fill:#FF6B6B,color:#fff
style G fill:#98FB98,color:#000
```
### 详细对照表
| 阶段 | cwnd 变化 | 增长速度 | 适用场景 |
|------|----------|---------|---------|
| **慢启动** | 每 RTT × 2 | 指数 | 初始探测、快速恢复后 |
| **拥塞避免** | 每 RTT + 1 MSS | 线性 | 接近容量时的精细调节 |
| **快重传** | 收到 3 个 Dup ACK | 立即重传 | 轻微丢包 |
| **快恢复** | cwnd = max(cwnd/2, 2MSS) | 从减半值开始慢启动 | 伴随快重传 |
### 三种触发事件
| 事件 | 操作 | ssthresh | cwnd |
|------|------|---------|------|
| 3个重复 ACK | 快重传 + 快恢复 | cwnd / 2 | max(cwnd/2, 2×MSS) |
| RTO 超时 | 慢开始 | cwnd / 2 | 1 MSS (Reno/Cubic) / 3 segments (NewReno) |
| SACK 确认部分缺失 | SACK-based fast recovery | 同上 | 同上 |
## Linux 拥塞控制算法对比
### 可选算法列表
```bash
$ cat /proc/sys/net/ipv4/congestion_control
bbr
$ ls /lib/modules/$(uname -r)/kernel/net/ipv4/*_cc.ko*
tcp_cubic.ko tcp_dctcp.ko tcp_htcp.ko tcp_highspeed.ko tcp_hybla.ko tcp_illinois.ko tcp_lp.ko tcp_reno.ko tcp_scalable.ko tcp_vegas.ko tcp_westwood.ko tcp_yeah.ko tcp_bbr.ko
```
### 主流算法对比
```mermaid
flowchart LR
Reno["TCP Reno
• cwnd/2 on drop
• Classic cubic curve"]
Cubic["TCP Cubic (Linux default)
• Non-linear recovery
• Better for high-BDP links"]
BBR["Google BBR v2
• Model bottleneck
• Max throughput, low latency
• No loss-based triggering"]
DCTCP["DCTCP (Datacenter)
• ECN-based
• Near-zero congestion in DC"]
style Reno fill:#DDA0DD,color:#000
style Cubic fill:#FFD700,color:#000
style BBR fill:#98FB98,color:#000
style DCTCP fill:#B0C4DE,color:#000
```
| 特性 | Reno | Cubic (默认) | BBR v2 | DCTCP |
|------|------|-------------|--------|-------|
| 触发方式 | 丢包/重复ACK | 丢包/重复ACK | **延迟模型** | ECN标记 |
| 高带宽利用 | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 低延迟 | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 数据中心 | ❌ | ✅ | ✅ (推荐) | ✅ (需ECN支持) |
| WAN/广域网 | ✅ | ✅ | ✅✅ | ❌ |
| Google 生产环境 | — | — | ✅ 全部使用 | — |
| 配置复杂度 | 零 | 调参选项多 | 最少 (只需设置目标 bw/rtt) | 需交换机支持 ECN |
### BBR v2 核心思想
BBR 不依赖丢包作为拥堵信号——它直接建立网络管道模型:
```
BBR 维护三个核心测量值:
┌─────────────┬──────────────┬─────────────┐
│ Bottleneck │ Propagation │ In-flight │
│ Bandwidth │ Delay (BDP) │ Data Limit │
│ (最高吞吐) │ (最低延迟) │ (当前水量) │
└─────────────┴──────────────┴─────────────┘
然后动态调整: send_rate ≤ bw AND in_flight ≤ bdp + buffer
```
```bash
# 切换为 BBR
$ sudo sysctl net.ipv4.tcp_congestion_control=bbr
$ echo 'net.ipv4.tcp_congestion_control=bbr' >> /etc/sysctl.conf
# 验证
$ ss --info
... cubic bbr ...
```
## Go 中的实践
### 设置 TCP 选项
```go
import "golang.org/x/net/ipv4"
// 设置 socket 级别的拥塞控制相关参数
conn, _ := net.Dial("tcp", "example.com:80")
c := ipv4.NewConn(conn.RawConn())
c.SetTrafficClass(0) // DSCP/ECN
c.SetNoDelay(false) // Nagle 算法关闭 → 小包立刻发
```
```go
// http.Transport 的连接管理
transport := &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
}
// 自定义 DialContext 以启用 keepalive
dialer := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second, // 替代内核默认的 7200s
}
```
## 关联笔记
- [[hhs/NETWORK/TCP段结构与状态机]] — cwnd/rwnd 在 TCP 首部中的 Window 字段
- [[hhs/NETWORK/TCP三次握手与四次挥手]] — 握手中协商的 Window Scale 选项
- [[hhs/NETWORK/TCP粘包与拆包]] — PSH 标志与流量控制的关联