知识点编号:008
难度等级:⭐⭐⭐(必掌握)
面试频率:🔥🔥🔥🔥🔥
🎯 一句话总结
半连接队列是"预约区"(握手中),全连接队列是"等位区"(握手完成)!
🤔 两个队列是什么?
客户端连接服务器的过程:
1️⃣ 客户端发送SYN
→ 进入半连接队列(SYN Queue)
2️⃣ 服务器回复SYN+ACK
3️⃣ 客户端发送ACK
→ 从半连接队列移到全连接队列(Accept Queue)
4️⃣ 应用程序调用accept()
→ 从全连接队列取出连接
🎭 生活例子:餐厅就餐 🍽️
半连接队列 = 预约登记处
全连接队列 = 等位区
你打电话预约:
"我要预约,2个人"(第1次握手)
服务员:"好的,您贵姓?"(第2次握手)
→ 你的名字进入预约本(半连接队列)
你:"我姓张"(第3次握手)
→ 预约确认,请到等位区(全连接队列)
服务员叫号:
"张先生,您的桌子准备好了"(accept())
→ 你从等位区进入餐厅(建立连接)
📊 完整流程
客户端 服务器
| |
| ①SYN |
|-------------------->|
| | → 进入半连接队列(SYN_RCVD)
| | [连接1(未完成)]
| |
| ②SYN+ACK |
|<--------------------|
| |
| ③ACK |
|-------------------->|
| | → 从半连接队列移除
| | 移到全连接队列(ESTABLISHED)
| | [连接1(已完成)]
| |
| | → 应用程序accept()
| | 从全连接队列取出
| |
| 开始通信 |
|<====================|
⚠️ 队列满了会怎样?
半连接队列满
场景:SYN Flood攻击
攻击者:疯狂发SYN,不回复ACK
服务器:半连接队列满了!
[SYN1][SYN2][SYN3]...[SYN100](满了!)
新客户端:发送SYN
服务器:❌ 队列满了,丢弃SYN或发送RST
正常用户无法连接!💀
全连接队列满
场景:应用程序accept()太慢
半连接 → 全连接 → 全连接 → 全连接(满了!)
[C1][C2][C3]...[C100]
应用程序:处理太慢,来不及accept()
服务器:全连接队列满了!
新连接完成第3次握手:
服务器:❌ 队列满了
- 方式1:丢弃ACK(客户端会重传)
- 方式2:发送RST(拒绝连接)
💻 Java代码示例
设置队列大小
// 创建ServerSocket时指定backlog(全连接队列大小)
ServerSocket serverSocket = new ServerSocket(8080, 50);
// 端口 ↑backlog=50
// backlog含义:
// - 全连接队列的最大长度
// - 默认值:50(Java)
// - 实际大小还受操作系统限制
System.out.println("服务器启动,backlog=50");
队列满的现象
// 服务器:accept()很慢
ServerSocket serverSocket = new ServerSocket(8080, 10); // 小队列
while (true) {
Socket socket = serverSocket.accept();
System.out.println("接受连接:" + socket);
// 模拟处理很慢
Thread.sleep(5000); // 5秒才处理一个
// 如果客户端连接很快,队列会满!
}
// 客户端:疯狂连接
for (int i = 0; i < 20; i++) {
new Thread(() -> {
try {
Socket socket = new Socket("localhost", 8080);
System.out.println("连接成功");
} catch (IOException e) {
System.out.println("连接失败:" + e.getMessage());
// 队列满时会连接失败
}
}).start();
}
查看队列状态(Linux)
# 查看TCP队列统计
ss -lnt
# 输出:
# State Recv-Q Send-Q Local Address:Port
# LISTEN 0 50 *:8080
# ↑ ↑
# 当前队列大小 队列最大值
# Recv-Q:当前全连接队列中的连接数
# Send-Q:队列最大容量(backlog)
# 查看半连接队列
netstat -s | grep SYN
# 输出:
# 12345 SYNs to LISTEN sockets dropped
#(半连接队列满导致的SYN丢弃)
🐛 常见面试题
Q1:什么是半连接队列和全连接队列?
答案:
半连接队列(SYN Queue):
- 存储处于SYN_RCVD状态的连接
- 已收到SYN,发送了SYN+ACK
- 等待客户端的第3次握手ACK
全连接队列(Accept Queue):
- 存储处于ESTABLISHED状态的连接
- 三次握手已完成
- 等待应用程序accept()取走
流程:
客户端SYN → 半连接队列
客户端ACK → 全连接队列
应用accept() → 取出连接
生活例子:
半连接:预约登记(握手中)
全连接:等位区(握手完成)
accept():叫号入座
Q2:如何防御SYN Flood攻击?
答案:
SYN Flood攻击:
攻击者发送大量SYN,不回复ACK
导致半连接队列满,正常用户无法连接
防御方案:
1. SYN Cookie
- 不分配半连接队列资源
- 在SYN+ACK中编码连接信息
- 收到ACK后验证Cookie
- 验证通过再建立连接
Linux: net.ipv4.tcp_syncookies = 1
2. 增加半连接队列大小
Linux: net.ipv4.tcp_max_syn_backlog = 2048
3. 减少SYN+ACK重传次数
Linux: net.ipv4.tcp_synack_retries = 1
(默认5次,改为1次)
4. 缩短超时时间
快速回收半连接队列资源
5. 使用防火墙/IDS
检测和过滤异常SYN流量
Q3:全连接队列满了怎么办?
答案:
现象:
- 客户端connect()超时或被拒绝
- 服务器日志:Connection refused
- netstat看到队列满
原因:
1. backlog设置太小
2. 应用程序accept()太慢
3. 连接数突增
解决方案:
1. 增大backlog
ServerSocket server = new ServerSocket(port, 1024);
2. 优化应用程序
- accept()后立即交给线程池处理
- 不要在accept()循环中做耗时操作
3. 使用线程池快速accept()
ExecutorService executor = Executors.newFixedThreadPool(100);
while (true) {
Socket socket = serverSocket.accept();
executor.submit(() -> handle(socket));
}
4. 调整系统参数(Linux)
net.core.somaxconn = 1024
(系统级别的队列上限)
🎓 总结
两个队列的关键点:
- 半连接队列:SYN_RCVD状态,握手中
- 全连接队列:ESTABLISHED状态,握手完成
- 队列满:SYN Flood攻击或accept()太慢
- 防御:SYN Cookie、增大队列、快速accept()
记忆口诀:
半连接,握手中 🤝
全连接,握手成 ✅
队列满,拒新客 🚫
快accept,解拥挤 ⚡
文档创建时间:2025-10-31
作者:AI助手 🤖