1、TCP三次握手:SYN、SYN-ACK、ACK的交互过程与状态变迁
聊TCP性能优化,三次握手是绕不开的起点。我个人习惯把三次握手看作「连接建立的敲门仪式」——虽然只有三个报文,但里面藏着的细节,往往决定了你整个连接的性能基调。
说白了,三次握手就是客户端和服务端互相确认两件事:「你在吗?」和「你准备好了吗?」。每次握手都伴随着状态机的迁移,而这些状态,正是我们排查连接问题的第一手线索。
1.1 三次握手的完整流程
我们先看一个标准的握手过程。假设客户端(主动方)想连接服务端(被动方)的80端口:
- 第一次握手(SYN):客户端发送SYN报文,初始序列号设为
x。此时客户端进入SYN_SENT状态。 - 第二次握手(SYN-ACK):服务端收到SYN后,回复SYN-ACK报文。确认号是
x+1,自己的初始序列号设为y。服务端进入SYN_RCVD状态。 - 第三次握手(ACK):客户端收到SYN-ACK后,发送ACK报文,确认号为
y+1。客户端进入ESTABLISHED状态。服务端收到ACK后,也进入ESTABLISHED状态。
嗯,这里要注意:第三次握手是可以携带数据的。RFC 793里明确说了,一旦连接建立,数据就可以随ACK一起发送。我在项目中遇到过不少同学以为第三次握手不能带数据,其实这是个常见的误解。
核心要点:三次握手的本质是「一来一回再一确认」,确保双方都具备收发能力,并且同步初始序列号。
1.2 状态变迁图
状态机是理解TCP的骨架。我画了一张图,把三次握手涉及的状态流转串起来,你一看就明白。
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 |
嗯,三次握手的内容就到这里。记住一句话:握手的质量,决定了连接的命运。后面我们会深入每个环节的优化手段,但地基先要打牢。