🚰 TCP流量控制:别让对方"噎着"

82 阅读4分钟

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


🎯 一句话总结

流量控制就像倒水,要看对方杯子的大小,别倒太快把水洒了!💧


🤔 什么是流量控制?

问题场景

发送方:疯狂发送数据!📤📤📤📤📤
接收方:等等!我处理不过来了!😰
(接收缓冲区满了,数据丢失!)

流量控制:让发送方根据接收方的处理能力调整发送速度。


🎭 滑动窗口机制

核心概念

接收窗口(Receive Window):
- 接收方告诉发送方:"我还能接收X字节"
- 通过TCP头部的Window字段传递
- 单位:字节
- 大小:0-65535字节(16位)

工作流程:
1. 接收方告诉发送方:我的窗口大小是1000字节
2. 发送方:好的,我一次最多发1000字节
3. 接收方处理了500字节:还能接收1500字节
4. 发送方:继续发送...

📊 流量控制过程

正常情况

发送方                           接收方
  |                               |
  | 数据500字节                    |
  |------------------------------>|
  |                               | (缓冲区剩余:1000-500=500)
  |                               |
  | ACK, Window=500               |
  |<------------------------------|
  |                               |
  | 数据500字节                    |
  |------------------------------>|
  |                               | (缓冲区满了!)
  |                               |
  | ACK, Window=0 ⚠️               |
  |<------------------------------|
  |                               |
  | (停止发送,等待...)            |
  |                               | (处理数据中...)
  |                               |
  | ACK, Window=1000 ✅            |
  |<------------------------------|
  |                               |
  | 继续发送...                    |

窗口探测

问题:如果Window=0的通知丢了怎么办?

发送方:停止发送(Window=0)
接收方:处理完数据,Window=1000
        发送ACK(Window=1000)
        ❌ ACK丢失!

发送方:还以为Window=0,一直等待
接收方:以为对方收到了,也在等待
死锁!💀

解决方案:窗口探测(Window Probe)
发送方:定期发送1字节的探测包
接收方:回复当前Window大小
发送方:收到Window>0,继续发送

💻 Java代码示例

查看接收缓冲区

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

// 获取接收缓冲区大小
int receiveBufferSize = socket.getReceiveBufferSize();
System.out.println("接收缓冲区: " + receiveBufferSize);

// 设置接收缓冲区大小
socket.setReceiveBufferSize(65536); // 64KB

// 获取发送缓冲区大小
int sendBufferSize = socket.getSendBufferSize();
System.out.println("发送缓冲区: " + sendBufferSize);

// 设置发送缓冲区大小
socket.setSendBufferSize(65536);

流量控制演示

public class FlowControlDemo {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8888);
        
        // 设置小接收缓冲区(模拟慢接收)
        Socket server = serverSocket.accept();
        server.setReceiveBufferSize(1024); // 只有1KB
        
        InputStream in = server.getInputStream();
        byte[] buffer = new byte[100];
        
        while (true) {
            int len = in.read(buffer);
            if (len == -1) break;
            
            // 模拟慢处理
            Thread.sleep(100);
            
            System.out.println("接收了 " + len + " 字节");
            System.out.println("可用空间: " + in.available());
        }
    }
}

🐛 常见面试题

Q1:什么是TCP流量控制?如何实现?

答案:

流量控制是TCP协议中防止发送方发送速度过快,导致接收方
来不及处理的机制。

实现机制:滑动窗口
1. 接收方通过TCP头部的Window字段告知可接收字节数
2. 发送方根据Window大小控制发送速度
3. 接收方处理数据后,更新Window大小
4. Window=0时,发送方停止发送
5. 定期发送窗口探测包,避免死锁

目的:
- 防止接收方缓冲区溢出
- 根据接收方能力调整发送速度
- 保证数据不丢失

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

答案:

流量控制(Flow Control):
- 目的:防止接收方过载
- 对象:点对点(发送方→接收方)
- 机制:接收窗口(Window)
- 由接收方控制

拥塞控制(Congestion Control):
- 目的:防止网络拥塞
- 对象:整个网络
- 机制:拥塞窗口(cwnd)+ 算法
- 由发送方控制

生活比喻:
流量控制:看对方杯子大小倒水(接收能力)
拥塞控制:看路况开车速度(网络状况)

Q3:Window=0时会怎样?

答案:

Window=0的含义:
接收方缓冲区满了,暂时无法接收数据

发送方行为:
1. 停止发送数据
2. 启动持续定时器
3. 定期发送窗口探测包(1字节数据)
4. 等待接收方Window>0

接收方行为:
1. 处理缓冲区数据
2. 有空间后发送Window>0的ACK
3. 恢复正常接收

为什么需要窗口探测?
防止Window>0的ACK丢失导致死锁:
- 发送方以为Window=0,停止发送
- 接收方以为对方知道Window>0,等待数据
- 双方都在等待,死锁!

窗口探测避免了这个问题。

🎓 总结

流量控制的关键点:

  1. 目的:防止接收方过载
  2. 机制:滑动窗口(Window字段)
  3. Window=0:停止发送,启动探测
  4. 区别拥塞控制:一个看接收方,一个看网络

记忆口诀

接收窗口告诉你 📢
能收多少别乱发 ⚠️
Window为零要停下 🛑
窗口探测防死锁 🔍