3. TCP三次握手与四次挥手:握手延迟分析、SYN洪水与半连接队列、挥手过程中的TIME_WAIT问题

TCP的连接与断开,说白了就是两台机器之间互相确认「我在,你也在,咱们可以聊了」以及「我说完了,你也说完了,咱们散了吧」的过程。很多性能问题,根源就藏在这两个阶段里。

我刚开始做网络排查时,总觉得握手挥手就那么几个包,能有什么好看的?直到有一次线上服务频繁超时,抓包一看——好家伙,握手阶段就卡了一秒多。嗯,从那以后我再也不敢小看这三握四挥了。

3.1 三次握手:不只是「你好你好你好」

三次握手的本质,是双方协商初始序列号、确认对方存活、交换窗口大小等关键参数。你想想看,如果这一步就慢了,后面的数据传输全得排队等。

核心流程回顾:

  1. 客户端发送 SYN(seq=x)—— 我来了,这是我的初始序号
  2. 服务端回复 SYN+ACK(seq=y, ack=x+1)—— 收到,这是我的序号
  3. 客户端发送 ACK(seq=x+1, ack=y+1)—— 确认收到,咱们开聊

但实际抓包时,你看到的往往不是这么干净利落。我习惯用 Wireshark 的 tcp.flags.syn==1 过滤出所有握手包,然后看时间戳列。如果 SYN 发出到 SYN+ACK 回来超过 200ms,那基本可以断定有问题。

3.1.1 握手延迟的常见原因

现象 可能原因 Wireshark 排查点
SYN 重传(间隔 1s、3s、7s…) 服务端未响应,客户端超时重传 查看 tcp.analysis.retransmission
SYN→SYN+ACK 间隔 > 1s 服务端 backlog 满,连接被排队 检查 tcp.analysis.ack_rtt
SYN+ACK→ACK 间隔异常 客户端处理慢或中间设备干扰 看客户端 TCP 窗口是否为零
初始 RTT 波动大 网络路径不稳定或丢包 用 tcp.analysis.ack_rtt 统计

我在项目中遇到过最典型的一次:客户端连数据库,每次握手都要 3 秒。抓包一看,SYN 发了三次才收到 SYN+ACK。查到最后,是中间防火墙把 SYN 包当攻击流量给限速了。说白了,中间设备对 TCP 的「过度保护」反而成了瓶颈。

我的排查习惯:在 Wireshark 里用 tcp.flags.syn==1 and tcp.flags.ack==0 只看纯 SYN 包,然后看 Expert Info 里的「Retransmission」和「Fast Retransmission」。如果 SYN 重传超过 1 次,先怀疑网络路径,再怀疑服务端负载。

3.2 SYN 洪水与半连接队列

SYN 洪水攻击,说白了就是攻击者只发 SYN,不发最后的 ACK。服务端傻傻地等着,把连接放到半连接队列(SYN Queue)里。队列满了,正常用户的 SYN 就进不来了。

你想想看,服务端资源是有限的。半连接队列的大小由 tcp_max_syn_backlog 和 somaxconn 共同决定。一旦被填满,新连接直接丢包——用户那边看到的就是「连接超时」。

我曾经踩过的坑:有一次线上告警说服务不可用,我第一反应是应用挂了。重启后好了五分钟,又挂了。抓包一看,半连接队列里全是来自同一个 IP 的 SYN,ack 号随机乱填。典型的 SYN 洪水。后来加了 SYN Cookie 才稳住。

3.2.1 如何用 Wireshark 识别 SYN 洪水

  • 看 SYN 包数量:用 tcp.flags.syn==1 and tcp.flags.ack==0 统计,如果每秒上千个且来源 IP 分散,基本就是攻击
  • 看 SYN+ACK 重传:服务端发了 SYN+ACK 但没等到 ACK,会重传。用 tcp.analysis.retransmission and tcp.flags.syn==1 and tcp.flags.ack==1 过滤
  • 看目标端口:如果所有 SYN 都打同一个端口,攻击目标明确
  • 看 TTL:攻击流量往往 TTL 相同或呈规律变化

防御三板斧:

  1. SYN Cookie:服务端收到 SYN 时不分配资源,而是用 Cookie 做无状态校验。Linux 下 sysctl -w net.ipv4.tcp_syncookies=1
  2. 调整半连接队列:增大 net.ipv4.tcp_max_syn_backlog 和应用的 somaxconn
  3. 限制 SYN 速率:用 iptables 或硬件防火墙做限速,比如 iptables -A INPUT -p tcp --syn -m limit --limit 100/s -j ACCEPT

3.3 四次挥手与 TIME_WAIT 问题

挥手比握手复杂,因为它是「半关闭」的——每一方都要独立关闭自己的数据流。四次挥手的过程我就不赘述了,但 TIME_WAIT 这个状态,是每个网络工程师都绕不开的坎。

