深入理解 TCP 三次握手与四次挥手
TCP连接管理采用三次握手建立连接和四次挥手终止连接。三次握手通过SYN和ACK标志位交换初始序列号,验证双方通信能力:客户端发送SYN→服务器回复SYN+ACK→客户端确认ACK。四次挥手确保可靠断开:主动方发送FIN→被动方ACK→被动方发送FIN→主动方ACK确认。关键点包括:1)三次握手防止历史连接问题;2)四次挥手处理半关闭状态;3)TIME_WAIT状态确保最后ACK可靠传输。整个过程
TCP使用三次握手和四次挥手来建立和终止连接。
一、三个标志位
1. SYN(Synchronize)标志位:用于请求建立连接。
2. ACK(Acknowledgement) 标志位:用于确认数据的传输。当成功接收到数据后,接收方发送一个带有 ACK 标志的报文段回复发送方,确认已经收到了数据。
3. FIN (Finish) 标志位:用于关闭连接。
二、三次握手过程

1、握手前的初始状态
客户端: CLOSED ——> 发送 SYN后进入 SYN_SENT
服务器: CLOSED ——> 监听端口时处于 LISTEN
2、第一次握手(客户端发起)
客户端:
发送SYN报文
| 字段 | 值 | 说明 |
|---|---|---|
| 序列号 (seq) | x |
随机生成的ISN(如:1523123456) |
| 确认号 (ack) | 0 |
无效/未设置,因为这是首次发起连接 |
| SYN标志位 | 1 |
这是SYN报文,SYN=1 |
| ACK标志位 | 0 |
不是确认报文,所以ACK=0 |
进入 SYN_SENT 状态,等待服务器的 SYN+ACK
服务器:
仍处于 LISTEN 状态,等待连接请求
3、第二次握手(服务器响应)
服务器:
收到SYN报文后,发送SYN+ACK报文(是一条报文,同时设置了SYN和ACK两个标志位)
| 字段 | 值 | 说明 |
|---|---|---|
| 源端口 | 服务器的监听端口(如80) | |
| 目的端口 | 客户端的源端口 | |
| 序列号 (seq) | y |
服务器的ISN(随机生成) |
| 确认号 (ack) | x+1 |
确认客户端的SYN,期待客户端发x+1 |
| SYN标志位 | 1 |
这是服务器的SYN |
| ACK标志位 | 1 |
这是对客户端SYN的确认 |
| 其他标志位 | 都为0 |
进入 SYN_RCVD 状态,等待客户端的 ACK
客户端:
仍处于 SYN_SENT 状态,等待 SYN_ACK
4、第三次握手(客户端确认)
客户端:
收到 SYN+ACK 后,发送 ACK
| 字段 | 值 | 说明 |
|---|---|---|
| 源端口 | 客户端的源端口 | |
| 目的端口 | 服务器的端口(如80) | |
| 序列号 (seq) | x+1 |
客户端的下一个序号(因为SYN消耗了序号x) |
| 确认号 (ack) | y+1 |
确认服务器的SYN,期待服务器发y+1 |
| SYN标志位 | 0 |
不是SYN报文 |
| ACK标志位 | 1 |
这是确认报文 |
| 其他标志位 | 都为0 | |
| 数据长度 | 0字节 | 纯ACK,不携带应用数据 |
进入 ESTABLISHED 状态
服务器:
收到 ACK后,进入 ESTABLISHED 状态
状态汇总表
| 步骤 | 客户端状态 | 服务器状态 | 说明 |
|---|---|---|---|
| 初始 | CLOSED | LISTEN | 服务器监听端口 |
| 第一次握手 | SYN_SENT | LISTEN | 客户端发送SYN |
| 第二次握手 | SYN_SENT | SYN_RCVD | 服务器发送SYN+ACK |
| 第三次握手 | ESTABLISHED | ESTABLISHED | 双方连接建立完成 |
三、三次握手的意义
1、投石问路,初步验证了网络通信路径是畅通的
2、验证通信双方的发送能力和接受能力是否正常
3、三次握手过程中,完成了一些参数协商
4、同步了双方的初始序列号
四、三次握手的几个常见问题
1、两次握手行不行

如图所示,缺少了最后一次握手,会导致无法验证客户端的接收数据的功能是否正常,所以不行
2、四次握手可不可以

如图,相对于三次握手来说,四次握手把第二次握手拆成了两次通信,可以看出,四次握手也可以实现三次握手所实现的功能,但是没有必要,效率比三次低
五、四次挥手的过程
主动关闭方和客户端/服务器角色没有必然联系,下图以客户端为主动关闭方为例

