TCP 三次握手和四次挥手?

2025年 阅读约 12 分钟 面试指南 · 计算机网络

深入解析TCP三次握手和四次挥手:SYN/ACK/FIN标志位、状态转换图、为什么是三次握手不是两次、TIME_WAIT状态的作用、常见面试题,附面试模拟问答。

一句话总结

三次握手:客户端 → SYN → 服务端 → SYN+ACK → 客户端 → ACK → 服务端,建立双向连接。为什么是三次?防止已失效的连接请求到达服务端导致错误(如果两次握手,服务端收到过期 SYN 就建立连接浪费资源)。四次挥手:客户端 → FIN → 服务端 → ACK → 服务端 → FIN → 客户端 → ACK → 服务端。多一次是因为 TCP 是全双工,一方关闭只表示不再发数据,还能收数据TIME_WAIT(2MSL)确保最后一个 ACK 能重传,让旧连接报文在网络中消失。

初级理解

三次握手过程

# 三次握手(Three-Way Handshake) # 第一次:客户端 → 服务端(SYN) 客户端:发送 SYN=1, seq=x 客户端状态:CLOSED → SYN_SENT # 第二次:服务端 → 客户端(SYN+ACK) 服务端:发送 SYN=1, ACK=1, seq=y, ack=x+1 服务端状态:LISTEN → SYN_RCVD # 第三次:客户端 → 服务端(ACK) 客户端:发送 ACK=1, seq=x+1, ack=y+1 客户端状态:SYN_SENT → ESTABLISHED 服务端收到后:SYN_RCVD → ESTABLISHED # 通俗理解: # 客户端:喂,能听到吗?(SYN) # 服务端:能听到,你能听到我吗?(SYN+ACK) # 客户端:我也能听到,开始聊天吧(ACK)

四次挥手过程

# 四次挥手(Four-Way Handshake) # 第一次:客户端 → 服务端(FIN) 客户端:发送 FIN=1, seq=u 客户端状态:ESTABLISHED → FIN_WAIT_1 # 第二次:服务端 → 客户端(ACK) 服务端:发送 ACK=1, seq=v, ack=u+1 服务端状态:ESTABLISHED → CLOSE_WAIT 客户端收到后:FIN_WAIT_1 → FIN_WAIT_2 # 第三次:服务端 → 客户端(FIN) 服务端:发送 FIN=1, ACK=1, seq=w, ack=u+1 服务端状态:CLOSE_WAIT → LAST_ACK # 第四次:客户端 → 服务端(ACK) 客户端:发送 ACK=1, seq=u+1, ack=w+1 客户端状态:FIN_WAIT_2 → TIME_WAIT(等待2MSL) 服务端收到后:LAST_ACK → CLOSED 客户端2MSL后:TIME_WAIT → CLOSED # 通俗理解: # 客户端:我说完了,先挂了(FIN) # 服务端:好的,我知道了(ACK)[但服务端可能还有话要说] # 服务端:我也说完了,挂了吧(FIN) # 客户端:好的,拜拜(ACK)[等2MSL确认服务端收到]

中级深入

为什么是三次握手?

# 核心原因:防止已失效的连接请求到达服务端 # 场景:两次握手的问题 # 1. 客户端发送 SYN(网络延迟,超时) # 2. 客户端重发 SYN,建立连接,通信完毕,关闭连接 # 3. 此时,第一个延迟的 SYN 到达服务端 # 4. 如果两次握手:服务端收到 SYN → 回复 ACK → 建立连接 # → 服务端等待客户端发数据,浪费资源 # 5. 三次握手:服务端收到 SYN → 回复 SYN+ACK # → 客户端收到后发现是过期连接 → 发送 RST 重置 # → 服务端不会建立无效连接 # 另一个角度:三次握手才能确认双方的收发能力 # 第一次:服务端知道客户端发送正常 # 第二次:客户端知道自己发送和接收都正常,服务端收发正常 # 第三次:服务端知道自己发送和接收都正常

为什么是四次挥手?

