ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

【大白话说Java面试题 第214题】【10_网络协议篇】第5题:说一下 TCP 协议的三次握手和四次挥手

2026/8/4 3:56:14 拓冰建站 浏览量
【大白话说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 后等待 ACKSYN 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 → SSYN=1, seq=x(ISN_C)CLOSEDSYN_SENTLISTEN客户端告知服务端自己的初始序列号
    第二次S → CSYN=1, ACK=1, seq=y(ISN_S),ack=x+1SYN_SENTLISTENSYN_RCVD服务端确认收到 SYN,并告知自己的 ISN
    第三次C → SACK=1, seq=x+1, ack=y+1SYN_SENTESTABLISHEDSYN_RCVDESTABLISHED客户端确认收到 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 → BFIN=1, seq=uESTABLISHEDFIN_WAIT_1ESTABLISHEDA 不再发送数据,但可继续接收
    第二次B → AACK=1, ack=u+1FIN_WAIT_1ESTABLISHEDCLOSE_WAITB 确认收到 A 的关闭请求
    第三次B → AFIN=1, seq=vFIN_WAIT_2(收到ACK后)CLOSE_WAITLAST_ACKB 也不再发送数据
    第四次A → BACK=1, ack=v+1FIN_WAIT_2TIME_WAITLAST_ACKCLOSEDA 最终确认,等待 2MSL 后关闭
  • 3.2 为什么是四次挥手?

    因为 TCP 是全双工的,A 发 FIN 只表示"我不再发送数据了",不代表 B 也立刻没有数据要发。B 收到 FIN 后:

    1. 内核自动回 ACK 确认(第二次挥手,立即响应);
    2. 应用层可能还有数据未发送,需等待处理完并调用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: ACK
  • 3.4 TIME_WAIT 状态的深度解析

    为什么需要 2MSL?

    1. 确保最后一个 ACK 到达:若 ACK 丢失,被动关闭方会重传 FIN,主动方需能在TIME_WAIT期间收到并重发 ACK;
    2. 防止旧连接报文干扰新连接:等待 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端口用于出站连接(作为客户端),不能用于服务端监听端口。服务端应通过长连接和连接池解决。

4. CLOSE_WAIT 堆积:应用层 Bug 的"照妖镜"

CLOSE_WAIT是被动关闭方收到 FIN 后的状态,表示"我已收到你的关闭请求,但我还有数据要发/我还没 close()"。如果应用层迟迟不调用close(),连接将永久停留在CLOSE_WAIT,耗尽文件描述符。

排查命令

ss-tan|grepCLOSE_WAIT|wc-l# 或netstat-tan|grepCLOSE_WAIT

根因分析

  1. 应用层未关闭 Socket:代码中InputStream.read()返回 -1 后未调用socket.close()
  2. 线程池阻塞:处理请求的线程被阻塞(如数据库查询慢),无法及时释放连接;
  3. 连接池配置不当:连接池最大连接数过小,新请求排队等待,旧连接无法释放。

解决方案

  • 确保在finally块中关闭 Socket;
  • 使用 try-with-resources 自动关闭;
  • 监控线程池活跃线程数,设置合理的超时时间。
5. 生产环境避坑指南
  • 5.1 SYN Flood 攻击防护

    攻击者发送大量伪造源 IP 的 SYN 报文,占满半连接队列,导致正常连接无法建立。

    防护手段配置原理
    SYN Cookiesnet.ipv4.tcp_syncookies = 1SYN 队列满时,用 Cookie 机制验证客户端合法性,不分配资源
    增大半连接队列tcp_max_syn_backlog = 65535提高容量
    缩短 SYN 超时tcp_synack_retries = 2减少半连接占用时间
    云厂商 DDoS 防护-流量清洗,过滤恶意 SYN
  • 5.2 连接建立慢排查

    现象可能原因排查手段
    连接超时服务端未响应 SYNtcpdump抓包,检查防火墙/安全组
    连接成功但无法通信全连接队列溢出ss -lnt查看Recv-Q是否超过Send-Q
    偶发超时半连接队列溢出查看SYNs to LISTEN sockets dropped计数
  • 5.3 内核参数调优速查表

    参数默认值建议值作用
    tcp_max_syn_backlog12865535半连接队列长度
    somaxconn12865535全连接队列长度
    tcp_syncookies01SYN Flood 防护
    tcp_tw_reuse01复用 TIME_WAIT 端口(出站)
    tcp_timestamps11tcp_tw_reuse 前置条件
    tcp_fin_timeout6030缩短 FIN_WAIT_2 超时
