--- 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 标志与流量控制的关联