前言
TCP 是全双工通信:
存在独立的上行通道(你发我收)、下行通道(我发你收),关闭连接时,两个方向的通道需要分别关闭,这就是「建立连接 3 次、断开连接 4 次」的根本原因。
复用上一篇讲的统一规则
1.控制标志位:
FIN:关闭本方发送通道,请求断开连接
ACK:确认报文,纯 ACK 不消耗序列号
2.序号消耗规则(和 SYN 完全一致):
SYN / FIN 控制报文:无数据,固定占用 1 个序列号
纯 ACK 报文不占用序列号
3.确认号万能公式:
ack=对方上一次seq+对方占用的字节/控制位数量
只要对方发了 FIN/SYN,我方确认号一定 +1
角色定义
主动关闭方:先调用 close() / 主动发起断开(可以是客户端,也可以是服务端,不固定)
被动关闭方:收到 FIN 后,被动响应关闭
初始状态
双方数据传输,都处于ESTABLISHED稳定通信状态
四次挥手整体流程总览

第一次挥手:主动方 → 被动方:我没数据要发了,请求关闭我的发送通道(FIN)
第二次挥手:被动方 → 主动方:收到你的关闭请求,我知道了(ACK 确认)
第三次挥手:被动方数据发完 → 主动方:我也没数据要发了,我也要关闭我的发送通道(FIN)
第四次挥手:主动方 → 被动方:收到,确认关闭,连接彻底销毁
分布讲解
我们假设:
主动关闭方为A
被动关闭方为B
A发送 FIN 序列号:x
B传输数据使用序列号:y
第一次挥手:A -> B(FIN报文)
主动方应用层调用 close() 系统调用,告知内核:不再发送任何数据。
TCP报文字段:
标志位:FIN=1,ACK=1
序列号:seq = x
无应用层数据
FIN 消耗 1 个序列号
主动方 A从 ESTABLISHED→FIN_WAIT_1(等待对方对 FIN 的 ACK 确认),
被动方 B保持 ESTABLISHED。
A 单方面关闭自己的写通道,A 再也不会发数据;但 TCP 全双工:A 仍然可以接收 B 发来的数据。
第二次挥手:B -> A(ACK确认报文)
B 内核收到 A 的 FIN 报文,校验合法,立即回复确认。
TCP报文字段:
标志位:ACK=1
确认号:ack=x+1(因为 A 的 FIN 占用 1 个序号,必须 + 1)
序列号:seq=y
无 FIN、无数据,纯 ACK 不消耗序号
被动方 B:ESTABLISHED→CLOSE_WAIT(半关闭状态,等待本机应用层调用 close)
主动方 A:FIN_WAIT_1→FIN_WAIT_2
此时进入TCP 半关闭状态:
A:不能发数据,但还能收 B 的数据
B:正常收发完全不受限,B 可以继续向 A 发送剩余业务数据
这一步只确认关闭 A 的写通道,不关闭 B 的写通道( 这就是为什么不能把第二次、第三次挥手合并的核心原因)。
第三次挥手:B->A(FIN+ACK 报文)
B 的应用层把剩余所有数据全部发送完毕,调用 close(),告知内核关闭自己的发送通道。
TCP报文字段:
标志位:FIN=1,ACK=1(连接一旦完成三次握手、进入 ESTABLISHED 之后,后续所有数据报文、挥手报文,一律必须 ACK=1,第三次挥手的 ACK=1 不是再确认一次,只是协议强制标配)
序列号:seq=w
确认号:ack=x+1(沿用上次确认号不变)
被动方 B:CLOSE_WAIT→LAST_ACK(最后一次确认,等待 A 回复最终 ACK)
主动方 A:保持 FIN_WAIT_2
B 也关闭了自己的写通道;至此,上下两个方向的发送通道全部关闭,双方都不能再发数据。
第四次挥手A->B(最终ACK报文)
A 收到 B 的 FIN 报文,回复最后一次确认。
TCP报文字段:
标志位:ACK=1
确认号:ack=w+1
序列号:seq=x+1
被动方 B:收到该 ACK 后 LAST_ACK→CLOSED(B 直接释放连接资源,彻底关闭)
主动方 A:FIN_WAIT_2→TIME_WAIT
主动关闭方不会立刻关闭,必须在 TIME_WAIT 状态停留2MSL时长。
问题
1:为什么断开连接需要四次挥手,不能像建连一样三次?
建立连接:
双方 SYN 同步 + ACK 确认可以合并为一个报文(第二次握手:SYN+ACK),所以 3 次。
断开连接:
TCP 全双工,两个方向关闭独立:
第一步:A 发 FIN 关闭自己写通道
第二步:B 先单独 ACK 确认(B 可能还有数据要发,不能立刻关)
第三步:B 数据发完,再发 FIN 关闭自己写通道
第四步:A 最终 ACK 确认
什么情况下会变成三次挥手断开?
当被动方没有任何剩余数据时:
B 收到 FIN 后,会把 ACK + FIN 合并为一个报文发送,四次挥手简化为三次挥手,属于特殊场景。
2.TIME_WAIT 状态为什么必须等待 2MSL?
MSL:报文最大生存时间,Linux默认30s,RFC标准60s。
2MSL两大核心作用:
1.保证最后一次 ACK 可靠到达对方
如果第四次挥手的 ACK 丢失,B 超时会重发 FIN;
A 处在 TIME_WAIT 仍可以重发 ACK,避免 B 一直重传。
2.等待网络中残留迟到报文彻底过期
等待过往连接的延迟、迷路报文全部失效;
防止旧连接的无效报文,干扰新建立的相同端口连接,造成数据错乱。
3.为什么是主动关闭方进入 TIME_WAIT,不是被动方?
如果被动方留 TIME_WAIT:
服务端一般固定端口,频繁重启服务会因大量 TIME_WAIT 端口占用,无法立刻监听;
主动方一般是客户端、随机端口,TIME_WAIT 对服务端影响最小,设计更合理。
4:大量 CLOSE_WAIT 状态堆积是什么问题?
原因:被动关闭方 应用层代码漏写 close ()
过程:
内核收到对方 FIN、回复了 ACK,进入 CLOSE_WAIT;
但业务代码不调用 close,内核永远不会发第三次挥手的 FIN;
后果:连接僵死、占用文件描述符,服务卡死。
5:四次挥手过程中可以传输数据吗?
第二次挥手后,B 处于 CLOSE_WAIT,依然可以正常发送业务数据给 A,直到业务数据全部发完才会发 FIN。
补充
暴力断开 RST 报文(异常场景)
正常是四次挥手优雅关闭;
如果出现:进程崩溃、端口直接关闭、连接超时、非法报文:
系统会直接发送 RST 重置报文,强制释放连接,跳过四次挥手,直接强行断开,不保证数据完整性。