一句话总结
三次握手:客户端 → 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 和长连接优化。