外观
TCP 三次握手与四次挥手
约 1192 字
面试必考题,但大部分人(包括背过答案的我)只是记住了「三次握手 四次挥手」这个说法,说不清为什么。这篇从「要解决什么问题」出发, 把过程推一遍,最后给个自己抓包验证的方法。
一、TCP 想解决什么问题
TCP 是可靠的、面向连接的传输层协议。两个关键词:
- 可靠:发出去的数据,对方收到了、按顺序、没损坏;
- 面向连接:正式传数据前,双方要先确认「你准备好听、我准备好说」。
握手就是为了建立这个「双向确认」。挥手就是为了干干净净地拆掉它。
二、三次握手:互相确认收听状态
图:三次握手的完整过程,以及对应的四次挥手
过程
① 客户端 → 服务端:SYN(seq=x)
我要连你,我的初始序号是 x。
② 服务端 → 客户端:SYN + ACK(seq=y, ack=x+1)
好的我收到了,我的初始序号是 y;你的 x 我收到了。
③ 客户端 → 服务端:ACK(ack=y+1)
你的 y 我也收到了。开始吧。为什么是三次,不是两次
这是核心问题。两次的问题在于:服务端无法确认客户端 「能收到」自己的消息。
设想两次就建立连接:
- 客户端发 SYN,服务端回 SYN+ACK,双方开始传数据;
- 如果客户端根本没收到服务端的 SYN+ACK(网络丢了), 客户端认为连接没建立,什么都不发;
- 服务端却认为连接建好了,吭哧吭哧发数据,还占着资源等确认。
三次握手补上了第三步:客户端对服务端的 SYN 做出回应。 这样双方都确认了「我发的对方能收到,对方发的我能收到」, 双向信道都验证过了。
那四次行不行?行,但浪费。第二步的 SYN 和 ACK 是同一个包里 发出去的(都是服务端一个内核进程处理的),没必要分两次。 三次是「够用的最小值」。
三、四次挥手:双向都要单独关
过程
① 客户端 → 服务端:FIN
我说完了(我的数据发完了)。
② 服务端 → 客户端:ACK
知道了。
③ 服务端 → 客户端:FIN
我也说完了。
④ 客户端 → 服务端:ACK
知道了。连接彻底关闭。为什么是四次
因为关闭是两个方向独立的事。握手时服务端能把 「我也要连你」和「我同意你连我」合成一个包(SYN+ACK), 但挥手时,客户端说「我说完了」,服务端只能先「知道了」—— 服务端可能还有数据没发完,不能顺手把自己的 FIN 也带上。 得等它自己发完了,才能单独发 FIN。
所以挥手比握手多一次:二次不可合,各关各的。
四、抓包亲眼看看
理论背十遍,不如抓一次包:
# Linux/macOS
sudo tcpdump -i any -c 20 'tcp[tcpflags] & (tcp-syn|tcp-fin) != 0'
# Windows(Wireshark 图形界面更直观)
# 装个 Wireshark,访问任意 http 网站,过滤 tcp.flags.syn==1访问一个网页,能看到熟悉的 [SYN] → [SYN, ACK] → [ACK]。 看到的那一刻,「三次握手」四个字才真正长在脑子里。
Wireshark 过滤表达式备忘:
tcp.flags.syn == 1 && tcp.flags.ack == 0 # 第一次握手
tcp.flags.syn == 1 && tcp.flags.ack == 1 # 第二次握手
tcp.flags.fin == 1 # 挥手五、几个容易混淆的点
TIME_WAIT 是谁的? 主动关闭的一方(先发 FIN 的)进入 TIME_WAIT,等 2MSL (最长报文寿命的两倍)确保对方收到最后的 ACK。 这就是为什么服务端主动关连接后,端口有时不能立即重用。
SYN 洪水攻击 攻击者狂发第一次握手(SYN)但不理第三次,服务端的 半连接队列被填满,正常用户连不上。防御靠 SYN cookie、 减半连接队列超时等,运维层的事,知道有这回事就行。
UDP 怎么没有握手? UDP 不管可靠,发出去就不管了,自然不需要「确认你能收到」。 DNS 查询、视频流、游戏同步这些场景用它 —— 丢一两个包无所谓,快就行。
小结
三次握手:双向确认信道通畅,两次不够,四次多余。 四次挥手:两个方向独立关闭,服务端有数据没发完就不能合并。 记住这两句,比背流程靠谱。想彻底搞懂,抓一次包, 亲眼看那三个包飞出去。