三次握手全过程
客户端调 connect(),三次报文往来,连接才算立住:
客户端 服务端
│ ① SYN, seq=x │
│ ────────────────────────────→ │ 服务端进入 SYN_RCVD
│ │ 半连接队列 +1
│ ② SYN+ACK, seq=y, ack=x+1 │
│ ←──────────────────────────── │
│ ③ ACK, ack=y+1 │
│ ────────────────────────────→ │ 移入全连接队列
│ 客户端 ESTABLISHED │ 服务端 ESTABLISHED
// 初始序号 ISN(x 和 y)是随机的,不是从 0 开始
// 目的:防止上一次连接的旧报文混进新连接报文里的 seq 是我这个方向的数据从第几字节开始,ack 是我期待对方下一个字节从第几开始(等于收到的字节数 + 1)。TCP 的一切可靠传输,都建立在这两个数字的对账上。
为什么偏偏是三次
先明确握手要达成什么:双方都要确认自己和对方的收发能力都正常。一次做不到,两次有个致命漏洞:
两次握手的问题:迟到的旧 SYN
客户端发了一个 SYN( seq=100 ),网络堵塞,它滞留在路上
客户端超时重发新 SYN,完成连接,干完活,正常断开
那个旧 SYN 终于到达服务端——
两次握手:服务端回个 ACK 就当连接建立,傻等数据,资源被占死
三次握手:服务端回 SYN+ACK 后还要等客户端的最终 ACK
客户端一看 ack 对不上自己的状态,回 RST,连接作废 ✓三次的本质:服务端把连接建立这件事的决定权留到收到客户端最终确认为止。四次则没必要——SYN 和 ACK 可以合并成一个报文,合并了正好三次。
半连接队列与 SYN Flood
服务端收到 SYN 后先把连接放进半连接队列,等第三次 ACK 才挪进全连接队列。攻击者专打这里:海量 SYN 只发第一步不回第三步(伪造源地址,服务端的 SYN+ACK 根本送不到真人手里),半连接队列被灌满,正常用户连不上——这就是 SYN Flood。两个参数是常规防御:
$ cat /proc/sys/net/ipv4/tcp_syncookies # 1 = 开启 syncookies
$ cat /proc/sys/net/ipv4/tcp_max_syn_backlog # 半连接队列长度
# syncookies 思路:队列满了不再排队,把连接信息编进 SYN+ACK 的序号里
# 第三次 ACK 回来时验算通过就直接建立连接——用算术换空间,攻击者的伪造 ACK 算不出来握手成本:连接池的由来
一次握手 = 一个 RTT 的延迟 + 内核资源。北京到广州 RTT 约 30ms,短连接每次请求都白付 30ms 建立费,高 QPS 下还要叠加端口、队列、上下文的开销。所以数据库有连接池、HTTP 有 keepalive、RPC 框架有长连接——都是在摊薄握手成本。后面讲长连接复用时,会算这笔账。面试若追问 ISN 为什么随机:防止上一个四元组相同的历史连接残留报文被误认,也增加伪造难度。下一篇:握手好聚,散伙难——四次挥手和缠人的 TIME_WAIT。
评论 (0)