3.3.1 TIME_WAIT 为什么存在?

说白了,TIME_WAIT 有两个目的:

  • 确保被动关闭方收到最后的 ACK(如果 ACK 丢了,对方会重传 FIN)
  • 让旧连接的报文在网络中消散,避免干扰新连接

TIME_WAIT 持续 2MSL(通常 1-4 分钟)。对于高并发的短连接服务(比如 Nginx、Redis 客户端),这个状态会堆积大量端口和内存。

我的经验:用 netstat -tan | grep TIME_WAIT | wc -l 看数量。如果超过几万,而且你的服务是短连接模型,那就要处理了。我见过一台机器上 TIME_WAIT 堆到 30 万,新连接直接报错「Cannot assign requested address」。

3.3.2 Wireshark 中看挥手问题

现象 过滤表达式 说明
FIN 重传 tcp.analysis.retransmission and tcp.flags.fin==1 对方没收到 FIN,或者 ACK 丢了
RST 替代 FIN tcp.flags.reset==1 异常关闭,可能应用层主动 RST
挥手卡住 tcp.flags.fin==1 or tcp.flags.reset==1 看时间戳,如果 FIN 发出后长时间没收到 FIN+ACK,可能是对端卡住
大量 TIME_WAIT 无法直接过滤,但可通过统计看 用 Statistics → Conversations 看连接数

3.3.3 TIME_WAIT 的优化策略

注意,我不是让你盲目调优。TIME_WAIT 本身是安全机制,只有在确认不会导致问题时才调整。

  • 开启端口复用:net.ipv4.tcp_tw_reuse=1(客户端场景,允许重用 TIME_WAIT 状态的端口)
  • 快速回收:net.ipv4.tcp_tw_recycle=1(已废弃,不推荐,NAT 环境下会出大问题)
  • 减少 MSL:修改 /proc/sys/net/ipv4/tcp_fin_timeout,默认 60s,可降到 30s
  • 应用层改造:用长连接替代短连接,或者用连接池

我曾经犯过的错:为了快速解决 TIME_WAIT 堆积,我直接开了 tcp_tw_recycle。结果 NAT 环境下的用户频繁掉线,因为不同用户经过同一个 NAT 出口时,时间戳乱了。后来老老实实改成长连接 + 端口复用,问题才解决。

3.4 本章知识体系图

下面这张图把三次握手、半连接队列、四次挥手和 TIME_WAIT 的核心逻辑串在一起。你可以对照着看,哪个环节出问题,就从哪个方向排查。

TCP 握手挥手与瓶颈定位知识体系 TCP 握手挥手与瓶颈定位 三次握手 半连接队列 (SYN Queue) 握手延迟分析 四次挥手 TIME_WAIT 状态 FIN/RST 异常排查 Wireshark 排查要点 • SYN 重传 → 网络/防火墙 • SYN+ACK 延迟 → backlog 满 • FIN 重传 → 对端未收到 ACK • RST 替代 FIN → 异常关闭 • TIME_WAIT 堆积 → 端口耗尽 核心思路:先看握手是否正常 → 再看挥手是否干净 → 最后定位瓶颈 抓包过滤:tcp.flags.syn==1 统计:Statistics → Conversations 优化:长连接 / 端口复用 图注:深色节点为本章核心知识点,中间黄色框为 Wireshark 排查要点 底部三个节点为实战中最高频使用的排查与优化手段

3.5 实战排查清单

最后,我把自己平时排查握手挥手问题的步骤整理成清单,你遇到类似问题时可以直接照着做:

  1. 抓包:在客户端和服务端分别抓,用 tcp.port==目标端口 过滤
  2. 看握手:找到第一个 SYN,看它到 SYN+ACK 的间隔。超过 200ms 就标记
  3. 看重传:Expert Info 里有没有红色或黄色的 Retransmission?有的话看是哪个方向
  4. 看半连接:服务端用 ss -s 看当前半连接数,对比 tcp_max_syn_backlog
  5. 看挥手:找到 FIN 包,看它到最后一个 ACK 的时间。如果超过 60s,检查 TIME_WAIT 堆积
  6. 看 TIME_WAIT:netstat -tan | grep TIME_WAIT | wc -l,超过 5 万就要关注
  7. 看应用日志:有时候是应用层主动 RST,Wireshark 只能告诉你「断了」,为什么断还得看日志

一个小技巧:在 Wireshark 里用 tcp.time_delta > 0.5 过滤出所有间隔超过 500ms 的 TCP 包。这些「慢包」往往就是瓶颈所在。我每次排查性能问题,第一步就是跑这个过滤,然后看这些慢包都出现在哪个阶段。

握手挥手,看似基础,但很多线上问题就藏在这几个包的交互里。你只要把这一章的内容吃透,以后遇到连接超时、响应慢、端口耗尽这类问题,心里就有底了。