🌈个人主页:一条泥憨鱼(欢迎各位大佬莅临)

🎬精选专栏传送门:
❄️《数据结构》 ❄️《AI与Agent那些事》
❄️《从0开始学计算机网络》 ❄️《后端开发》

你有没有过这种经历:跟不太熟的人打电话,到了结尾要挂电话,结果客套话来回说了好几轮——"那先这样?""好,那先这样。""嗯嗯,挂了啊。""好,拜拜。""拜拜。"——明明一句话就能结束的事,非要拉扯四个来回。
TCP 断开连接也是这个德行。它不叫"拜拜",叫"四次挥手"。很多初学者背下了"四次挥手"这个结论,却不明白为什么偏偏是四次,不是三次、不是两次。今天我们就从打电话这个场景说起,把这个问题彻底讲透。
先搞清楚:TCP 连接到底是什么?
要理解挥手,先得知道 TCP 在挥手什么。
想象你要给远方的朋友寄一箱书。你不能把书往楼下一扔就完事——你得先打电话确认对方在家,然后寄出去,对方收到后还得回你一声"收到了"。这个过程,就是 TCP 的"连接"。它的本质,是在通信双方之间建立一条可靠的、有序的、不会丢数据的管道。
注意两个关键词:双向和可靠。
双向的意思是,数据不是只能从 A 流到 B。A 可以给 B 发数据,B 也可以同时给 A 发数据,两边互不耽误。专业说法叫"全双工"。
可靠的意思是,TCP 要保证你发的每一个字节,对方都能按顺序收到。如果丢了,就重传;如果乱了,就排序。为了做到这一点,双方得时刻知道"对方还在不在""我发的数据对方收到没有",所以需要一套精细的确认机制。
连接建立时,双方要来回确认三次(三次握手),确认"你能听清我说话吗?""我能听清你说话吗?"都 OK 了,才开始传数据。
那断开的时候呢?既然连接是双向的,断开也得双向都断干净。这就是四次挥手的根源。
挥手全过程:四次消息逐一拆解
先看一张时序图,把整个过程铺在眼前:

假设客户端主动断开连接。整个过程如下:
第一次挥手:客户端 → 服务器,发送 FIN
FIN 是 Finish 的缩写,意思是"我这边没有数据要发了,我想结束连接"。
注意措辞:客户端说的是"我这边没有数据要发了"。它并没有说"咱俩都别发了"。这是一个非常重要的细节。
第二次挥手:服务器 → 客户端,发送 ACK
服务器收到 FIN 后,回一个 ACK(Acknowledgment,确认),意思是"收到,我知道你发完了"。
但服务器不会立刻也跟着发 FIN。为什么?因为服务器可能还有数据没发完。比如客户端说"我不买了",服务器可能还得把退款流程走完、把最后的账单发过去。所以服务器先回一个 ACK,告诉客户端"你那边可以关了,但我这边还没忙完"。
从这一刻起,客户端到服务器这个方向的数据传输就关闭了,但服务器到客户端的方向仍然开着。这种"只关了一半"的状态,专业术语叫半关闭。
第三次挥手:服务器 → 客户端,发送 FIN
等服务器把手里最后的数据都发完了,它才发出自己的 FIN:"我这边也发完了,可以关了。"
第四次挥手:客户端 → 服务器,发送 ACK
客户端收到服务器的 FIN 后,回一个 ACK:"知道了,那咱就彻底结束。"
到这,四次挥手全部完成,连接关闭。
为什么偏偏是四次,不是三次?
这是全文最核心的问题。
回到打电话的类比。假设你是主动挂电话的那个人,你以为说一句"再见"就完了?不行。电话这头你说了"再见",电话那头的人得回应你"再见",你才能确定对方听到了、可以安心挂断。但对方说完"再见"之后,ta 也得等你回应一个"再见",ta 才敢挂。所以实际上,两个人都各说了一次"再见",各收到了一次"再见"的确认——这就是四次。
技术上的原因更精确:TCP 的两个方向是相互独立的,每个方向的关闭都需要一次 FIN 和一次 ACK 来确认。
- 客户端关闭"客户端→服务器"方向:客户端发 FIN,服务器回 ACK。这是两次。
- 服务器关闭"服务器→客户端"方向:服务器发 FIN,客户端回 ACK。这又是两次。
加起来,正好四次。
那为什么建立连接只需要三次握手呢?因为建立连接时,服务器收到客户端的 SYN 后,可以把自己的 SYN 和 ACK 合并成一条消息一起发回去——"我收到你的请求了(ACK),同时我也请求跟你建立连接(SYN)"——一次搞定两件事,所以三次就够了。
但断开连接时,服务器收到客户端的 FIN 后,没法合并。因为服务器可能还有数据没发完,它不能立刻回 FIN,只能先回一个 ACK 说"我知道了",等数据发完了再单独发 FIN。这两条消息之间隔着不确定的时间,没法合并成一条。
所以:三次握手是因为可以"捎带",四次挥手是因为"不能捎带"。