6. 面试官追问与高分回答模板
  • 追问 1:“画一下 TCP 三次握手的状态转换图?”

    低分回答:“客户端发 SYN,服务端回 SYN+ACK,客户端再发 ACK。”(没有状态转换)

    高分回答

    "三次握手涉及 5 个状态转换:

    1. 客户端从CLOSED发送 SYN 后进入SYN_SENT
    2. 服务端从LISTEN收到 SYN 后进入SYN_RCVD,回复 SYN+ACK;
    3. 客户端收到 SYN+ACK 后进入ESTABLISHED,发送 ACK;
    4. 服务端收到 ACK 后从SYN_RCVD进入ESTABLISHED
      关键点:SYN_RCVD是服务端的中间状态,用于等待最终 ACK。如果没有这个中间状态(两次握手),服务端无法区分有效 SYN 和历史重发,会直接建连分配资源,造成浪费。"
  • 追问 2:“为什么是三次握手,不是两次?”

    低分回答:“为了防止重复连接。”(太笼统)

    高分回答

    "核心原因有三层:

    1. 防止历史连接初始化(首要):若客户端 SYN 因延迟滞留,超时重发后成功建连并释放,旧 SYN 到达服务端。两次握手下服务端直接建连,但客户端已无此状态,导致服务端维护无效连接。三次握手通过最终 ACK 确认闭环,客户端会 RST 这个失效请求。
    2. 同步双方 ISN:TCP 依赖序列号保证可靠传输,双方必须互相确认对方的 ISN。两次握手只能保证一方的 ISN 被确认。
    3. 避免资源浪费:两次握手下服务端每收到一个 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 有两个作用:

    1. 确保最后一个 ACK 到达:若 ACK 丢失,被动方重传 FIN,主动方需能重发 ACK;
    2. 防止旧连接报文干扰新连接:等待网络中旧报文全部消亡。
      大量 TIME_WAIT 的解决方案:
    • 开启tcp_tw_reuse+tcp_timestamps(仅出站连接);
    • 改用长连接(HTTP Keep-Alive)减少短连接数量;
    • 使用连接池;
    • 多源 IP 绑定扩大可用端口范围。"
  • 追问 5:“服务端出现大量 CLOSE_WAIT,怎么排查?”

    低分回答:“应用层没关闭连接。”(没有排查手段)

    高分回答

    "CLOSE_WAIT是被动关闭方收到 FIN 后等待应用close()的状态。大量堆积说明应用层未及时释放连接。
    排查步骤:

    1. ss -tan | grep CLOSE_WAIT | wc -l确认数量;
    2. 检查应用代码:是否在read()返回 -1 后调用了close()
    3. 检查线程池:是否有线程被阻塞(如慢 SQL),导致无法及时处理连接释放;
    4. 检查连接池配置:最大连接数是否过小。
      根因通常是应用层 Bug(未关闭 Socket)或业务逻辑阻塞。"
  • 追问 6:“三次握手过程中,如果第三次 ACK 丢了会怎样?”

    高分回答

    "第三次 ACK 丢失后:

    1. 服务端:仍处于SYN_RCVD状态,启动重传定时器,超时后重传 SYN+ACK(默认重试 5 次,间隔指数退避);
    2. 客户端:已认为连接建立(ESTABLISHED),可能开始发送数据。如果数据报文到达服务端,服务端发现不是期望的 ACK,可能丢弃或回复 RST;
    3. 如果客户端发送的数据报文中带有 ACK 标志且确认号正确,服务端可将其视为有效的第三次握手,直接建立连接并接收数据。
      所以第三次 ACK 丢失不一定会导致连接失败,客户端的数据报文可能’救场’。"
7. 方案选型速查表
问题场景排查命令/参数解决方案
连接建立慢/超时tcpdump+ss -lnt检查防火墙、调整tcp_max_syn_backlog
SYN Flood 攻击`netstat -sgrep SYNs`
大量 TIME_WAIT`ss -tangrep TIME_WAIT`
大量 CLOSE_WAIT`ss -tangrep CLOSE_WAIT`
全连接队列溢出ss -lntRecv-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 的连接管理。


觉得对您有帮助,麻烦点点关注啦,您的关注是我创作的最大动力~ 🎯