前面进行“三次握手”建立连接后,当客户端的数据发送完毕,它就会要求与服务器端断开连接,那么就要进行“四次挥手”进行连接的释放。

  注意,此处所谓的“客户端”与“服务器端”,只是为了方便标识连接的双方,即确认哪一方是“要求断开连接”的主动方,哪一方是“要求断开连接”的被动方。事实上任何一方都可能在发送完数据后要求与另一方断开连接。

1、“四次挥手”过程

  如下图:

TCP协议“三次握手”与“四次挥手”详解(下)-LMLPHP

  “四次挥手”的具体过程如下:

1)“第一次挥手”:首先,客户端已经发送完数据,想要释放连接(客户端是释放连接的主动方),向服务器端发送一段TCP报文,其中:

  i)标记位 FIN=1:表示这个TCP请求是“请求释放连接”;

  ii)报文的序号 seq=u:u 等于前面客户端已经传送的数据的最后一个字节的序号加1;

  iii)客户端由“ESTABLISHED”连接建立状态,进入“FIN-WAIT-1”终止等待状态1。此时客户端不会再向服务器端发送数据。

需要注意,FIN报文虽然不携带数据,但是但是它会消耗一个字节序号。即客户端第一次发送的FIN报文 seq=u,报文会消耗一个序号,那么客户端下次发送的报文应该从 seq=u+1 开始发送。

2)“第二次挥手”:服务器端接收到从客户端发出的TCP报文之后,确认了客户端想要释放连接,会发送回一个TCP报文,其中:

  i)标记位为ACK=1:表示“服务器端告知客户端,自己已经接收到客户端发送的释放连接的请求”;

  ii)报文序号 seq=v:服务器端到客户端的连接还没有关闭,服务器端还会向客户端发送数据包,这个报文序号为 v;

  iii)确认号ack = u+1:表示希望客户端下一个报文的序号是 u+1,即希望客户端下一个报文从序号为 u+1 的字节开始发送。我们知道客户端第一个报文 seq=u,且该报文为FIN报文,占一个序号,那么客户端下一个报文就应该从 u+1 开始发送。

  iv)服务器端结束 “ESTABLISHED” 连接阶段,进入“CLOSE-WAIT” 关闭等待状态;

此时从 客户端到服务器端 这个方向的连接就被释放,TCP连接处于“半关闭状态” 。 此时客户端不会再向服务器端发送数据,但是服务器端可能还会想客户端发送数据。

  v)客户端收到从服务器端发出的TCP报文之后,确认了服务器端收到了客户端发出的释放连接请求,随后客户端结束“FIN-WAIT-1”终止等待状态1,进入“FIN-WAIT-2”终止等待状态2。

总结:前两次挥手,既让服务器端知道了客户端想要释放连接,也让客户端知道了服务器端知道了自己想要释放连接。

3)“第三次挥手”:客户端经过 “ClOSE-WAIT” 关闭等待 状态后,它要发送给服务器端的数据也发送完毕,即它做好了释放服务器端到客户端连接的准备,就会想客户端发送一段FIN的TCP报文:

  i)标记位 FIN=1,ACK=1:表示“服务器端告知客户端,自己已经做好释放连接的准备”;

  ii)报文序号 seq=w:关闭等待阶段,服务器端可能又向客户端发送了数据,因此此时 seq 不是v,而是最新的数据 w;

  iii)确认号 ack=u+1:还是希望客户端下一次发送的报文序号为 u+1;

  iv)发送“第三次挥手”报文后,服务器端进入“LAST-ACK” 最后确认阶段。此后,服务器端再也无法向客户端发送数据。

注意,第三次挥手发送的是FIN的报文,没有数据但是会占一个序号,即下一次服务器端发送的报文序号 seq=w+1。

4)“第四次挥手”:客户端接收到服务器端“第三次挥手”的报文后,确认服务器端已经做好断开连接的准备,会向客户端发送“第四次握手”的报文:

  i)标记位ACK=1:表示“客户端已经知道服务器端做好释放连接的准备”;

  ii)ack = w+1:将收到服务器端报文的 seq+1,作为自己的ack。表示希望服务器端下次发送的报文的序号为 w+1;

  iii)seq=u+1:将收到服务器端报文的 ack 作为自己的 seq,因为服务器端报文 ack=u+1 表示希望客户端这次发送的报文序号是 u+1,那么客户端这次发送报文的序号就设置为 u+1;

  iv)客户端发送完第四次挥手的报文后,启动等待计时器,等待2MSL后,如果没有收到服务器端新的请求,就进入“CLOSED” 连接关闭状态;

  v)服务器端在收到客户端发送的第四次挥手的报文后,进入进入“CLOSED” 连接关闭状态。

总结:后“两次挥手”既让客户端知道了服务器端准备好释放连接了,也让服务器端知道了客户端了解了自己准备好释放连接了。于是,可以确认关闭服务器端端到客户端方向上的连接了,由此完成“四次挥手”,关闭了连接。

2、三个关键问题

1)为什么“握手”是三次,而“挥手”却是四次?

  对于“三次握手”,在第二次握手的时候,服务器端向客户端发送的报文的标记位包含 SYN=1以及ACK=1,SYN是请求连接标志,表示服务器端同意建立连接;ACK是确认报文,表示告诉客户端,服务器端收到了它的请求报文。即“确认接收”与“同意连接”是在同一次握手中传输的。那么通过三次握手就刚刚好可以建立连接;

  对于“四次挥手”,第二次挥手的时候,服务器端可能还没有做好关闭连接的准备(它可能还有数据要发送给客户端),因此,它不会立即释放连接,会在第二次挥手先返回一个ACK=1,表示已经接受到客户端的断开连接的请求。随后,客户端处理完数据后,会发送第三次挥手报文,其中FIN=1,表示服务器端已经准备好释放连接。因此,释放连接需要经过“四次挥手”。

2)为什么客户端要在发送第四次挥手的报文后等待2MSL的时间才进入CLOSED状态?

  目的是为了确认服务器端会收到客户端发送的“第四次挥手”的ACK确认报文。

  MSL(Max Segment Lifetime): 最长报文段寿命,也就是一个报文在网络中存活的最长时间,一般设置为2分钟。当客户端发出最后的ACK确认报文时,并不能确定服务器端能够收到该段报文。所以客户端在发送完ACK确认报文之后,会设置一个时长为2MSL的计时器。

  在1MSL的时候,如果服务器端没有收到客户端发送的ACK确认报文,就会再次向客户端发送FIN报文,这个报文在1MSL的时间内会到达客户端,此时客户端边知道自己上一次发送的ACK确认报文没有发送到服务器端,于是客户端会再次向服务器端发送ACK确认报文,并将等待计时器重置。

  如果在2MSL的时间内没有接受到服务器端发送回来的FIN报文,说明客户端最后发送的ACK确认报文已经被服务器端接收到,可以关闭连接。因此,事实上客户端会比服务器端更晚进入CLOSED状态。

3)如果已经建立了连接,但是客户端出现故障了怎么办?

  如果客户端发送错误,服务器端还一直保持连接,这个连接并不会传送数据,并且占用了服务器端的资源。服务器端有一个“保活计时器”,服务器端每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若2小时还没有收到客户端的任何数据,客户端就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文客户端仍然没反应,服务器端就认为客户端出了故障,接着就关闭连接。

05-08 15:34