🚦 TCP拥塞控制:网络的"红绿灯"

54 阅读5分钟

知识点编号:005
难度等级:⭐⭐⭐(必掌握)
面试频率:🔥🔥🔥🔥


🎯 一句话总结

拥塞控制就像开车看路况:路畅通就加速🚗,路堵了就减速🐢!


🤔 什么是拥塞控制?

问题场景

发送方:疯狂发送!
网络:承受不住了,路由器缓冲区满了!
结果:大量丢包!📉

解决:拥塞控制
根据网络状况动态调整发送速度

🎭 四大算法

1. 慢启动(Slow Start)🐌

原理:指数增长

初始:cwnd = 1(拥塞窗口=1个MSS)
第1轮:发送1个包,收到ACK
第2轮:cwnd = 2,发送2个包
第3轮:cwnd = 4,发送4个包
第4轮:cwnd = 8,发送8个包
...

1 → 2 → 4 → 8 → 16 → 32 ...

指数增长,直到达到慢启动阈值(ssthresh)

生活例子:
新手开车,先慢慢加速
油门:10% → 20% → 40% → 80%

2. 拥塞避免(Congestion Avoidance)🚶

原理:线性增长

达到ssthresh后,改为每轮+1
cwnd = 16 → 17 → 18 → 19 ...

为什么要线性增长?
- 接近网络容量了
- 要小心试探
- 避免突然拥塞

生活例子:
接近学校门口,减速慢行
速度:50km/h → 40 → 30 → 20

3. 快速重传(Fast Retransmit)⚡

原理:收到3个重复ACK,立即重传

正常:
发送:1, 2, 3, 4, 5
接收:ACK1, ACK2, ACK3, ACK4, ACK5

丢包:
发送:1, 2, 3(丢), 4, 5
接收:ACK1, ACK2, ACK2, ACK2, ACK2
                    ↑ 3个重复!
发送方:立即重传3号包!

不用等超时重传,更快!⚡

生活例子:
你:"听到了吗?"
朋友:"啊?啊?啊?"3次)
你立刻重复:"听到了吗?!"

4. 快速恢复(Fast Recovery)🔄

原理:快速重传后,不回到慢启动

传统做法:
丢包 → ssthresh = cwnd/2cwnd = 1
      → 慢启动

快速恢复:
丢包 → ssthresh = cwnd/2cwnd = ssthresh + 3
      → 拥塞避免

为什么+3?
因为收到了3个重复ACK,说明网络还能传输

生活例子:
开车遇到小堵车
不是完全停下(cwnd=1)
而是减速到一半继续走

📊 完整过程图

cwnd (拥塞窗口)
  |
40|                            拥塞避免
  |                          /
30|                        /
  |                      /
20|                    /  快速恢复
  |     慢启动        / /
10|    /           /丢包
  |  /           /
 5| /           /
  |/___________/
  +----------------------------> 时间
    1  2  3  4  5  6  7  8

过程说明:
1-4:慢启动阶段(指数增长)
4:达到ssthresh,转为拥塞避免
5:线性增长
6:检测到丢包(3个重复ACK7:快速重传+快速恢复
8:继续拥塞避免

💻 Java代码示例

查看TCP参数

// Linux查看拥塞控制算法
// cat /proc/sys/net/ipv4/tcp_congestion_control
// 输出:cubic(默认算法)

// Java无法直接操作拥塞控制
// 但可以通过系统属性观察

Socket socket = new Socket("localhost", 8080);

// 观察TCP行为
SocketImpl impl = socket.getImpl();

// 设置无延迟(禁用Nagle算法)
socket.setTcpNoDelay(true);

// 发送数据
OutputStream out = socket.getOutputStream();
for (int i = 0; i < 1000; i++) {
    out.write(("数据" + i).getBytes());
    out.flush();
    
    // TCP层会自动进行拥塞控制
    // cwnd会动态调整
}

模拟拥塞场景

public class CongestionDemo {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("localhost", 8888);
        OutputStream out = socket.getOutputStream();
        
        byte[] data = new byte[1024]; // 1KB数据
        
        long start = System.currentTimeMillis();
        
        // 发送大量数据
        for (int i = 0; i < 10000; i++) {
            out.write(data);
            
            if (i % 100 == 0) {
                long time = System.currentTimeMillis() - start;
                double speed = (i * 1024.0 / 1024) / (time / 1000.0);
                System.out.printf("已发送%d KB, 速度: %.2f MB/s\n", 
                    i, speed);
            }
        }
        
        // 观察速度变化:
        // 开始慢(慢启动)
        // 逐渐加快(拥塞避免)
        // 遇到丢包可能下降(快速恢复)
    }
}

🐛 常见面试题

Q1:TCP拥塞控制的四个算法是什么?

答案:

1. 慢启动(Slow Start)
   - 初始cwnd=1
   - 指数增长:1→2→4→8→16
   - 直到达到ssthresh

2. 拥塞避免(Congestion Avoidance)
   - 达到ssthresh后
   - 线性增长:+1 MSS每RTT
   - 小心试探网络容量

3. 快速重传(Fast Retransmit)
   - 收到3个重复ACK
   - 立即重传丢失的包
   - 不等超时

4. 快速恢复(Fast Recovery)
   - 快速重传后
   - ssthresh = cwnd/2
   - cwnd = ssthresh + 3
   - 进入拥塞避免
   - 不回到慢启动

Q2:慢启动为什么叫"慢"?

答案:

虽然是指数增长(1→2→4→8),看起来很快,
但相对于"一开始就全速发送"来说,是慢的。

命名原因:
- 相对概念:相对于"快启动"(不存在)
- 初始谨慎:从1个包开始,很保守
- 避免拥塞:不是一上来就把网络打满

实际上:
初期确实慢:1, 2, 4(很小)
后期很快:1024, 2048, 4096(指数爆炸)

生活比喻:
飞机起飞:
- 开始在跑道慢慢加速(慢启动)
- 速度够了快速升空(拥塞避免)
- 虽然加速快,但相对于"直接起飞",还是"慢"

Q3:拥塞控制和流量控制的区别?

答案:

拥塞控制(Congestion Control):
- 目的:防止网络拥塞
- 对象:整个网络(路由器、链路)
- 控制者:发送方
- 依据:网络状况(丢包、延迟)
- 机制:cwnd(拥塞窗口)
- 算法:慢启动、拥塞避免、快速重传、快速恢复

流量控制(Flow Control):
- 目的:防止接收方过载
- 对象:接收方
- 控制者:接收方
- 依据:接收方缓冲区大小
- 机制:rwnd(接收窗口)
- 算法:滑动窗口

实际发送窗口 = min(cwnd, rwnd)

生活比喻:
流量控制:看朋友杯子大小倒水(接收能力)
拥塞控制:看路况决定车速(网络状况)

🎓 总结

拥塞控制的关键点:

  1. 慢启动:指数增长(1→2→4→8)
  2. 拥塞避免:线性增长(+1每轮)
  3. 快速重传:3个重复ACK立即重传
  4. 快速恢复:不回到慢启动

记忆口诀

慢启动,指数长 📈
拥塞避免加1强 ➕
快速重传遇三重 ⚡
快速恢复不归零 🔄

四字诀

慢(慢启动)
避(拥塞避免)
快(快速重传)
复(快速恢复)

文档创建时间:2025-10-31
作者:AI助手 🤖