【从0开始学习计算机网络】三次握手:为什么是三次,不是两次或四次?
抓包的时候看到那三行 SYN → SYN, ACK → ACK,很多人第一反应是"能通就行,为什么非得三次"。
我刚学网络那会儿也这么想,直到有次线上服务出现大量 CLOSE_WAIT 连接堆积,才逼着自己把这个问题从头推了一遍。今天把"为什么是三次"讲透,顺便把面试会追问的延伸点一起收拾了。
先说结论
三次握手要解决的核心问题,不是"确认对面能收到",而是双方都确认自己和对方的收发能力正常,并且对初始序列号达成一致。
具体来说,这三次完成了四件事:
- 客户端确认:我的发送 OK、你的接收 OK
- 服务端确认:我的发送 OK、你的接收 OK
- 双方交换初始序列号(ISN)
- 双方确认这个连接是"新鲜"的,不是历史遗留的失效请求
第 4 条是最容易被忽略、也是最能体现"为什么必须是三次"的一条。
为什么不能是两次
两次握手只能让服务端确认"客户端发的包我能收到",但服务端无法确认"客户端能收到我发的包"。这还只是能力层面的问题,更严重的是历史失效连接请求。
看这个场景:
客户端发了一个 SYN(seq=100),路上卡了很久。客户端超时后重发了新的 SYN(seq=200),这次连接建好、传完数据、正常关闭。这时候,那个卡住的老 SYN(seq=100)终于抵达服务端。
如果是两次握手,服务端收到老 SYN,会直接认为连接已建立,开始分配资源、等待客户端发数据。但客户端根本不知道这个连接存在——它早就关了。这份资源就一直空等,成了"半开连接"。攻击者拿这个做文章,就是 SYN 洪泛。
三次握手下,服务端收到老 SYN 后回 SYN-ACK,客户端一比对序列号(100 和自己期望的对不上),直接回 RST,服务端资源释放。一次往返解决。
TCP 的三次握手在 RFC 793 里定死,背后有 1983 年 Tomlinson 那篇讨论"不可靠信道上建立可靠连接"的经典讨论:三次是最小可行次数,两次不足以判定连接的新鲜性。
为什么不是四次
因为第二步能合并。
看起来应该是四次:
客户端 → SYN 我要连你
服务端 → ACK 我收到了
服务端 → SYN 我也要连你
客户端 → ACK 我收到了
但服务端要发的 ACK(确认你的 SYN)和 SYN(同步我的序列号),方向相同、时机相同,完全可以塞进同一个包里——这就是 SYN-ACK。
所以:三次是理论最小值,四次是没必要的重复。
一句话记:握手要交换两个方向各一次"我准备好了",而中间那次可以双向合并。
序列号为什么必须同步
握手还有个隐藏任务:交换初始序列号。
TCP 是字节流协议,每个字节都有序号。双方必须知道对方的起始序号,才能:
- 判断哪些数据是新数据、哪些是重传
- 做流量控制和丢包检测
- 防止"旧连接的残包"被当成本次连接的数据
如果 ISN 不在握手阶段对齐,后面所有序号都对不上,会话直接崩。而 ISN 的选取本身也不该是简单递增——现代实现会用时间戳 + 随机数生成,防止被猜测后注入数据。
几个实战延伸
半连接队列 vs 全连接队列
这两个队列经常被混着说,但排查问题时必须分清:
- 半连接队列(syn queue):收到 SYN、已回 SYN-ACK、还没收到 ACK 的连接
- 全连接队列(accept queue):三次握手已完成、等应用
accept()取走的连接
线上"连接超时但服务进程活着"的典型原因,往往不是被攻击,而是全连接队列满了——应用 accept() 太慢,队列积压。这两个队列要分开看,排查方向完全不同。
# 全连接队列溢出统计(ListenOverflows 非 0 就是)
ss -lnt
nstat -az TcpExtListenOverflows TcpExtListenDrops
SYN 洪泛怎么防
# 开启 syncookies:不回队列,把连接状态编码进序列号
sysctl -w net.ipv4.tcp_syncookies=1
# 半连接队列上限
sysctl net.ipv4.tcp_max_syn_backlog
# SYN-ACK 重试次数(越少越快释放)
sysctl net.ipv4.tcp_synack_retries
tcp_syncookies 的原理值得一提:它不保存连接状态,而是把状态信息编码进返回的序列号里,客户端带着它回来时再解码校验。代价是丢失部分 TCP 选项(比如窗口扩大),但在被打的时候是救命手段。
抓包怎么看
sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0'
正常握手是 S → S. → .(三个包)。如果看到大量只有 S 没有回应,要么是被打了,要么对端服务挂了。如果看到 S → R.,说明对端端口没开。
一句话总结
三次握手不是"规定",是在不可靠网络里建立可靠连接的最小共识次数——两次不够(防不住历史失效连接),四次多余(ACK 和 SYN 可以合并)。
下次面试被问"为什么三次",别只答"确认双方收发能力",把历史失效连接这个点抛出来,再补一句"所以 ISN 的随机性也是安全设计",面试官的眼睛会亮一下。
下一篇讲四次挥手,以及为什么 TIME_WAIT 要等 2MSL——那个才是真·面试重灾区。
更多推荐


所有评论(0)