# 核心原因:TCP 是全双工通信 # 四次挥手 = 两次"半关闭" # 第一次 FIN + 第二次 ACK = 客户端→服务端方向关闭 # 第三次 FIN + 第四次 ACK = 服务端→客户端方向关闭 # 为什么不能合并第二次和第三次? # 客户端发送 FIN 表示"我不发数据了" # 服务端回复 ACK 表示"我知道了" # 但服务端可能还有数据要发给客户端 # 等服务端数据发完了,再发送 FIN 表示"我也不发了" # 所以 ACK 和 FIN 不能合并,必须分开发送 # 特殊情况:如果服务端收到 FIN 时也没有数据要发了 # 可以将 ACK 和 FIN 合并到一个报文(三次挥手) # 但通常服务端还有数据要处理

高级进阶

TIME_WAIT 状态

# TIME_WAIT 时长:2MSL(Maximum Segment Lifetime) # MSL:报文最大生存时间,Linux 默认 30 秒 # TIME_WAIT = 2 * 30 = 60 秒 # TIME_WAIT 的两个作用: # 1. 确保最后一个 ACK 能被服务端收到 # 如果 ACK 丢失,服务端会重发 FIN # 客户端在 TIME_WAIT 期间可以重发 ACK # 如果客户端直接 CLOSED,服务端收不到 ACK 会一直重发 FIN # 2. 让旧连接的所有报文在网络中消失 # 经过 2MSL,旧连接的所有报文都已消失 # 新连接不会收到旧连接的残留报文 # TIME_WAIT 过多的问题: # 1. 占用端口资源(客户端端口范围 32768-60999) # 2. 高并发短连接场景下端口耗尽 # 解决方案: # 1. 调整内核参数: # net.ipv4.tcp_tw_reuse = 1 # 复用 TIME_WAIT 连接 # net.ipv4.tcp_timestamps = 1 # 2. 使用长连接(HTTP Keep-Alive) # 3. 让客户端主动关闭(服务端 TIME_WAIT 少)

TCP 状态转换

# TCP 11 种状态: CLOSED # 初始状态(无连接) LISTEN # 服务端监听状态 SYN_SENT # 客户端发送 SYN 后 SYN_RCVD # 服务端收到 SYN 并回复 SYN+ACK 后 ESTABLISHED # 连接建立,正常通信 FIN_WAIT_1 # 主动关闭方发送 FIN 后 FIN_WAIT_2 # 主动关闭方收到 ACK 后 CLOSE_WAIT # 被动关闭方收到 FIN 并回复 ACK 后 LAST_ACK # 被动关闭方发送 FIN 后 TIME_WAIT # 主动关闭方收到 FIN 并回复 ACK 后 CLOSING # 双方同时关闭(罕见) # 常见问题状态: # 1. 大量 CLOSE_WAIT:服务端没有调用 close() # → 检查代码是否忘记关闭连接 # 2. 大量 TIME_WAIT:短连接过多 # → 使用长连接或调整内核参数 # 3. 大量 SYN_RCVD:可能遭受 SYN Flood 攻击 # → 启用 SYN Cookie

实战场景

# 场景1:查看 TCP 连接状态 # Linux netstat -ant | awk '{print $6}' | sort | uniq -c # 输出示例: # 5 ESTABLISHED # 2 LISTEN # 50 TIME_WAIT # 1 CLOSE_WAIT # 场景2:CLOSE_WAIT 排查 # CLOSE_WAIT 说明对方已关闭连接,但我方未调用 close() # 检查代码: try { // 业务处理 } finally { // 确保关闭连接! if (conn != null) conn.close(); } # 场景3:TIME_WAIT 优化 # /etc/sysctl.conf net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_timestamps = 1 net.ipv4.tcp_fin_timeout = 30

面试模拟

面试官:TCP 为什么是三次握手,不是两次或四次?

你:两次握手无法防止已失效的连接请求到达服务端(历史连接问题)。如果客户端第一个 SYN 延迟,服务端收到后直接建立连接,但客户端已经不要这个连接了,浪费服务端资源。三次握手让客户端确认后才建立连接。四次没必要,因为第二次握手 SYN+ACK 可以合并发送。

面试官:TIME_WAIT 状态是什么?为什么需要?

你:TIME_WAIT 是主动关闭方在发送最后一个 ACK 后等待 2MSL(约60秒)的状态。两个作用:1)确保最后一个 ACK 能重传(如果丢失,对端重发 FIN);2)让旧连接的所有报文在网络中消失,不影响新连接。大量 TIME_WAIT 可通过 tcp_tw_reuse 和长连接优化。