写在前面
今天我们来图解一下TCP的四次挥手、深度解析为什么是四次?
上一片文章我们已经介绍了TCP的三次握手
解析四次挥手
数据传输完毕之后,通信的双方都可释放连接。现在客户端A和服务端B都处于ESTABLISHED
状态。
第一次挥手
客户端A的应用进程先向TCP发出连接释放报文段,并停止再发送数据,主动关闭TCP连接。客户端A把链接释放报文段首部的终止控制位FIN
置为1
,其序号为seq=u
,它等于前面以传送过的数据的最后一个字节的序号加1
。这时候A进入了FIN-WAIT-1(终止等待1)
状态,等待B的确认。
注意:TCP规定:FIN报文段即使不携带数据,也消耗掉一个序号!!
第二次挥手
服务端B收到链接释放报文段后即发出确认,确认号是ack = u + 1
,而这个报文段自己的序号v
是等于B前面已传送过的数据的最后一个字节的序号加1
,然后B就进入CLOSE-WAIT(关闭等待)
状态。
TCP服务器进程这时应通知应用进程,因而从A到B这个方向的链接就释放了。这时的TCP链接处于半关闭状态,即A已经没有数据要发送了,但B若发送数据,A仍要接收,也就是说,从B到A这个方向的连接并未关闭,这个状态要维持一段时间。
第三次挥手
A收到来自B的确认后,就进入了FIN-WAIT-2(终止等待2)
状态满等待B发出的连接释放报文段。若B已经没有要向A发送的数据,其应用进程就通知TCP释放连接,这时B发出的连接释放报文段必须使FIN = 1
,现假定B的序号为w
,B还必须重复上次已发送过的确认号ack = u + 1
.这时B就进入LAST-ACK(最后确认)
状态,等待A的确认。
第四次挥手
A在收到了B的链接释放报文段后,必须对此发出确认。在确认报文段中把ACK置1
,确认号ack=w+1
,而自己的序号是seq=u+1
(根据TCP标准,前面发送过的FIN报文段要消耗一个序号),然后进入到TIME-WAIT
(时间等待)状态。
注意: 现在TCP连接还没有还没有释放掉
。必须经过时间等待计时器设置的时间2MSL
后,A才能进入CLOSED状态。
MSL叫做最长报文段寿命,RFC793建议设在两分钟,但TCP允许不同的实现可以根据具体情况使用更小的MSL值。
为什么要2MSL时间呢
1. 为了保证A发送的最后一个ACK报文段能够到达B。
这个ACK报文段有可能丢失,因而使处于在LAST-ASK状态的B收不到对己发送的FIN-ACK报文段的确认。B会超时重传这个FIN+ACK报文段,而A就能在2MSL时间内收到这个重传的FIN+ACK报文段。接着A重传一次确认,重新启动2MSL计时器,最后的A和B都正常进入CLOSED状态。
如果A在TIME-WAIT状态不等待一段时间,而是发送完ACK报文段后立即释放连接,那么就无法收到B重传的FIN+ACK报文段,因而也不会再发送一次确认报文段。
2. 防止了已失效的连接请求报文段。
A 在发送完最后一个ACK报文段后,在经过时间2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失,这样就可以使下一个连接中不会出现这种旧的连接请求报文段。B只要收到了A发出的确认,就进入CLOSED状态。同样,B在撤销相应的传输控制块TCB后,就结束了这次的TCP连接,所以B结束TCP连接的时间要比A早一些的。
为什么是四次?
首先我们可以确定一点TCP是全双工的,四次挥手是保证了双方都知道并且都已经断开连接。
四次挥手过程的目的是确保数据在关闭过程中能够被完整传输,同时也允许延迟的数据包在关闭后仍然能够被接收。TIME_WAIT 状态的存在是为了处理可能的重复数据包,以确保连接的完全关闭。
⚠️ 注意一点:关闭连接时,客户端A向服务端B发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据
。
问题又来了:为什么握手要三次,挥手却要四次呢?
服务器B收到客户端A的 FIN 报文时,先回一个 ACK 应答报文,而B可能还有数据需要处理和发送,等B不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。
握手的时候并没有数据传输
,所以服务端的 SYN 和 ACK 报文可以一起发送,但是挥手的时候有数据在传输,服务端B通常需要等待完成数据的发送和处理, 所以服务端B的 ACK 和 FIN 一般都会分开发送
,从而比三次握手导致多了一次。
参考
[1] https://segmentfault.com/a/1190000040571193