高级 #network#tcp#congestion
TCP 流量控制与拥塞控制
流量控制(Flow Control)防止发送方太快淹没接收方——用滑动窗口调节。拥塞控制(Congestion Control)防止网络中间路由器被撑爆——用慢启动和拥塞避免
🚦 两个”堵车”的问题
TCP 要解决两个完全不同的”太快”问题:
问题 1:接收方来不及收(流量控制) 你说话太快——对方听不过来,记不住。你要慢点说。
问题 2:网络中间节点被撑爆(拥塞控制) 你说的话太多了——中间的”传话人”(Wi-Fi、路由器)处理不过来,排队太长,丢包了。
🏪 类比:食堂打饭
流量控制 = 你(服务器)往窗口端菜——窗口只有 5 个位置,端太快放不下,你得等窗口空出位置再端。
拥塞控制 = 食堂排队太长了——不是因为窗口慢,而是食堂大门只能同时进 10 个人(网络带宽有限)。你不能一次让 100 个人挤进去(发大量包)。
📏 流量控制——滑动窗口
流量控制(Flow Control) 防止”发送方太快,接收方来不及收”。
TCP 头部有一个 Window Size(窗口大小) 字段——接收方用它告诉发送方”我还能收多少字节”。
发送方 接收方
│ │
│──── 数据 1-1000 ──────→ │
│──── 数据 1001-2000 ───→ │ 接收方缓冲区满
│──── 数据 2001-3000 ───→ │ 处理不过来了!
│ │
│←─ ACK, Win=0 ──────────│ "别发了,我满了!"
│ │ (发送方停止发送)
│ │ 应用程序读取数据,腾出空间
│←─ ACK, Win=5000 ───────│ "好了,继续发"
│──── 数据 3001-4000 ───→ │
关键:Win=0 告诉发送方”暂停”——直到接收方发出 Win>0 的确认才继续。这保证了接收方不会被淹没。
🚗 拥塞控制——慢启动 + 拥塞避免
拥塞控制(Congestion Control) 防止”发太快,网络撑不住”——中间路由器缓存满了,开始丢包。
① 慢启动(Slow Start)
刚开始发送时——不确定网络带宽,慢慢试探:
第 1 轮:发 1 个包 → 收到 ACK → 翻倍
第 2 轮:发 2 个包 → 收到 ACK → 翻倍
第 3 轮:发 4 个包 → 收到 ACK → 翻倍
第 4 轮:发 8 个包 → 收到 ACK → 翻倍
...
以指数速度增长——直到:
a) 达到阈值(ssthresh)→ 切换为拥塞避免
b) 发生丢包 → 认为网络拥塞 → 大幅降低发送速率
② 拥塞避免(Congestion Avoidance)
超过阈值后——不再翻倍,改为"每个 RTT(往返时间)加 1":
线性增长——缓慢探测网络极限
如果发生丢包(拥塞信号)→ 将阈值降为当前速率的一半
→ 重新从慢启动开始
发送速率
↑
│ ╱╲ ╱╲ ╱╲
│ ╱ ╲ ╱ ╲ 丢包! ╱ ╲ 丢包!
│╱ ╲ ╱ ╲────────╱ ╲──
│ ╲╱ ╲╱
│ 慢启动 拥塞避免 慢启动
└──────────────────────────────────────→ 时间
📊 TCP 拥塞控制的实际表现
| 场景 | TCP 行为 |
|---|---|
| 刚开始传输 | 慢启动——从 1 开始指数增长 |
| 网络良好 | 线性增长(拥塞避免)——探测上限 |
| 出现丢包 | 认为网络拥塞——速率减半 |
| 长时间空闲 | 重置拥塞窗口——重新慢启动 |
# Linux 查看当前 TCP 拥塞控制算法
$ sysctl net.ipv4.tcp_congestion_control
net.ipv4.tcp_congestion_control = cubic
# 常见算法:cubic(默认)、bbr(Google 开发,高吞吐)、reno(经典)
📝 小结
| 概念 | 一句话 |
|---|---|
| 流量控制 | 防止发送方淹没接收方——接收方告诉发送方”我的缓冲区还剩多少” |
| 滑动窗口 | 接收方通过 TCP 头部的 Window Size 控制发送速率 |
| 拥塞控制 | 防止发送方撑爆网络——检测丢包后减速 |
| 慢启动 | 开始时指数增长试探网络——直到发生丢包或达到阈值 |
| 拥塞避免 | 超过阈值后线性增长——缓慢探测网络上限 |
| BBR | Google 的新算法——不靠丢包判断拥塞,更高效 |