第一次挥手:主动关闭方发起
主动关闭方:
触发条件:应用层调用 close()
发送报文:构造并发送一个 FIN 报文
| 段名 | 值(十六进制/二进制) | 说明 |
|---|---|---|
| 序列号(seq) | u |
最后一个应用数据字节的序号+1 |
| 确认号(ack) | 当前期望接收的下一字节序号 |
这个值有意义!不是0 |
| 标志位 | FIN=1, ACK=0或1 |
关键澄清 |
状态变化:状态由 ESTABLISHED 进入 FIN_WAIT_1(等待确认)
后续行为:停止发送引用层数据,但可以继续接受对方发来的数据,等待对方的 ACK 确认
被动关闭方:
收到 FIN 报文:从网卡接收到 FIN
内核处理:检查连接是否存在且有效、更新接收窗口,期望下一个字节序号改为 u+1
通知应用层: 如果由阻塞的 read() 操作,会返回 EOF(0)
状态变更:从 ESTABLISED 进入 CLOSED_WAIT(等待关闭)
理解现状:知道对方已经关闭了发送通道,自己仍可以向对方发送数据,连接进入半关闭状态
第二次挥手:被动关闭方确认
被动关闭方:
立即响应:收到 FIN 后,尽快发送 ACK
发送报文:构造 ACK 报文
| 字段名 | 值 | 说明 |
|---|---|---|
| 序列号(seq) | v |
被动方最后发送的应用数据字节序号+1 |
| 确认号(ack) | u+1 |
关键值:确认主动方的FIN |
| 标志位 | ACK=1,其他全0 |
纯确认报文 |
应用层机会:
在 CLOSE_WAIT 状态下,应用可以继续读取接受缓冲区、如果还有数据要发给主动发,现在可以发送、这是发送剩余数据的最后机会
准备关闭:应用层最终也会调用 close()
主动关闭方:
收到 ACK:确认对方已经收到自己的 FIN
状态变化:从 FIN_WAIT_1 进入 FIN_WAIT_2(等待对方关闭)
能力变化:完全不能发送数据,但仍可以接收对方发来的数据,仍处于单向接受状态
下一步等待:等待对方的 FIN 报文
第三次挥手:被动关闭方发起关闭
被动关闭方:
触发条件: 应用层调用 close() (数据已经发送完)
发送报文:构造 FIN 报文
ACK为1:再次确认"我知道你之前要关闭了"(ACK=1)
序列号可变(取决于 CLOSE_WAIT 期间是否发送数据)
确认号与第二次挥手相同(主动方在发送FIN后停止发送新数据)
| 字段名 | 值 | 说明 |
|---|---|---|
| 序列号(seq) | w |
被动方当前最后发送的字节序号+1 |
| 确认号(ack) | u+1 |
关键值:依然确认主动方的初始FIN |
| 标志位 | FIN=1, ACK=1,其他全0 |
这是重点! |
状态变更:从 CLOSE_WAIT 进入 LAST_ACK(最终确认)
最后清理:关闭自己的发送缓冲区,等待对方的最终确认
超时准备:如果没有收到 ACK,会重传 FIN
主动关闭方:
收到 FIN:知道对方也要关闭了
确认收到:需要响应这个 FIN
状态保持: 仍然在 FIN_WAIT_2状态(尚未变化)
准备确认:立即准备发送最后的 ACK
理解现状:知道双方都已同意关闭
第四次挥手:主动关闭方最终确认
主动关闭方:
立即响应:收到 FIN 后立即发送 ACK
发送报文:构造 ACK 报文
| 字段名 | 值 | 说明 |
|---|---|---|
| 序列号(seq) | u+1 |
关键值:自己的初始FIN消耗了序列号u |
| 确认号(ack) | w+1 |
关键值:确认被动方的FIN |
| 标志位 | ACK=1,其他全0 |
纯确认报文 |
状态变更:从 FIN_WAIT_2 进入 TIME_WAIT(时间等待) ,启动 2MSL 定时器(MSL = 一个TCP报文段在网络中能够存活的最长时间)
TIME_WAIT 期间:不能立即关闭连接,如果收到对方的 FIN 重传(因ACK丢失),会重发 ACK,确保网络中的旧报文都过期
最终关闭:2MSL 超时后,进入 CLOSED 状态
被动关闭方:
收到 ACK:确认对方已经收到自己的 FIN
状态变更:从 LAST_ACK 进入 CLOSED
立即释放:立即释放所有资源,端口可以立即重用,连接完全终止
完成关闭:不会再有这个连接的任何状态
双方对比总结
| 挥手阶段 | 主动关闭方 | 被动关闭方 |
|---|---|---|
| 第一次挥手 | 发送FIN 进入FIN_WAIT_1 |
收到FIN 进入CLOSE_WAIT |
| 第二次挥手 | 收到ACK 进入FIN_WAIT_2 |
发送ACK 保持CLOSE_WAIT 可发送剩余数据 |
| 第三次挥手 | 收到FIN 保持FIN_WAIT_2 |
发送FIN 进入LAST_ACK |
| 第四次挥手 | 发送ACK 进入TIME_WAIT 等待2MSL |
收到ACK 进入CLOSED |
六、完整过程

更多推荐


所有评论(0)