TCP专栏-4.四次挥手
目录
什么是四次挥手
TCP建立连接时候,双方总共会发送3个包,以确保连接建立成功。称之为三次握手。同样的,TCP断开连接时候,双方总共会发送4个包,以确保连接正常断开。而这个过程,称之为四次挥手或四次断开。
四次挥手过程
以客户端主动断开为例进行说明。如图,是我找到的四次挥手示意图:

第一次挥手时,客户端首先发送一个标志位FIN=1的报文,告诉对方,我已经没有数据要发送了,准备断开连接了。发送完该报文后,客户端便等待对方返回确认信息。从发送到接收到对方确认信息的这段时间,客户端状态统称为FIN_WAIT_1。
服务端收到报文后,通过标志位FIN=1判断出来这是一条请求断开连接的报文。随后,服务端便发送一个确认报文,ACK=1。发送后,服务端进入CLOSE_WAIT(关闭等待)状态。而客户端在接收到确认报文后,便进入FIN_WAIT_2状态,等待服务端断开连接。这个过程便是第二次挥手。
注意,此时服务端仍能正常发送数据,客户端仍能正常接收。因为从第一次挥手到现在,只是客户端到服务端的单向连接中断了,服务端到客户端的连接还正常维持,所以客户端还能接受到数据。那什么时候接收不到数据呢?是第三次握手时候。
服务端在第二次握手后,如果还有数据没发送完会继续发送,如果发送完了,可以断开连接了,便会发送一个断开连接的报文,即FIN=1。此后,服务端进入LAST_ACK(最后确认)状态,等待客户端的确认。这个过程就是第三次挥手。
客户端收到服务端请求断开连接的报文后,发送一个确认报文ACK=1作为应答。此时客户端处于TIME_WAIT(时间等待)状态,并在这个状态下等待2MSL时长。这个过程称为第四次握手。
最后,服务端收到从客户端发出的确认报文之后结束 LAST-ACK 阶段,进入 CLOSED 阶段。客户端等待完 2MSL之后,结束 TIME-WAIT 阶段,进入 CLOSED 阶段,由此完成四次挥手。
为什么最后要等待2MSL
MSL全称为Maximum Segment Lifetime,最大报文生存时间,意思就是一个TCP报文在网络中最大的存活时间,超过这个时间,包就会被网络丢弃掉。一个MSL时间大约60s,不同系统可能不一样。那为啥最后客户端还要等等2MSL呢?为啥不是1MSL?为啥不能立即断开?
正常情况下,第三次挥手时,服务端发送一个FIN报文,然后等待客户端确认。客户端收到后发送确认报文,服务端收到这个确认报文后,流程结束。到这里是没有问题的。但现实网络情况复杂,如果服务端没有收到确认报文,它是会重新发送FIN报文的。
假设客户端发送最后一次确认报文后就立即断开,但受网络影响,服务端迟迟没有收到这个确认报文,之后它便重新发送了一个FIN报文。此时FIN抵达客户端后,发现没有对应的连接了,这时候客户端的内核会直接回复一个RST给服务端,强制断开连接,服务端异常报错。显然这种立即断开机制不合理,需要等到服务端收到确认报文后才能断开。
现在让客户端在发送最后一次确认报文后不立即断开,而是等待一段时间,我们再来看看会发生什么情况。此时收到了服务端重传的FIN,由于客户端没有断开连接,依然持有这条连接的四元组,收到重传 FIN 后,便可以重新补发 ACK 给服务端。完成闭环,没有问题。
既然要等待一段时间,那这个时间是多长呢?设置很大行不行,肯定行,但造成资源浪费了。比如,等待2小时,是能确认没问题,但这意味着原来使用的端口在2小时内,不能被其他连接复用。设置很小行不行,比如,等待1s。答案是,也不行。
我们知道,一次重传等待的超时时间约为20-200ms,假设这里使用200ms。这意味着如果发送FIN后,200ms内没有收到对方返回的确认报文,便会重新发送FIN。
这样一看,1s大于200ms,貌似够用了,其实不然。因为你无法保证,服务端重发FIN后,能正常收到第二次的确认报文。假设网络一直不稳定,确认报文一直没收到,服务端便会一直不停的发送FIN。总共要发送多少次才停止呢。15次左右(可以参考前一章)。200*15=3s,也就是说整个重传耗时可能会达到3s中,在这个时间断内,客户端是不能断开的。
到这里,基本可以找出TIME-WAIT等待的最小时间,就是重传的最大时间,一般也就几秒-十几秒。那就设置等待十几秒,满足最大重传时间行不行?不行,因为还有一个安全性,它也要能满足。即,要能做到“防止旧连接滞留的延迟数据包干扰新连接”。
如何理解这句话呢。假设,之前客户端发过一个包给服务端,但受网络延迟影响,这个包直到最后,也就是过了MLS时长后,才抵达服务端。服务端验证后,发现这是个正常包,于是便返回数据给了客户端。同样受网络延迟影响,这个包也经过了MLS后,才到达客户端。一来一回,加在一起,便是2MLS。
如果在小于2MLS时间内,客户端断开连接了,但又马上新建一条完全相同四元组的新连接。如果此时上面的旧包到达客户端了,新连接便会读取这个包数据,轻者产生脏数据,重者干断新连接。所以,为了确认安全,客户端等待的最小时间为2MLS。2MLS的时间,足以保证旧连接所有残留报文都已经不存在。
是否可以三次挥手
是否可以三次挥手就断开连接呢?答案是可以的。
虽然很多教程、书籍或博客等,都说必须4次才行,3次不行。其实,实测是可以的,因为TCP做了很多优化。就比如建立连接时候,最开始也是要4次才行,优化后,中间两次合二为一,即捎带应答。就变成3次了。而之所以能合并,是因为建立连接时,不存在互发消息,不用等待,可以请求和应答一起发送。
而4次挥手,之所以是4次,其中一个关键原因是,第二次和第三次挥手之间,服务端还可能继续发送数据,不能直接合并。必须等待数据发生完后,才能发起中断连接的挥手。所以这里面要求的是必须是4次挥手,没有问题。
那假如,第二次和第三次中间,没有数据发生,不就可以将两个挥手合并为一个挥手了吗。合并完后,就是3次。会有这种情况吗,有,以抓包为例说明。
建立连接,互发数据后,先关闭服务端,此时报文如下:

建立连接,互发数据后,先关闭客户端,此时报文如下:

从上面对比可以发现:
先关闭服务端时,挥手是4次。分别是:1.服务端发送FIN。2.客户端应答ACK。3.客户端发送FIN。4.服务端应答ACK。
而先关闭客户端时,挥手是3次。分别是:1.客户端发送FIN。2.服务端应答ACK+发送FIN。3.客户端应答ACK。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)