挥手之后:TIME_WAIT 状态在等什么?
四次挥手完了,连接就关了吗?还没完。
注意第四次挥手——客户端发出最后的 ACK 之后,会进入一个叫 TIME_WAIT的状态,并且要等上 2MSL 的时间才彻底关闭。MSL 是 Maximum Segment Lifetime 的缩写,意思是"一个数据包在网络里最长能活多久",通常是 30 秒到 2 分钟。
为什么要等这么久?两个原因。
第一个原因:怕最后一个 ACK 丢了。
你想,客户端发出的那个 ACK,万一在路上丢了怎么办?服务器没收到,它会以为自己的 FIN 客户端没收到,于是会重发一次 FIN。如果客户端发完 ACK 就立刻关闭连接,那这个重发的 FIN 就没人理了,服务器会一直等不到确认,连接就卡住了。
所以客户端必须等上一段时间——如果服务器的 FIN 重传过来了,它还能再回一个 ACK。2MSL 的时间,足够一个数据包在网络里走一个来回,也就是说,万一 ACK 丢了,服务器重传的 FIN 也能在这段时间内到达,客户端来得及回应。
第二个原因:怕旧连接的延迟报文干扰新连接
假设客户端和服务器断开后,马上又建立了一个新的连接,碰巧用了相同的 IP 和端口。如果旧连接里有一个迟到的数据包,在网络里绕了很久才到达服务器,服务器可能会误以为这是新连接的数据——这就乱套了。
等 2MSL 的时间,就是为了确保旧连接的所有数据包都在网络里彻底消失(每个包最多活 MSL 时间,等 2MSL 就能保证往返都清空),不会污染新连接。

这个 TIME_WAIT 状态,是面试高频考点,也是实际开发中最容易遇到的坑。你在一台高并发的服务器上跑 `netstat`,经常会看到一大堆 TIME_WAIT 状态的连接,就是这个原因。
结尾:看到大量 TIME_WAIT 怎么办?
如果你用 `netstat -an | grep TIME_WAIT | wc -l` 数一下,发现服务器上有成千上万个 TIME_WAIT,别慌——这是正常现象。TIME_WAIT 多,说明主动断开连接的请求多,每个连接关闭后都要等 2MSL 才能彻底消失。
但数量太多确实会占用端口资源。常见处理思路有几个:调小 TIME_WAIT 的等待时间(改内核参数)、开启端口复用(`tcp_tw_reuse`)、或者从架构上让断开连接的一方不集中在同一台服务器上。
不过对初学者来说,第一步不是调参,而是先理解:TIME_WAIT 不是 bug,它是 TCP 为了保证可靠性而故意设计的"等待期"。就像你挂电话前多等几秒,确认对方真的挂了,才放下听筒。
TCP 的挥手设计,处处体现着"可靠"二字的分量。从建立到断开,每一步都在确认、都在等待、都在防备意外。下次再看到 `netstat` 里那一排 TIME_WAIT,你至少能知道,那是 TCP 在讲礼貌——等对方把话说完,确认彼此都听见了,才肯真正离开。

1855

被折叠的 条评论
为什么被折叠?



