TCP三次握手和四次挥手是什么?连接建立与释放过程解析

面试里被问烂,运维中却天天踩坑:明明服务在监听,客户端却连不上;抓包只看到 SYN 没有回包;服务器上堆了几万个 TIME_WAIT;程序里连接池泄漏,CLOSE_WAIT 越积越多。这些问题的答案,都藏在 TCP 的"三次握手"和"四次挥手"里。

这篇文章不背八股,而是把握手和挥手的每一个报文、每一次状态变化讲透,配上 ss、tcpdump 的真实命令,让你能看懂抓包、能判断连接卡在哪一步、能解释 TIME_WAIT 和 CLOSE_WAIT 带来的实际影响。读完你应该能做到:看到连接异常,知道该查哪个状态、哪个队列、哪个内核参数。

一、为什么连接需要"握手"

TCP 是面向连接的协议,双方在传数据前必须先确认三件事:彼此都愿意通信、彼此的序号从哪里开始、连接确实可达。因为网络不可靠,你不能假设"我发了对方一定收到",必须靠一来一回的确认来同步状态。

握手的核心价值是同步初始序号(ISN)并防止历史连接干扰。如果只握两次,服务端无法确认客户端的接收能力,也可能被一个早已失效的旧 SYN 建出一个假连接。三次是目前最少的、能同时完成双向确认的次数。

二、三次握手完整流程

假设客户端端口 50000,服务端 80 端口,三个报文依次是:

  1. 第一次:客户端发 SYN。客户端选一个初始序号(比如 Seq=x),置 SYN=1,状态从 CLOSED 进入 SYN_SENT。意思是"我想连你,我的序号从 x 开始"。
  2. 第二次:服务端回 SYN+ACK。服务端选自己的初始序号 Seq=y,置 SYN=1、Ack=x+1,状态从 LISTEN 进入 SYN_RCVD。意思是"我同意,我的序号从 y 开始,我期待你下一个字节是 x+1"。
  3. 第三次:客户端回 ACK。客户端置 Ack=y+1,Seq=x+1,进入 ESTABLISHED;服务端收到后也进入 ESTABLISHED。连接建立完成。

用抓包看最直观(抓包里 S 是 SYN,. 是 ACK,S. 是 SYN+ACK):

# 抓一次握手过程,-nn 不解析名字,-S 显示绝对序号
sudo tcpdump -nn -S -i eth0 'tcp port 80 and (tcp[tcpflags] & (tcp-syn|tcp-ack) != 0)'

风险提示:抓包需要 root 或 CAP_NET_RAW 权限,且在高流量网卡上长时间全量抓包会占满磁盘和 CPU,务必加过滤条件和 -c 限制条数。

为什么不能是两次

如果只有两次(客户端 SYN、服务端 SYN+ACK),客户端其实还没告诉服务端"我收到你的回包了"。这会导致:服务端回完 SYN+ACK 就单方面认为连接建好了,一旦这个回包在网络上延迟,客户端早已放弃,服务端却白白维护了一个半开连接,浪费资源。第三次 ACK 的作用就是让服务端确认"客户端确实收到了、连接确实双向可达"。

三、四次挥手完整流程

关闭连接比建立更啰嗦,因为 TCP 是全双工,两个方向的数据流要分别关闭。整个流程:

  1. 第一次:主动关闭方发 FIN。比如客户端发 FIN(Seq=u),表示"我没数据要发了",进入 FIN_WAIT_1。
  2. 第二次:被动方回 ACK。服务端回 Ack=u+1,进入 CLOSE_WAIT。此时客户端进入 FIN_WAIT_2。这就是半关闭状态:客户端不再发数据,但还能收服务端剩下的数据。
  3. (服务端可能还要继续把没发完的数据发完,客户端一直能收到。)
  4. 第三次:被动方发 FIN。服务端数据发完,发 FIN(Seq=w),进入 LAST_ACK。
  5. 第四次:主动方回 ACK。客户端回 Ack=w+1,进入 TIME_WAIT,等待 2MSL 后进入 CLOSED;服务端收到 ACK 后进入 CLOSED,连接彻底释放。
# 抓挥手过程:FIN 和 RST 标志
sudo tcpdump -nn -i eth0 'tcp port 80 and (tcp[tcpflags] & (tcp-fin|tcp-rst) != 0)'

# 看本机连接状态分布
ss -ant | awk '{print $1}' | sort | uniq -c | sort -rn

为什么是四次而不是三次

因为"关闭"这件事,服务端 ACK 和 FIN 通常不能合并。客户端发 FIN 时,只是说"我不发了",但服务端可能还有数据要发。所以服务端先回一个 ACK(第二次),把数据发完后再发 FIN(第三次)。这两个动作之间有间隔,就拆成了四次。如果服务端恰好也没数据要发,ACK 和 FIN 可能一起发送,看起来像三次,但协议逻辑上仍是四个步骤。

四、状态转换全表

TCP 连接的生命周期由有限状态机描述,运维最需要认识这几个状态:

状态 出现在哪一方 含义 关注点
LISTEN 服务端 监听端口,等待连接 服务没起来时该状态缺失
SYN_SENT 客户端 已发 SYN,等 SYN+ACK 大量堆积说明对端不回或丢包
SYN_RCVD 服务端 收到 SYN,已回 SYN+ACK 大量堆积可能是 SYN Flood
ESTABLISHED 双方 连接已建立,可传数据 数据是正常连接数
FIN_WAIT_1 主动关闭方 已发 FIN,等 ACK
FIN_WAIT_2 主动关闭方 收到 ACK,等对方 FIN 大量堆积说明对端一直不发 FIN
TIME_WAIT 主动关闭方 等 2MSL,防止旧包干扰 短连接业务高发,属正常但要控制
CLOSE_WAIT 被动关闭方 收到 FIN,本端还没 close 大量堆积几乎一定是应用 bug
LAST_ACK 被动关闭方 已发 FIN,等最后 ACK
CLOSED 双方 连接关闭

用一条命令看全貌:

# 按状态统计当前连接数
ss -ant | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn

# 找出处于 CLOSE_WAIT 的连接及其进程(关键排查命令)
sudo ss -antp | grep CLOSE_WAIT

# 统计每个进程的 CLOSE_WAIT 数量
sudo ss -antp | grep CLOSE_WAIT | awk '{print $NF}' | sort | uniq -c | sort -rn

五、握手相关的两个队列:SYN 队列和 Accept 队列

服务端收到 SYN 后,并不是立刻交给应用,而是先进内核的两个队列:

  • SYN 队列(半连接队列):存放已收到 SYN、已回 SYN+ACK、但还没收到最终 ACK 的连接(状态 SYN_RCVD)。长度由 tcp_max_syn_backlog 影响。
  • Accept 队列(全连接队列):存放已完成三次握手、等待应用调用 accept() 取走的连接(状态 ESTABLISHED)。长度由 listen() 的 backlog 参数和 somaxconn 共同决定,取较小值。

如果应用处理 accept 太慢,Accept 队列满了,新的握手会受影响,表现就是客户端偶发连接超时。查看队列溢出:

# 查看全连接队列溢出次数(n 表示 listen backlog)
ss -lnt | grep ':80'

# 查看内核统计:全连接队列溢出
netstat -s | grep -i 'listen'
# 或
nstat -az | grep -i -E 'ListenOverflows|ListenDrops|TCPBacklogDrop'

如果 ListenOverflows 持续增长,说明 Accept 队列被打满,需要调大 somaxconn 和应用 backlog,或者提升应用 accept 速度。

六、TIME_WAIT 的实际影响

TIME_WAIT 只出现在主动关闭的一方,持续 2MSL(通常 60 秒)。它的存在有意义:一是保证最后那个 ACK 能重传(对端没收到 FIN 重发时可以正确响应),二是让本次连接的旧数据包在网络中消散,避免污染新连接。

但高频短连接(比如爬虫、API 网关做健康检查、没配连接池的 HTTP 客户端)会产生海量 TIME_WAIT,占满本地端口,导致"无法建立新连接"。查看:

# 统计 TIME_WAIT 数量
ss -ant | grep -c TIME_WAIT
# 查看本地端口范围
cat /proc/sys/net/ipv4/ip_local_port_range

处理方向后面 027 篇会专门展开,核心是:优先用长连接/连接池,其次是扩大本地端口范围、开启 tcp_tw_reuse,不要轻易开启已废弃的 tcp_tw_recycle。

常见问题

Q:抓包只看到 SYN 没有 SYN+ACK,是什么问题?
A:三种可能:服务端没监听该端口(通常会回 RST)、服务端 SYN 队列满(丢弃 SYN)、中间防火墙丢包。用 ss -lnt 确认服务端是否在听,用 netstat -s 看是否有 SYN 丢弃计数。

Q:TIME_WAIT 很多,是不是服务器有问题?
A:不一定是故障。主动关闭方出现 TIME_WAIT 是协议正常行为,短连接多就会多。只有占了大量端口、影响新连接时才需要优化。

Q:CLOSE_WAIT 堆积说明什么?
A:说明本端收到了对端的 FIN,但应用没有调用 close() 关闭套接字。几乎百分之百是应用代码问题:连接没释放、异常分支没关连接、连接池配置不当。

Q:三次握手可以带数据吗?
A:可以。TCP Fast Open 允许在 SYN 或 SYN+ACK 中携带数据,减少一次往返。但需要内核和应用同时支持,且有安全权衡。

Q:为什么挥手要等 2MSL?
A:MSL 是报文最大生存时间,2MSL 保证"本端最后一个 ACK 丢失后,对端重发的 FIN 能在本端还在 TIME_WAIT 时被处理",同时让旧报文彻底消亡。

总结

  • 三次握手:SYN → SYN+ACK → ACK,核心是双向确认可达并同步初始序号;两次不足以确认客户端接收能力,四次是浪费。
  • 四次挥手:FIN → ACK → FIN → ACK,因为全双工两个方向要分别关闭,半关闭期间被动方还能发数据。
  • 记住状态机里三个高频状态:TIME_WAIT(主动关闭方,正常但需控制)、CLOSE_WAIT(被动关闭方未 close,多为应用 bug)、SYN_RCVD(可能与 SYN Flood 有关)。
  • 服务端连接队列分 SYN 队列和 Accept 队列,前者由 tcp_max_syn_backlog 影响,后者受 somaxconn 与 listen backlog 限制,溢出用 nstat 观察。
  • 排查连接问题用 ss -antp 看状态和进程,用 tcpdump 抓握手挥手报文,先定位卡在哪一步,再对症下药。