1、TCP三次握手:SYN、SYN-ACK、ACK的交互过程与状态变迁

聊TCP性能优化,三次握手是绕不开的起点。我个人习惯把三次握手看作「连接建立的敲门仪式」——虽然只有三个报文,但里面藏着的细节,往往决定了你整个连接的性能基调。

说白了,三次握手就是客户端和服务端互相确认两件事:「你在吗?」和「你准备好了吗?」。每次握手都伴随着状态机的迁移,而这些状态,正是我们排查连接问题的第一手线索。

1.1 三次握手的完整流程

我们先看一个标准的握手过程。假设客户端(主动方)想连接服务端(被动方)的80端口:

  1. 第一次握手(SYN):客户端发送SYN报文,初始序列号设为x。此时客户端进入SYN_SENT状态。
  2. 第二次握手(SYN-ACK):服务端收到SYN后,回复SYN-ACK报文。确认号是x+1,自己的初始序列号设为y。服务端进入SYN_RCVD状态。
  3. 第三次握手(ACK):客户端收到SYN-ACK后,发送ACK报文,确认号为y+1。客户端进入ESTABLISHED状态。服务端收到ACK后,也进入ESTABLISHED状态。

嗯,这里要注意:第三次握手是可以携带数据的。RFC 793里明确说了,一旦连接建立,数据就可以随ACK一起发送。我在项目中遇到过不少同学以为第三次握手不能带数据,其实这是个常见的误解。

核心要点:三次握手的本质是「一来一回再一确认」,确保双方都具备收发能力,并且同步初始序列号。

1.2 状态变迁图

状态机是理解TCP的骨架。我画了一张图,把三次握手涉及的状态流转串起来,你一看就明白。

TCP三次握手状态变迁图 TCP三次握手状态变迁图 客户端(主动方) 服务端(被动方) CLOSED SYN_SENT ESTABLISHED CLOSED LISTEN SYN_RCVD ESTABLISHED ① SYN (seq=x) ② SYN-ACK (seq=y, ack=x+1) ③ ACK (ack=y+1) 说明:客户端主动发起连接,服务端被动监听。 第三次握手后,双方进入 ESTABLISHED 状态,连接建立完成。

1.3 关键状态详解

你想想看,为什么TCP要设计这么多状态?其实每个状态都对应一个「等待确认」的阶段。我整理了一张表,把三次握手涉及的状态列清楚:

状态 所属端 含义 触发条件
CLOSED 双方 初始状态,无连接 启动时默认
LISTEN 服务端 等待客户端连接 调用 listen()
SYN_SENT 客户端 已发送SYN,等待SYN-ACK 调用 connect()
SYN_RCVD 服务端 已收到SYN,已回复SYN-ACK,等待ACK 收到SYN并回复后
ESTABLISHED 双方 连接已建立,可传输数据 收到ACK(客户端)或收到ACK(服务端)

个人经验:排查连接问题时,我第一件事就是看 netstat -n | grep SYN_RCVD。如果SYN_RCVD堆积过多,八成是服务端 backlog 满了或者客户端在搞 SYN Flood 攻击。

1.4 序列号与确认号的作用

三次握手不只是「打招呼」,它还在同步序列号。序列号是TCP可靠传输的基石——每个字节都有一个编号,接收方通过确认号告诉对方「我收到哪了」。

举个例子:

  • 客户端初始序列号 ISN_c = 1000,那么第一个数据字节的序列号就是 1001。
  • 服务端初始序列号 ISN_s = 5000,第一个数据字节的序列号就是 5001。
  • 确认号表示「期望收到的下一个字节序号」。比如客户端收到 ack=5001,就知道服务端已经收到自己发到 1000 为止的所有数据。

避坑指南:我曾经遇到过一个线上问题——客户端频繁断连重连,每次握手都慢。后来抓包发现,客户端的ISN每次都从0开始,导致服务端误判为旧连接,触发了PAWS(Protection Against Wrapped Sequences)机制。嗯,这就是序列号初始化的坑。

1.5 半连接与全连接队列

服务端在握手过程中维护了两个队列,这是性能优化的关键点:

  • 半连接队列(SYN Queue):服务端收到SYN后,进入SYN_RCVD状态,连接被放入半连接队列。此时还没完成握手。
  • 全连接队列(Accept Queue):服务端收到第三次握手的ACK后,连接进入ESTABLISHED状态,被移入全连接队列。应用层调用 accept() 从这里取走连接。

你想想看,如果全连接队列满了,会发生什么?服务端会丢弃第三次握手的ACK,或者根据 tcp_abort_on_overflow 的行为决定是否发送RST。我在项目中就遇到过因为 accept() 处理太慢,导致全连接队列溢出,客户端大量重试的场景。

性能启示:三次握手不仅仅是「三次报文交换」,它涉及状态机、队列、超时重传等多个子系统。优化连接性能,往往从理解这些底层机制开始。

1.6 常见问题与排查思路

最后,我列几个实际工作中经常遇到的握手问题,你可以对照排查:

现象 可能原因 排查命令
连接超时 防火墙拦截SYN、服务端未监听 tcpdump -i eth0 port 80
SYN_RCVD堆积 半连接队列满、SYN Flood netstat -s | grep LISTEN
连接建立慢 网络延迟高、SYN重传 ss -i 查看RTT
accept()返回慢 全连接队列溢出、应用层处理慢 ss -lnt 查看Recv-Q

嗯,三次握手的内容就到这里。记住一句话:握手的质量,决定了连接的命运。后面我们会深入每个环节的优化手段,但地基先要打牢。