Skip to content

说说 TCP 的三次握手? ​

  • 标签:网络

🏛 业务架构师 ​

三次握手:客户端发 SYN(携带初始序号)→ 服务端回 SYN+ACK(确认并带上自己的初始序号)→ 客户端回 ACK,连接建立。

为什么是三次不是两次?核心是双方都要确认"我说的话对方能收到,对方说的话我也能收到"——四次信息压缩成三次(SYN 和 ACK 可以合并)。两次握手的问题:历史网络里滞留的旧 SYN 到达服务端,服务端回 ACK 就认为连接建立并分配资源,客户端却根本没发起——服务端为一次幻觉对话白白占用连接资源,这正是 SYN 洪泛攻击的原理。

业务视角的三个落点:

  • 连接是成本项:每次握手一个 RTT,高频短连接的场景(移动端轮询)这个延迟直接进接口耗时。解法是长连接/连接池/HTTP keep-alive,把握手成本摊薄——网关侧开启 keep-alive 后,接口 P99 能显著改善;
  • SYN 攻击的防护是容量安全:SYN Cookie、连接数限流要在网关和内核参数(somaxconn、backlog)两层配置,大促前例行检查;
  • 握手超时决定用户感知:移动弱网下握手重试参数(tcp_syn_retries)影响"App 显示转圈多久",这是体验参数不是纯技术参数。

一句话:三次握手是用最小的往返次数买到双向通信的确定性,围绕它的所有优化都在回答一个问题——如何让用户更快连上、让服务端不被无效连接拖垮。

🏗 系统架构师 ​

三次握手是 TCP 建立连接的协商过程,本质是双方各自确认"我的发送和接收能力都正常",并同步初始序列号 ISN:

  1. SYN:客户端发 SYN + 客户端 ISN,进入 SYN_SENT;
  2. SYN + ACK:服务端回复 ACK(确认客户端 ISN+1)并带上自己的 ISN,进入 SYN_RCVD;
  3. ACK:客户端回 ACK 确认服务端 ISN+1,双方 ESTABLISHED。

为什么必须三次:两次不行——服务端无法确认"自己发的、对方能收"。历史场景:一个延迟到达的旧 SYN 让服务端两次握手就建连、单方面分配资源白白等待,三次握手客户端不回 ACK 就能识破。核心是防止历史重复连接初始化造成的资源错配(RFC 里最权威的理由),同时序列号是可靠传输(去重、排序、ACK)的锚点,必须双向确认。

工程关联:SYN Flood 打的就是握手的资源不对称(客户端伪造源地址不回 ACK,服务端半连接堆积)——防御靠 tcp_syncookies(不存半连接状态,编码进序列号)、缩短 SYN_RECV 超时、防火墙代理握手;高并发服务端还要调 somaxconn、accept backlog,避免完成队列满导致握手成功却被丢弃。

💻 全栈开发专家 ​

过程:

text
客户端                 服务端
  │ ── SYN, seq=x ──────────→ │   LISTEN → SYN_RCVD
  │ ←─ SYN+ACK, seq=y, ack=x+1 │
  │ ── ACK, ack=y+1 ────────→ │   双方 ESTABLISHED

为什么必须三次(核心问题):

  1. 双方都要确认自己的发送和接收能力:两次的话服务端无法确认"客户端能收到我的包"。
  2. 防止历史重复 SYN 建立无效连接:网络里滞留的旧 SYN(比如上一次超时重传的)迟到,若两次握手服务端直接建立连接白白占用资源;三次握手下客户端发现 ack 不对会 RST 掉。

工程关联(加分项):

  • SYN Flood 攻击:伪造源地址海灌 SYN,服务端 SYN_RCVD 塞满半连接队列。内核防线:tcp_syncookies=1(不占队列,把状态编码进序号)。我在生产被扫过,syncookies + 调大 tcp_max_syn_backlog + 防火墙限速解决。
  • 握手耗时可感知:跨地域 RTT 100ms,短连接每次握手就是白扔 1 个 RTT,所以长连接/连接池是基础优化——这也解释了 HTTPS 为什么要 session 复用和 0-RTT。