TCP 基础篇-第四篇:TCP 连接建立——三次握手全解析
从 SYN 到 ESTABLISHED:深度拆解 TCP 三次握手的每一帧
在 TCP 协议的学习曲线上,三次握手(Three-Way Handshake)无疑是最具标志性的里程碑。它不仅是连接建立的基石,更是 TCP 可靠性、顺序性和抗干扰能力的第一次集中体现。如果说 TCP 是一条可靠的“数据管道”,那么三次握手就是这条管道的“奠基仪式”。本文将从最底层的报文交互出发,逐帧分析 SYN、SYN-ACK、ACK 的语义与作用,深入探讨状态机变迁(LISTEN → SYN-SENT → ESTABLISHED 等),并覆盖 SYN 洪水攻击、半连接队列、以及常见优化策略,帮助读者彻底掌握 TCP 连接建立的全貌。

一、为什么需要三次握手?
在深入细节之前,我们先回答一个根本问题:为什么 TCP 建立连接需要三次交互,而不是两次或四次? 核心原因在于:TCP 需要在一个不可靠的信道上,为通信双方同步初始序列号(ISN),并确认双方的发送与接收能力。两次握手无法解决“历史连接”干扰问题:如果客户端发送的 SYN 延迟到达,服务器会误以为这是一个新连接请求,而客户端却认为连接已建立,导致数据混乱。三次握手通过客户端最后一次 ACK 的确认,确保了双方都明确知道连接已就绪,且交换了彼此的 ISN,为后续的可靠传输奠定了基础。此外,三次握手也验证了双方网络层的可达性,避免了无效的资源分配。
二、三次握手的标准流程
假设客户端(主动打开)与服务器(被动打开)之间建立连接。具体步骤如下:
第一次握手(SYN):客户端向服务器发送一个 SYN 报文段(同步序列号),其中包含客户端的初始序列号
seq = x(随机生成)。此时客户端进入SYN_SENT状态,等待服务器的确认。第二次握手(SYN + ACK):服务器收到 SYN 后,如果同意建立连接,则回复一个 SYN-ACK 报文段。该报文包含服务器的初始序列号
seq = y,并将确认号设为ack = x + 1,表示对客户端 SYN 的确认。服务器进入SYN_RCVD状态。第三次握手(ACK):客户端收到 SYN-ACK 后,发送一个 ACK 报文段,确认号
ack = y + 1,序列号seq = x + 1(即第一个数据字节的序号)。客户端进入ESTABLISHED状态,服务器收到 ACK 后也进入ESTABLISHED状态。此时连接正式建立,可以开始数据传输。
客户端: CLOSED → SYN_SENT → ESTABLISHED 服务器: LISTEN → SYN_RCVD → ESTABLISHED
值得注意的细节:第三次握手的 ACK 报文段通常可以携带数据(如果应用层有数据待发),但一般不带数据。此外,SYN 和 FIN 报文段都消耗一个序列号,而纯 ACK 不消耗序列号,这也是为什么 SYN 的确认号是 x+1 而不是 x。
三、状态机深度解读
三次握手涉及六个 TCP 状态(包括初始的 CLOSED 和 LISTEN),每个状态都有其意义和超时逻辑:
LISTEN:服务器被动打开,等待客户端连接请求。此时端口处于监听状态,半连接队列和全连接队列开始工作。
SYN_SENT:客户端发送 SYN 后,等待服务器的 SYN-ACK。如果未收到,客户端会重传 SYN(重传计时器),重传次数由
tcp_syn_retries控制。SYN_RCVD:服务器收到 SYN 并回复 SYN-ACK,等待客户端的最终 ACK。此时连接处于“半连接”状态,会被放入半连接队列(SYN Queue)。如果 ACK 丢失,服务器会重传 SYN-ACK(次数由
tcp_synack_retries决定)。ESTABLISHED:连接成功,双方可以交换数据。此时连接被移入全连接队列(Accept Queue),等待应用层调用
accept()取出。
如果第三次 ACK 丢失,客户端认为连接已建立(进入 ESTABLISHED),但服务器仍处于 SYN_RCVD。此时如果客户端发送数据,服务器收到数据后会确认(因为数据包的 ACK 字段可能携带正确的确认号),从而间接补上第三次握手,服务器也会进入 ESTABLISHED。这种机制增强了握手的容错性。
四、初始序列号(ISN)的生成与安全性
每个 TCP 连接都使用随机生成的初始序列号,而不是固定值(如 0)。这是为了安全考虑——如果 ISN 可预测,攻击者可以伪造 RST 或数据包实施会话劫持。现代操作系统使用复杂的随机算法(如基于时间戳或加密伪随机数)生成 ISN,并保证在 MSL(最大报文生存时间)内不会重复。同时,ISN 还用于防止旧连接的数据干扰新连接(通过序列号空间回绕检测)。
🔐 安全提示: 历史上著名的“TCP 序列号预测攻击”正是利用 ISN 可预测的漏洞。如今,Linux 等系统采用 /proc/sys/net/ipv4/tcp_timestamps 和随机化增强 ISN 的不可预测性。
五、SYN 洪水攻击与防护
SYN 洪水(SYN Flood)是经典的 DDoS 攻击方式之一。攻击者发送大量伪造源 IP 的 SYN 请求,服务器回复 SYN-ACK 后,由于源 IP 不存在或不可达,永远收不到最后的 ACK,导致服务器大量连接停留在 SYN_RCVD 状态,占满半连接队列,使得正常用户的连接请求被丢弃。防护手段包括:
SYN Cookie:当半连接队列满时,服务器不分配资源,而是通过 SYN-ACK 中编码客户端的相关信息(如 MSS、时间戳),当收到客户端 ACK 时,根据 cookie 重建连接状态,从而无状态地验证 ACK 合法性。
调整队列大小:增大
net.ipv4.tcp_max_syn_backlog和net.core.somaxconn。启用
tcp_syncookies(1 表示启用),以及tcp_abort_on_overflow(谨慎使用)。在防火墙层限制单位时间内的 SYN 请求数。
六、半连接队列与全连接队列
Linux 内核维护两个队列来管理连接建立过程:
半连接队列(SYN Queue):存放处于
SYN_RCVD状态的连接,即收到 SYN 但尚未完成三次握手的连接。每个条目占用少量内存,但数量有限。全连接队列(Accept Queue):存放已完成三次握手、进入
ESTABLISHED状态但尚未被应用层accept()取走的连接。该队列长度由listen()的 backlog 参数和系统somaxconn共同决定。
当全连接队列满时,新完成的连接可能会被丢弃(取决于 tcp_abort_on_overflow 配置)。服务端应用应尽快调用 accept() 以避免队列积压,否则客户端可能经历超时或 RST。
七、握手失败与重传机制
网络不可靠,握手报文可能丢失。TCP 通过超时重传保证可靠性:
客户端发送 SYN 后,若未收到 SYN-ACK,会在 1 秒、2 秒、4 秒……(指数退避)重传 SYN,直到达到
tcp_syn_retries(默认 5~6 次),然后放弃连接。服务器发送 SYN-ACK 后,若未收到 ACK,也会重传 SYN-ACK,次数由
tcp_synack_retries控制(默认 5 次)。如果重传次数耗尽,服务器会释放半连接资源,客户端也会超时返回错误(如
ETIMEDOUT或ECONNREFUSED)。
八、三次握手的性能调优
对于高并发服务(如 Web 服务器、API 网关),三次握手的开销不容忽视。优化方向包括:
启用 TCP Fast Open(TFO):允许在 SYN 中携带数据,减少一个 RTT 的延迟,适用于重复连接的场景(需客户端和服务器同时支持)。
增大队列长度:适当提高
net.ipv4.tcp_max_syn_backlog和net.core.somaxconn,避免在高并发下丢弃连接。调整重传超时:根据网络状况调整
tcp_syn_retries和tcp_synack_retries,平衡可靠性与响应速度。使用连接池:在应用层复用长连接,减少频繁的握手/挥手,这是最有效的优化手段。
九、常见问题排障
大量 SYN_RCVD 堆积:可能遭受 SYN Flood,检查是否启用 syncookies;或服务端性能不足,导致 accept 处理慢。
客户端连接超时:检查防火墙是否拦截 SYN 或 SYN-ACK;检查服务器是否监听正确端口;检查路由是否可达。
全连接队列溢出:通过
netstat -s | grep overflowed查看统计,若持续增长,需增加 backlog 或优化应用处理速度。
结语:握手是信任的开始
三次握手虽然只有短短三个报文,却承载了 TCP 协议最核心的设计哲学:确认、协商、同步。它不仅解决了不可靠网络下的连接建立问题,更为后续的可靠传输、流量控制和拥塞控制奠定了序列号基础。理解三次握手,是深入理解 TCP 全部机制的起点。在下一篇中,我们将探讨 TCP 连接的断开——四次挥手,届时我们会看到对称设计中的更多精妙之处。敬请期待!


客服1