知识点编号:011
难度等级:⭐⭐(必掌握)
面试频率:🔥🔥🔥🔥🔥
🎯 一句话总结
TCP四次挥手就像打电话结束前的礼貌告别,双方都要确认挂断!
🎭 四次挥手的四个步骤
第一步:FIN(我要挂了)
客户端发送:FIN=1, seq=u "我没话说了,要关闭连接了"
第二步:ACK(知道了,等等)
服务器回复:ACK=1, ack=u+1 "我知道了,但我还有话要说"
第三步:FIN(我也要挂了)
服务器发送:FIN=1, ACK=1, seq=w, ack=u+1 "我也说完了,可以关闭了"
第四步:ACK(好的,拜拜)
客户端确认:ACK=1, seq=u+1, ack=w+1 "好的,拜拜"
📊 图解
客户端 服务器
| |
| ①FIN seq=100 |
|------------------------------->|
| (FIN_WAIT_1) | (CLOSE_WAIT)
| |
| ②ACK ack=101 |
|<-------------------------------|
| (FIN_WAIT_2) |
| |
| ... 服务器继续发送数据 ... |
|<-------------------------------|
| |
| ③FIN seq=200,ack=101 |
|<-------------------------------|
| (TIME_WAIT) | (LAST_ACK)
| |
| ④ACK seq=101,ack=201 |
|------------------------------->|
| | (CLOSED)
| (等待2MSL...) |
| |
| (CLOSED) |
💡 为什么是四次?三次不行吗?
原因:TCP是全双工通信!
三次握手时:双方都还没开始发数据,可以同时准备
第二次握手合并了:SYN(我也要连接)+ ACK(收到你的SYN)
四次挥手时:一方想关闭,但另一方可能还有数据要发
第二次挥手:ACK(我知道了)
... 继续发送未完成的数据 ...
第三次挥手:FIN(我的数据发完了)
不能合并第二、三次挥手!
生活比喻:
打电话结束:
你:"我要挂了"(第一次)
朋友:"等等!我还有话说!"(第二次)
朋友:"好了,我说完了,挂吧"(第三次)
你:"好的,拜拜"(第四次)
如果合并第二、三次:
你:"我要挂了"
朋友:"好,我也挂了"
你心想:"我话还没说完呢!"😱
⏰ TIME_WAIT状态:最关键的状态
什么是TIME_WAIT?
主动关闭方在发送最后的ACK后,进入TIME_WAIT状态,等待2MSL(1-4分钟)。
为什么要等2MSL?
原因1:确保最后的ACK被收到
如果最后的ACK丢失:
- 服务器会重传FIN
- 客户端要能接收到并重新回复ACK
- 需要等待足够的时间确保网络中的数据包都消失
原因2:防止旧连接的数据包干扰新连接
如果不等待:
- 旧连接的延迟数据包可能还在网络中
- 新连接可能使用相同的端口
- 旧数据包会被误认为是新连接的数据 ❌
等待2MSL后:
- 旧数据包肯定已经消失 ✅
💔 CLOSE_WAIT状态:开发者的噩梦
什么是CLOSE_WAIT?
被动关闭方收到FIN后,发送ACK,进入CLOSE_WAIT状态。
CLOSE_WAIT过多的原因
❌ 根本原因:应用程序没有调用socket.close()!
常见错误代码:
Socket socket = new Socket("localhost", 8080);
// ... 使用socket ...
// 忘记调用socket.close()了!
客户端关闭连接(发送FIN)
服务器收到FIN,进入CLOSE_WAIT
但是应用程序没有关闭socket
导致一直停留在CLOSE_WAIT状态!😱
解决方案
// ✅ 正确做法:使用try-with-resources
try (Socket socket = new Socket("localhost", 8080)) {
// 使用socket
} // 自动调用close()
// ✅ 或者使用finally
Socket socket = null;
try {
socket = new Socket("localhost", 8080);
// 使用socket
} finally {
if (socket != null) {
socket.close();
}
}
🐛 常见面试题
Q1:为什么建立连接是三次握手,关闭连接是四次挥手?
答案:
建立连接时:双方都没有数据要发
- 服务器可以同时发送SYN和ACK(第二次握手)
- 所以只需要3次
关闭连接时:一方想关闭,但另一方可能还有数据要发
- 第二次挥手:ACK(知道了)
- ... 发送剩余数据 ...
- 第三次挥手:FIN(发完了)
- 不能合并,所以需要4次
Q2:TIME_WAIT过多怎么办?
答案:
原因:短连接太多,每个连接关闭后都要等待1-4分钟
解决方案:
1. ✅ 使用长连接(HTTP Keep-Alive)
2. ✅ 使用连接池
3. ✅ 让服务器主动关闭(服务器端口固定,不怕占用)
4. ⚠️ 调整系统参数(谨慎使用):
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1(NAT环境慎用!)
Q3:CLOSE_WAIT过多怎么办?
答案:
原因:应用程序没有正确关闭socket
排查步骤:
1. 查看CLOSE_WAIT数量:
netstat -an | grep CLOSE_WAIT | wc -l
2. 检查代码:
- 是否忘记调用socket.close()
- 异常处理是否正确
- 是否使用了try-with-resources
3. 修复代码:
确保在finally块或try-with-resources中关闭socket
🎓 总结
四次挥手是TCP连接终止的过程,记住:
- 第一次:FIN(客户端→服务器)"我要关闭了"
- 第二次:ACK(服务器→客户端)"知道了"
- 第三次:FIN(服务器→客户端)"我也要关闭了"
- 第四次:ACK(客户端→服务器)"好的"
关键状态:
- TIME_WAIT:主动关闭方,等待2MSL
- CLOSE_WAIT:被动关闭方,应用程序应该关闭socket
生活比喻:
打电话结束:
你:"我要挂了"
朋友:"知道了,等等"
朋友:"好,我也挂了"
你:"拜拜"
文档创建时间:2025-10-31