数据传输完成,协议栈在设计上允许任何一方先发起断开过程。
以服务器一方发起断开过程为例。
首先,服务器一方的应用程序会调用Socket库的close程序。然后,服务器的协议栈会生成包含断开信息的TCP头部,将控制位中的FIN比特设为1。接下来,协议栈会委托IP模块向客户端发送数据。同时,服务器的套接字中也会记录下断开操作的相关信息。
四次挥手当客户端收到服务器发来的FIN为1的TCP头部时,客户端的协议栈会将自己的套接字标记为进入断开操作状态。然后,为了告知服务器已收到FIN为1的包,客户端会向服务器返回一个ACK号。这些操作完成后,协议栈就可以等待应用程序来取数据了。应用程序就会调用read来读取数据。这时,协议栈不会向应用程序传递数据,而是会告知应用程序(浏览器)来自服务器的数据已经全部收到了。
客户端应用程序会调用close来结束数据收发操作,这时客户端的协议栈也会和服务器一样,生成一个FIN比特为1的TCP包,然后委托IP模块发送给服务器。一段时间之后,服务器就会返回ACK号。到这里,客户端和服务器的通信就全部结束了。
和服务器的通信结束之后,用来通信的套接字就可以删除了。不过,套接字并不会立即被删除,而是会等待一段时间之后再被删除。
客户端先发起断开,则断开的顺序如下:
1)客户端发送FIN
2)服务器返回ACK号
3)服务器发送FIN
4)客户端返回ACK号
如果最后客户端返回的ACK号丢失了,结果会如何呢?这时,服务器没有接收到ACK号,可能会重发一次FIN。
如果这时客户端的套接字已经删除了,会发生什么事呢?套接字被删除,那么套接字中保存的控制信息也就跟着消失了,套接字对应的端口号就会被释放出来。
这时,如果别的应用程序要创建套接字,新套接字碰巧又被分配了同一个端口号,而服务器重发的FIN正好到达,会怎么样呢?本来这个FIN是要发给刚刚删除的那个套接字的,但新套接字具有相同的端口号,于是这个FIN就会错误地跑到新套接字里面,新套接字就开始执行断开操作了。之所以不马上删除套接字,就是为了防止这样的误操作。
至于具体等待多长时间,这和包重传的操作方式有关。网络包丢失之后会进行重传,这个操作通常要持续几分钟。
如果重传了几分钟之后依然无效,则停止重传。在这段时间内,网络中可能存在重传的包,也就有可能发生前面讲到的这种误操作,因此需要等待到重传完全结束。协议中对于这个等待时间没有明确的规定,一般来说会等待几分钟之后再删除套接字。
本文摘取自周自恒翻译的户根勤编写的《网络是怎样连接的》
网友评论