【大白话说Java面试题 第214题】【10_网络协议篇】第5题:说一下 TCP 协议的三次握手和四次挥手
📌PDF:大白话说Java面试题 — 10_网络协议篇
第5题:说一下 TCP 协议的三次握手和四次挥手
📚回答:
- 核心考点: TCP 三次握手和四次挥手是网络面试的"必考题",大厂面试官不会满足于流程背诵,而是深入考察状态转换图(11 种状态的完整迁移路径)、半连接队列与全连接队列(SYN Queue / Accept Queue 的容量与溢出处理)、为什么不是两次/四次(历史连接、ISN 同步、资源浪费的三层分析)、四次挥手的 ACK 和 FIN 为什么通常分开发(全双工关闭语义)、TIME_WAIT 的 2MSL 精确计算、以及CLOSE_WAIT 堆积的排查与根因。面试官真正想判断的是:你是否能从协议设计原理和工程实践两个维度,给出有深度的分析。
1. TCP 状态机全景图
TCP 连接生命周期涉及11 种状态,理解状态迁移是排查网络问题的核心能力:
+--------+ 主动打开 | CLOSED | 被动打开 +--------->| |<-----------+ | +--------+ | | | | | | 发送SYN | | 监听 | | v v | | +------------+ | | | LISTEN | | | +------------+ | | | | | | 收到SYN | | v | | +------------+ | +------| SYN_SENT | | | +------------+ | | | | | 收到SYN+ACK | 收到SYN | | | 发送SYN+ACK | | v v | +------------+ +------------+ +----->| ESTABLISHED|<----| SYN_RCVD | +------------+ +------------+ | 数据传输 | | | 主动关闭 | | 被动关闭 +----------+ +----------+ | | | v v v +------------+ +------------+ +------------+ | FIN_WAIT_1 | | CLOSE_WAIT | | LAST_ACK | +------------+ +------------+ +------------+ | | | 收到ACK | 收到FIN | 收到ACK | | | | v v v +------------+ +------------+ +------------+ | FIN_WAIT_2 | | CLOSED | | CLOSED | +------------+ +------------+ +------------+ | 收到FIN | v +------------+ | TIME_WAIT | <-- 等待 2MSL +------------+ | v +------------+ | CLOSED | +------------+关键状态说明:
| 状态 | 含义 | 典型问题 |
|---|---|---|
| SYN_SENT | 客户端发送 SYN 后等待 SYN+ACK | 连接超时,服务端未响应 |
| SYN_RCVD | 服务端收到 SYN 后等待 ACK | SYN Flood 攻击时大量堆积 |
| ESTABLISHED | 连接建立,可传输数据 | 正常通信状态 |
| FIN_WAIT_1 | 主动关闭方发送 FIN 后等待 ACK | 对端未响应 FIN |
| FIN_WAIT_2 | 收到 ACK 后等待对端 FIN | 对端未关闭连接,长时间停留 |
| CLOSE_WAIT | 被动关闭方收到 FIN 后等待应用 close() | 应用层 Bug 导致堆积 |
| LAST_ACK | 被动关闭方发送 FIN 后等待 ACK | 正常过渡状态 |
| TIME_WAIT | 主动关闭方等待 2MSL | 大量堆积耗尽端口 |
| CLOSING | 双方同时关闭(罕见) | 同时发送 FIN |
2. 三次握手:建立连接
2.1 完整流程与状态转换
步骤 方向 报文内容 发送方状态 接收方状态 核心确认 第一次 C → S SYN=1, seq=x(ISN_C)CLOSED→SYN_SENTLISTEN客户端告知服务端自己的初始序列号 第二次 S → C SYN=1, ACK=1, seq=y(ISN_S),ack=x+1SYN_SENTLISTEN→SYN_RCVD服务端确认收到 SYN,并告知自己的 ISN 第三次 C → S ACK=1, seq=x+1, ack=y+1SYN_SENT→ESTABLISHEDSYN_RCVD→ESTABLISHED客户端确认收到 SYN+ACK,服务端确认闭环完成 第三次握手可以携带数据吗?可以。RFC 9293 允许第三次握手的 ACK 携带应用数据,但接收端在确认连接有效前不能交付给应用。如果第三次 ACK 丢失,但随后发送的携带数据且带 ACK 标志的报文到达,服务端可将其视为有效的第三次握手确认。
2.2 为什么是三次握手?(三层分析)
原因一:防止历史连接初始化(首要原因)
客户端发送 SYN1(seq=90)后因网络延迟滞留,超时后重发 SYN2(seq=100)并成功建连、传输、释放。此时延迟的 SYN1 到达服务端:
- 两次握手:服务端收到 SYN1 后直接建连分配资源,但客户端已无此连接状态,导致服务端维护无效连接;
- 三次握手:服务端回复 SYN+ACK 后等待最终 ACK,客户端发现 ack=91 而非期望的 101,发送 RST 终止连接,服务端释放资源。
原因二:同步双方初始序列号(ISN)
TCP 依赖序列号保证可靠传输(去重、排序、确认)。双方必须互相确认对方的 ISN:
- 客户端发送 ISN_C,需要服务端 ACK 确认;
- 服务端发送 ISN_S,需要客户端 ACK 确认。
两次握手只能保证一方的 ISN 被确认,无法保证双向同步。四次握手可以,但第二步和第三步可合并为一步(SYN+ACK),因此三次是最小可靠次数。
原因三:避免资源浪费
两次握手下,服务端每收到一个 SYN 就必须建连,无法区分是有效请求还是历史重发。网络拥堵时客户端重复发送 SYN,服务端会建立多个冗余无效连接,造成资源浪费。三次握手通过最终 ACK 确认闭环,服务端只在确认有效后才分配资源。
握手次数 能否防止历史连接 能否同步双方 ISN 资源浪费风险 结论 两次 ❌ 不能 ❌ 只能单向 ⚠️ 高 不可用 三次 ✅ 能 ✅ 双向 ✅ 低 最优 四次 ✅ 能 ✅ 双向 ✅ 低 冗余,第三步可合并 2.3 半连接队列与全连接队列
服务端在握手过程中维护两个队列,是排查连接建立慢、SYN Flood 攻击的关键:
队列 保存状态 触发条件 溢出后果 半连接队列(SYN Queue) SYN_RCVD状态的连接收到 SYN,回复 SYN+ACK 后 SYN Flood 攻击时满,新连接无法建立 全连接队列(Accept Queue) ESTABLISHED状态的连接收到 ACK,三次握手完成 应用 accept()不及时时满,客户端认为连接成功但无法通信内核参数调优:
net.ipv4.tcp_max_syn_backlog:半连接队列长度;net.core.somaxconn:全连接队列长度(受listen()的backlog参数限制,取两者较小值);net.ipv4.tcp_syncookies:SYN 队列满时启用 Cookie 机制,不分配资源验证客户端合法性。
3. 四次挥手:断开连接
3.1 完整流程与状态转换
TCP 是全双工通信,两个方向的数据传输需要分别关闭、分别确认。
步骤 方向 报文内容 发送方状态 接收方状态 核心语义 第一次 A → B FIN=1, seq=uESTABLISHED→FIN_WAIT_1ESTABLISHEDA 不再发送数据,但可继续接收 第二次 B → A ACK=1, ack=u+1FIN_WAIT_1ESTABLISHED→CLOSE_WAITB 确认收到 A 的关闭请求 第三次 B → A FIN=1, seq=vFIN_WAIT_2(收到ACK后)CLOSE_WAIT→LAST_ACKB 也不再发送数据 第四次 A → B ACK=1, ack=v+1FIN_WAIT_2→TIME_WAITLAST_ACK→CLOSEDA 最终确认,等待 2MSL 后关闭 3.2 为什么是四次挥手?
因为 TCP 是全双工的,A 发 FIN 只表示"我不再发送数据了",不代表 B 也立刻没有数据要发。B 收到 FIN 后:
- 内核自动回 ACK 确认(第二次挥手,立即响应);
- 应用层可能还有数据未发送,需等待处理完并调用
close()后才发 FIN(第三次挥手)。
"回 ACK"和"发 FIN"的触发时机解耦,通常无法合并,因此需要四次。
3.3 什么情况下可以三次挥手?
当 B 收到 FIN 时恰好没有待发数据,且应用层立即调用
close(),同时延迟确认(Delayed ACK)机制允许 ACK 等待合并时,第二次的 ACK 和第三次的 FIN 可合并为一个FIN+ACK报文,抓包上呈现三次交互。正常四次挥手: 优化三次挥手: A → B: FIN A → B: FIN B → A: ACK B → A: FIN+ACK(合并) B → A: FIN A → B: ACK A → B: ACK3.4 TIME_WAIT 状态的深度解析
为什么需要 2MSL?
- 确保最后一个 ACK 到达:若 ACK 丢失,被动关闭方会重传 FIN,主动方需能在
TIME_WAIT期间收到并重发 ACK; - 防止旧连接报文干扰新连接:等待 2MSL 确保网络中所有旧连接的报文全部消亡,避免新连接(可能复用相同四元组)收到旧报文导致数据混乱。
为什么是 2MSL 而不是 1MSL?因为最坏情况下,最后一个 ACK 从 A 到 B 需要 1MSL,B 重传的 FIN 从 B 到 A 又需要 1MSL,总共 2MSL 才能覆盖这个往返周期。
生产隐患与解决方案:
问题 根因 解决方案 大量 TIME_WAIT耗尽端口高并发短连接(HTTP/1.0) 开启 tcp_tw_reuse+tcp_timestamps;改用长连接/连接池TIME_WAIT导致端口复用失败四元组(源IP、源端口、目的IP、目的端口)唯一标识连接 多源IP绑定、扩大 ephemeral 端口范围 注意:
tcp_tw_reuse只能复用TIME_WAIT端口用于出站连接(作为客户端),不能用于服务端监听端口。服务端应通过长连接和连接池解决。- 确保最后一个 ACK 到达:若 ACK 丢失,被动关闭方会重传 FIN,主动方需能在
4. CLOSE_WAIT 堆积:应用层 Bug 的"照妖镜"
CLOSE_WAIT是被动关闭方收到 FIN 后的状态,表示"我已收到你的关闭请求,但我还有数据要发/我还没 close()"。如果应用层迟迟不调用close(),连接将永久停留在CLOSE_WAIT,耗尽文件描述符。
排查命令:
ss-tan|grepCLOSE_WAIT|wc-l# 或netstat-tan|grepCLOSE_WAIT根因分析:
- 应用层未关闭 Socket:代码中
InputStream.read()返回 -1 后未调用socket.close(); - 线程池阻塞:处理请求的线程被阻塞(如数据库查询慢),无法及时释放连接;
- 连接池配置不当:连接池最大连接数过小,新请求排队等待,旧连接无法释放。
解决方案:
- 确保在
finally块中关闭 Socket; - 使用 try-with-resources 自动关闭;
- 监控线程池活跃线程数,设置合理的超时时间。
5. 生产环境避坑指南
5.1 SYN Flood 攻击防护
攻击者发送大量伪造源 IP 的 SYN 报文,占满半连接队列,导致正常连接无法建立。
防护手段 配置 原理 SYN Cookies net.ipv4.tcp_syncookies = 1SYN 队列满时,用 Cookie 机制验证客户端合法性,不分配资源 增大半连接队列 tcp_max_syn_backlog = 65535提高容量 缩短 SYN 超时 tcp_synack_retries = 2减少半连接占用时间 云厂商 DDoS 防护 - 流量清洗,过滤恶意 SYN 5.2 连接建立慢排查
现象 可能原因 排查手段 连接超时 服务端未响应 SYN tcpdump抓包,检查防火墙/安全组连接成功但无法通信 全连接队列溢出 ss -lnt查看Recv-Q是否超过Send-Q偶发超时 半连接队列溢出 查看 SYNs to LISTEN sockets dropped计数5.3 内核参数调优速查表
参数 默认值 建议值 作用 tcp_max_syn_backlog128 65535 半连接队列长度 somaxconn128 65535 全连接队列长度 tcp_syncookies0 1 SYN Flood 防护 tcp_tw_reuse0 1 复用 TIME_WAIT 端口(出站) tcp_timestamps1 1 tcp_tw_reuse 前置条件 tcp_fin_timeout60 30 缩短 FIN_WAIT_2 超时
6. 面试官追问与高分回答模板
追问 1:“画一下 TCP 三次握手的状态转换图?”
低分回答:“客户端发 SYN,服务端回 SYN+ACK,客户端再发 ACK。”(没有状态转换)
高分回答:
"三次握手涉及 5 个状态转换:
- 客户端从
CLOSED发送 SYN 后进入SYN_SENT; - 服务端从
LISTEN收到 SYN 后进入SYN_RCVD,回复 SYN+ACK; - 客户端收到 SYN+ACK 后进入
ESTABLISHED,发送 ACK; - 服务端收到 ACK 后从
SYN_RCVD进入ESTABLISHED。
关键点:SYN_RCVD是服务端的中间状态,用于等待最终 ACK。如果没有这个中间状态(两次握手),服务端无法区分有效 SYN 和历史重发,会直接建连分配资源,造成浪费。"
- 客户端从
追问 2:“为什么是三次握手,不是两次?”
低分回答:“为了防止重复连接。”(太笼统)
高分回答:
"核心原因有三层:
- 防止历史连接初始化(首要):若客户端 SYN 因延迟滞留,超时重发后成功建连并释放,旧 SYN 到达服务端。两次握手下服务端直接建连,但客户端已无此状态,导致服务端维护无效连接。三次握手通过最终 ACK 确认闭环,客户端会 RST 这个失效请求。
- 同步双方 ISN:TCP 依赖序列号保证可靠传输,双方必须互相确认对方的 ISN。两次握手只能保证一方的 ISN 被确认。
- 避免资源浪费:两次握手下服务端每收到一个 SYN 就必须建连,无法区分有效请求和历史重发,网络拥堵时会产生大量冗余连接。"
追问 3:“四次挥手为什么不是三次?什么情况下可以是三次?”
低分回答:“因为 TCP 是全双工的。”(没有解释清楚)
高分回答:
“TCP 是全双工,两个方向需分别关闭。被动关闭方收到 FIN 后,内核自动回 ACK(第二次),但应用层可能还有数据未发送,需等待
close()后才发 FIN(第三次)。'回 ACK’和’发 FIN’触发时机解耦,通常无法合并,所以是四次。
三次挥手的条件是:被动关闭方收到 FIN 时恰好无待发数据,应用层立即close(),且延迟 ACK 允许合并时,ACK 和 FIN 可合并为FIN+ACK,呈现三次交互。”追问 4:“TIME_WAIT 状态的作用是什么?大量 TIME_WAIT 怎么解决?”
低分回答:“等待 2MSL,防止报文干扰。”(没有提解决方案)
高分回答:
"TIME_WAIT 等待 2MSL 有两个作用:
- 确保最后一个 ACK 到达:若 ACK 丢失,被动方重传 FIN,主动方需能重发 ACK;
- 防止旧连接报文干扰新连接:等待网络中旧报文全部消亡。
大量 TIME_WAIT 的解决方案:
- 开启
tcp_tw_reuse+tcp_timestamps(仅出站连接); - 改用长连接(HTTP Keep-Alive)减少短连接数量;
- 使用连接池;
- 多源 IP 绑定扩大可用端口范围。"
追问 5:“服务端出现大量 CLOSE_WAIT,怎么排查?”
低分回答:“应用层没关闭连接。”(没有排查手段)
高分回答:
"
CLOSE_WAIT是被动关闭方收到 FIN 后等待应用close()的状态。大量堆积说明应用层未及时释放连接。
排查步骤:ss -tan | grep CLOSE_WAIT | wc -l确认数量;- 检查应用代码:是否在
read()返回 -1 后调用了close(); - 检查线程池:是否有线程被阻塞(如慢 SQL),导致无法及时处理连接释放;
- 检查连接池配置:最大连接数是否过小。
根因通常是应用层 Bug(未关闭 Socket)或业务逻辑阻塞。"
追问 6:“三次握手过程中,如果第三次 ACK 丢了会怎样?”
高分回答:
"第三次 ACK 丢失后:
- 服务端:仍处于
SYN_RCVD状态,启动重传定时器,超时后重传 SYN+ACK(默认重试 5 次,间隔指数退避); - 客户端:已认为连接建立(
ESTABLISHED),可能开始发送数据。如果数据报文到达服务端,服务端发现不是期望的 ACK,可能丢弃或回复 RST; - 如果客户端发送的数据报文中带有 ACK 标志且确认号正确,服务端可将其视为有效的第三次握手,直接建立连接并接收数据。
所以第三次 ACK 丢失不一定会导致连接失败,客户端的数据报文可能’救场’。"
- 服务端:仍处于
7. 方案选型速查表
| 问题场景 | 排查命令/参数 | 解决方案 |
|---|---|---|
| 连接建立慢/超时 | tcpdump+ss -lnt | 检查防火墙、调整tcp_max_syn_backlog |
| SYN Flood 攻击 | `netstat -s | grep SYNs` |
| 大量 TIME_WAIT | `ss -tan | grep TIME_WAIT` |
| 大量 CLOSE_WAIT | `ss -tan | grep CLOSE_WAIT` |
| 全连接队列溢出 | ss -lnt看Recv-Q | 增大somaxconn+ 应用及时accept() |
| 半连接队列溢出 | netstat -s看 dropped | 增大tcp_max_syn_backlog+tcp_syncookies |
💡面试官想要的满分总结:
TCP 三次握手和四次挥手的本质不是"发了几个包",而是状态机的精确状态转换和全双工通信的优雅管理。
三次握手的核心目的:通过
SYN_RCVD中间状态防止历史连接初始化,同步双方 ISN,避免资源浪费。服务端维护的半连接队列(SYN Queue)和全连接队列(Accept Queue)是排查连接建立问题的关键抓手。四次挥手的核心原因:TCP 全双工,两个方向需分别关闭。被动关闭方的 ACK 和 FIN 通常分开发,因为 ACK 是内核自动响应,FIN 需等待应用层
close()。只有在无待发数据 + 延迟 ACK 合并时,才可能呈现三次挥手。TIME_WAIT的 2MSL 不是多余等待,而是确保 ACK 到达和旧报文消亡的必要机制。高并发短连接场景下需通过
tcp_tw_reuse+ 长连接缓解。CLOSE_WAIT则是应用层 Bug 的"照妖镜",大量堆积时优先排查代码是否及时close()。最后记住:面试中画出完整的状态转换图,比背诵流程更有说服力。理解每个状态的存在意义,才能真正掌握 TCP 的连接管理。
觉得对您有帮助,麻烦点点关注啦,您的关注是我创作的最大动力~ 🎯