Appearance
TCP 连接管理(三次握手与四次挥手)
考情分析
三次握手和四次挥手是 408 计算机网络的超高频考点,选择题和大题都常见。选择题常考:为什么是三次不是两次、TIME_WAIT 等待 2MSL 的原因、某一步的序号/确认号是多少。大题常考:画出完整的连接建立/释放过程,标注每一步的 seq、ack 和标志位。
考频:★★★★★
TCP 连接的建立——三次握手
TCP 是面向连接的协议,通信前必须先建立连接。这个过程需要交换三个报文段,因此称为"三次握手"(Three-Way Handshake)。
握手过程详解
假设客户端 A 主动发起连接,服务器 B 被动等待连接。A 的初始序号为 x,B 的初始序号为 y。
第一次握手: 客户端 A 发送连接请求报文段,SYN=1,seq=x。此报文不携带数据,但 SYN 要消耗一个序号。发送后 A 进入 SYN_SENT 状态。
第二次握手: 服务器 B 收到请求后,如果同意连接,发送确认报文段,SYN=1,ACK=1,seq=y,ack=x+1。这个报文段也不携带数据,但同样要消耗一个序号。B 进入 SYN_RCVD 状态。
第三次握手: 客户端 A 收到 B 的确认后,还要再发送一个确认报文,ACK=1,seq=x+1,ack=y+1。这个报文段可以携带数据,如果不携带数据则不消耗序号。A 进入 ESTABLISHED 状态,B 收到后也进入 ESTABLISHED 状态。
为什么是三次,不是两次
这是 408 的经典问题。核心原因:防止已失效的连接请求报文段突然又传送到服务器,导致服务器错误地建立连接。
具体场景:
- A 发出连接请求报文段,但因网络延迟滞留在网络中
- A 等待超时后重新发送请求,成功建立连接,完成数据传输后释放连接
- 之前滞留的那个旧请求终于到达 B
- 如果只有两次握手,B 收到这个旧请求会认为 A 要建立新连接,于是分配资源进入 ESTABLISHED 状态
- 但 A 并没有发起新的请求,不会理会 B 的确认,B 就会一直白白等待,浪费资源
有了第三次握手,B 发出确认后需要等待 A 的再次确认。A 收到 B 对旧请求的确认后,发现自己并没有发起这个请求,不会发送第三次握手,B 收不到确认就不会建立连接。
换一个更本质的角度理解:三次握手的目的是让双方都确认对方的发送能力和接收能力正常。
| 握手次数 | A 确认的信息 | B 确认的信息 |
|---|---|---|
| 第一次 | — | A 的发送能力正常 |
| 第二次 | B 的发送和接收能力都正常 | — |
| 第三次 | — | A 的接收能力正常 |
三次握手后,双方都确认了对方的收发能力。两次握手无法达到这个效果。
SYN 洪泛攻击
顺带提一下安全相关的知识点。攻击者伪造大量不同源 IP 的 SYN 报文段发给服务器,服务器为每个请求分配资源并回复 SYN+ACK,但由于源 IP 是伪造的,第三次握手永远不会到来。大量半开连接耗尽服务器资源,导致正常用户无法建立连接。
防御手段包括 SYN Cookie:服务器不在收到 SYN 时立即分配资源,而是将状态信息编码在 SYN+ACK 的序号中,只有收到合法的第三次握手后才分配资源。
TCP 连接的释放——四次挥手
TCP 连接是全双工的,因此每个方向的连接需要单独关闭。释放连接需要四个报文段,称为"四次挥手"(Four-Way Handshake)。
挥手过程详解
假设客户端 A 主动发起关闭连接,此时 A 的序号为 u,B 的序号为 v。
第一次挥手: A 发送 FIN 报文段,FIN=1,ACK=1,seq=u,ack=v。A 进入 FIN_WAIT_1 状态。FIN 消耗一个序号。
第二次挥手: B 收到 FIN 后发送确认,ACK=1,seq=v,ack=u+1。B 进入 CLOSE_WAIT 状态,A 收到后进入 FIN_WAIT_2 状态。此时 A 到 B 方向的连接已释放,但 B 到 A 方向还可以发送数据。这就是半关闭状态。
第三次挥手: B 发送完剩余数据后,发送 FIN 报文段,FIN=1,ACK=1,seq=w(w 可能不等于 v+1,因为 B 可能又发了数据),ack=u+1。B 进入 LAST_ACK 状态。
第四次挥手: A 收到 B 的 FIN 后发送确认,ACK=1,seq=u+1,ack=w+1。A 进入 TIME_WAIT 状态,等待 2MSL 后进入 CLOSED 状态。B 收到确认后立即进入 CLOSED 状态。
半关闭状态
第二次挥手后,A 已经没有数据要发了(FIN 表示"我这边发完了"),但 B 可能还有数据没发完。此时 B 到 A 方向的连接仍然有效,B 可以继续发送数据,A 可以接收。这种一个方向关闭、另一个方向仍然开放的状态就是半关闭。
为什么需要 TIME_WAIT(等待 2MSL)
MSL(Maximum Segment Lifetime)是报文段在网络中的最大生存时间。TIME_WAIT 状态要等待 2MSL 后才能进入 CLOSED 状态,原因有两个:
原因一:保证最后一个 ACK 能到达 B
如果 A 发送的最后一个 ACK 在传输中丢失,B 收不到确认就会重传 FIN。如果 A 已经关闭了,就无法重发 ACK,B 就永远无法正常关闭。等待 2MSL 保证了:如果最后一个 ACK 丢失(最多 1 个 MSL),B 重传的 FIN 能在 A 关闭前到达(又是最多 1 个 MSL),A 可以重发 ACK。
原因二:让本次连接的所有报文段都从网络中消失
2MSL 足够让网络中所有属于本次连接的残留报文段超时消失。这样新建立的连接就不会收到旧连接的数据。
为什么是四次,不是三次
因为 TCP 是全双工的,每个方向需要一个 FIN 和一个 ACK 来关闭,理论上就是四次。
B 收到 A 的 FIN 后不能立即发 FIN,因为 B 可能还有数据没发完。所以 B 先回一个 ACK 表示"收到你的关闭请求了",等自己数据发完后再发 FIN。这导致 B 的 ACK 和 FIN 不能合并为一个报文段(和三次握手中第二步的 SYN+ACK 合并不同),因此是四次而非三次。
不过如果 B 没有数据要发,B 的 ACK 和 FIN 是可以合并的,此时就变成了三次挥手。但 408 考试中默认按四次处理。
TCP 状态转换
下面是 TCP 完整的状态转换图,涵盖连接建立和释放的所有状态。
各状态含义速查
| 状态 | 含义 |
|---|---|
| CLOSED | 初始状态,无连接 |
| LISTEN | 服务器等待连接请求 |
| SYN_SENT | 客户端已发送 SYN,等待服务器确认 |
| SYN_RCVD | 服务器已收到 SYN 并发送了 SYN+ACK,等待客户端确认 |
| ESTABLISHED | 连接已建立,正常数据传输 |
| FIN_WAIT_1 | 主动关闭方已发送 FIN,等待对方的 ACK 或 FIN |
| FIN_WAIT_2 | 主动关闭方已收到对方的 ACK,等待对方的 FIN |
| CLOSE_WAIT | 被动关闭方已收到 FIN 并发送了 ACK,等待本端应用关闭 |
| LAST_ACK | 被动关闭方已发送 FIN,等待最后的 ACK |
| TIME_WAIT | 主动关闭方收到对方的 FIN 并发送了 ACK,等待 2MSL |
交互可视化
下面的可视化工具展示了三次握手的详细过程,包括每一步的序号、确认号和标志位变化。
四次挥手的可视化:
同时打开和同时关闭
TCP 还有两种特殊情况,虽然实际中极少发生,但 408 偶尔会提到。
同时打开(Simultaneous Open): 双方几乎同时发送 SYN 报文段。这种情况下需要四次报文交换才能建立连接(每方各发一个 SYN,再各回一个 SYN+ACK)。
同时关闭(Simultaneous Close): 双方几乎同时发送 FIN 报文段。这种情况下双方都经历 FIN_WAIT_1 → CLOSING → TIME_WAIT → CLOSED 的状态转换,而不是一方经历 FIN_WAIT 系列、另一方经历 CLOSE_WAIT 系列。
这两种情况在选择题中偶尔作为干扰项出现,了解即可。
连接建立中的资源分配
三次握手的各个阶段,双方分配资源的时机不同:
- 第一次握手后:客户端已发送 SYN,等待确认。客户端分配了少量资源(记录连接状态)。服务器此时还不知道有连接请求。
- 第二次握手后:服务器收到 SYN,发送 SYN+ACK。服务器此时分配了资源(创建半开连接,进入 SYN_RCVD 状态)。这就是 SYN 洪泛攻击的利用点。
- 第三次握手后:客户端收到 SYN+ACK,发送 ACK。此时双方都进入 ESTABLISHED,连接完全建立,可以传输数据。
注意:服务器在第二步就分配了资源,而客户端的第三个 ACK 可能丢失或伪造。这就是 SYN 洪泛攻击能够生效的根本原因。
序号和确认号的计算实例
三次握手和四次挥手题目中,序号和确认号的计算是最容易出错的地方。下面用一个完整的例子串起来。
假设: A(客户端)的初始序号 x=100,B(服务器)的初始序号 y=300。连接建立后 A 向 B 发送 200 字节数据,然后 A 主动关闭连接。
三次握手阶段:
| 步骤 | 方向 | 标志位 | seq | ack | 说明 |
|---|---|---|---|---|---|
| 1 | A→B | SYN=1 | 100 | — | A 的初始 SYN,ACK 位为 0 所以 ack 无效 |
| 2 | B→A | SYN=1, ACK=1 | 300 | 101 | SYN 消耗一个序号,所以 ack=100+1=101 |
| 3 | A→B | ACK=1 | 101 | 301 | SYN 消耗一个序号,所以 ack=300+1=301。不携带数据,不消耗序号 |
数据传输阶段:
| 步骤 | 方向 | 标志位 | seq | ack | 数据长度 | 说明 |
|---|---|---|---|---|---|---|
| 4 | A→B | ACK=1 | 101 | 301 | 200 | A 发送 200 字节(字节 101~300) |
| 5 | B→A | ACK=1 | 301 | 301 | — | B 确认收到 200 字节,ack=101+200=301。B 的 seq 仍为 301(未发送过数据) |
四次挥手阶段:
| 步骤 | 方向 | 标志位 | seq | ack | 说明 |
|---|---|---|---|---|---|
| 6 | A→B | FIN=1, ACK=1 | 301 | 301 | A 主动关闭,seq=301(上次发完 200 字节后 seq 到 301) |
| 7 | B→A | ACK=1 | 301 | 302 | FIN 消耗一个序号,ack=301+1=302 |
| 8 | B→A | FIN=1, ACK=1 | 301 | 302 | B 关闭,B 的 seq 仍为 301(没发过数据) |
| 9 | A→B | ACK=1 | 302 | 302 | ack=301+1=302(B 的 FIN 消耗一个序号) |
这种逐步推导的方法很适合在考场上使用。关键规则:
- SYN 和 FIN 各消耗一个序号(即使没有数据,对方的 ack 也要 +1)
- 纯 ACK 不消耗序号(不携带数据的 ACK 报文,seq 不变)
- 确认号 = 对方 seq + 数据长度(无数据时 +0,但有 SYN/FIN 时 +1)
408 真题常见考法
考法一:补全序号和确认号
给出三次握手或四次挥手的部分信息,要求补全 seq 和 ack 的值。解题要点:
- SYN 消耗一个序号,FIN 消耗一个序号
- 确认号 = 对方 seq + 数据长度(无数据时为对方 seq + 1,因为 SYN/FIN 消耗一个序号)
考法二:判断某个状态下能做什么
比如"FIN_WAIT_2 状态下 A 能否接收 B 的数据?"——能,因为只是 A 到 B 方向关闭了,B 到 A 方向还开着。
考法三:TIME_WAIT 相关
"TIME_WAIT 等待多久?为什么?"—— 2MSL,理由见上文。
易错点
1. 第三次握手可以携带数据
前两次握手不能携带数据(此时连接还没建立完成),但第三次握手可以携带数据。如果携带了数据,则消耗序号;如果没携带数据,则不消耗序号(下一个报文段的 seq 仍然是 x+1)。
2. SYN_RCVD 不是 ESTABLISHED
服务器在收到第三次握手的 ACK 之前处于 SYN_RCVD 状态,此时连接还没有完全建立。这和 SYN 洪泛攻击有关。
3. CLOSE_WAIT 状态是被动关闭方的
如果看到大量 CLOSE_WAIT 状态的连接,说明应用程序收到了对方的 FIN 但自己没有调用 close(),通常是程序 bug。
4. FIN 报文段的确认号
A 发送 FIN 时,ack 字段填的是 A 期望从 B 收到的下一个字节编号,不要忘了填。
5. 四次挥手中第三步的 seq 值
B 发送 FIN 时的 seq=w,这个 w 不一定等于 v+1。如果 B 在半关闭期间又发送了数据,w 就会大于 v+1。
6. 三次握手中第一次握手的 ACK 位
第一次握手 SYN=1,ACK=0。因为此时还没有收到对方的任何数据,所以确认号字段无意义,ACK 位不置 1。第二次和第三次握手 ACK 都为 1。
7. MSL 的典型值
RFC 793 建议 MSL = 2 分钟,但实际实现中常取 30 秒或 1 分钟。TIME_WAIT = 2MSL,所以等待时间通常是 1~4 分钟。在高并发服务器上,大量 TIME_WAIT 连接占用资源是一个实际工程问题,但这个细节 408 不会考。
8. RST 报文段的作用
RST=1 表示连接出现严重错误,必须立即释放连接。常见触发场景:连接到不存在的端口、异常终止连接、半开连接被发现。收到 RST 的一方直接关闭连接,不需要发送 ACK。
高频考点清单
- 三次握手每一步的报文段内容(标志位、seq、ack)
- 为什么是三次握手不是两次(防止旧连接请求)
- 四次挥手每一步的报文段内容
- 半关闭状态的含义
- TIME_WAIT 等待 2MSL 的两个原因
- 为什么是四次挥手不是三次(B 的 ACK 和 FIN 不能立即合并)
- TCP 各状态的含义和转换条件
- SYN 和 FIN 各消耗一个序号