【大白话说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) CLOSEDSYN_SENT LISTEN 客户端告知服务端自己的初始序列号
    第二次 S → C SYN=1, ACK=1, seq=y(ISN_S), ack=x+1 SYN_SENT LISTENSYN_RCVD 服务端确认收到 SYN,并告知自己的 ISN
    第三次 C → S ACK=1, seq=x+1, ack=y+1 SYN_SENTESTABLISHED SYN_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 → B FIN=1, seq=u ESTABLISHEDFIN_WAIT_1 ESTABLISHED A 不再发送数据,但可继续接收
    第二次 B → A ACK=1, ack=u+1 FIN_WAIT_1 ESTABLISHEDCLOSE_WAIT B 确认收到 A 的关闭请求
    第三次 B → A FIN=1, seq=v FIN_WAIT_2(收到ACK后) CLOSE_WAITLAST_ACK B 也不再发送数据
    第四次 A → B ACK=1, ack=v+1 FIN_WAIT_2TIME_WAIT LAST_ACKCLOSED A 最终确认,等待 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,耗尽文件描述符。

排查命令

bash 复制代码
ss -tan | grep CLOSE_WAIT | wc -l
# 或
netstat -tan | grep CLOSE_WAIT

根因分析

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

解决方案

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

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

    防护手段 配置 原理
    SYN Cookies net.ipv4.tcp_syncookies = 1 SYN 队列满时,用 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_backlog 128 65535 半连接队列长度
    somaxconn 128 65535 全连接队列长度
    tcp_syncookies 0 1 SYN Flood 防护
    tcp_tw_reuse 0 1 复用 TIME_WAIT 端口(出站)
    tcp_timestamps 1 1 tcp_tw_reuse 前置条件
    tcp_fin_timeout 60 30 缩短 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 -s grep SYNs`
大量 TIME_WAIT `ss -tan grep TIME_WAIT`
大量 CLOSE_WAIT `ss -tan grep 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 的连接管理。


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

相关推荐
我叫唧唧波1 小时前
【Java】Java 基础系统学习笔记
java·笔记·学习
好好沉淀1 小时前
Spring @Validated和Validation注解 校验机制完全指南
java·数据库·后端
唐青枫1 小时前
Java RxJava 实战指南:从 Observable、Flowable 到线程切换和背压处理
java
凤山老林2 小时前
SpringBoot + Configuration2 实现配置的实时双向更新
java·spring boot·后端
2601_963869954 小时前
【计算机毕业设计】基于 Spring Boot+Vue的手工体验馆管理系统的设计与实现
java·spring boot·后端
曹牧4 小时前
Java:BeanListHandler
java·数据库·oracle
白鸽(二般)10 小时前
MinIO Java Client API
java·开发语言
软萌萌的111 小时前
Java Spring Boot 修改yml配置&加载顺序规则
java·spring boot·python
IT小盘12 小时前
13-企业Prompt模板-角色任务约束与输出格式
java·前端·prompt