知识点编号: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/2
→ cwnd = 1
→ 慢启动
快速恢复:
丢包 → ssthresh = cwnd/2
→ cwnd = 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个重复ACK)
7:快速重传+快速恢复
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→2→4→8)
- 拥塞避免:线性增长(+1每轮)
- 快速重传:3个重复ACK立即重传
- 快速恢复:不回到慢启动
记忆口诀:
慢启动,指数长 📈
拥塞避免加1强 ➕
快速重传遇三重 ⚡
快速恢复不归零 🔄
四字诀:
慢(慢启动)
避(拥塞避免)
快(快速重传)
复(快速恢复)
文档创建时间:2025-10-31
作者:AI助手 🤖