2.4 从服务器断开并删除套接字
数据发送完毕后断开连接
首先,服务器一方的应用程序会调用Socket库的close程序。然后,服务器的协议栈会生成包含断开信息的TCP头部,具体来说就是将控制位中的FIN比特设为1。接下来,协议栈会委托IP模块向客户端发送数据(图2.12①)。同时,服务器的套接字中也会记录下断开操作的相关信息。
接下来轮到客户端了。当收到服务器发来的FIN为1的TCP头部时,客户端的协议栈会将自己的套接字标记为进入断开操作状态。然后,为了告知服务器已收到FIN为1的包,客户端会向服务器返回一个ACK号(图2.12②)。这些操作完成后,协议栈就可以等待应用程序来取数据了。
过了一会儿,应用程序就会调用read来读取数据[插图]。这时,协议栈不会向应用程序传递数据[插图],而是会告知应用程序(浏览器)来自服务器的数据已经全部收到了。根据规则,服务器返回请求之后,Web通信操作就全部结束了,因此只要收到服务器返回的所有数据,客户端的操作也就随之结束了。因此,客户端应用程序会调用close来结束数据收发操作,这时客户端的协议栈也会和服务器一样,生成一个FIN比特为1的TCP包,然后委托IP模块发送给服务器(图2.12③)。一段时间之后,服务器就会返回ACK号(图2.12④)。到这里,客户端和服务器的通信就全部结束了。
删除套接字
和服务器的通信结束之后,用来通信的套接字也就不会再使用了,这时我们就可以删除这个套接字了。不过,套接字并不会立即被删除,而是会等待一段时间之后再被删除。
等待这段时间是为了防止误操作,引发误操作的原因有很多。
可能存在服务器或客户端 fin 丢失,重发。如果断开连接,立即删除套接字,就无法重新发送 fin 了。
数据收发操作小结
创建套接字之后,客户端会向服务器发起连接操作。首先,客户端会生成一个SYN为1的TCP包并发送给服务器(图2.13①)。这个TCP包的头部还包含了客户端向服务器发送数据时使用的初始序号,以及服务器向客户端发送数据时需要用到的窗口大小[插图]。当这个包到达服务器之后,服务器会返回一个SYN为1的TCP包(图2.13②)。和图2.13①一样,这个包的头部中也包含了序号和窗口大小,此外还包含表示确认已收到包①的ACK号[插图]。当这个包到达客户端时,客户端会向服务器返回一个包含表示确认的ACK号的TCP包(图2.13③)。到这里,连接操作就完成了,双方进入数据收发阶段。
以Web为例,首先客户端会向服务器发送请求消息。TCP会将请求消息切分成一定大小的块,并在每一块前面加上TCP头部,然后发送给服务器(图2.13④)。TCP头部中包含序号,它表示当前发送的是第几个字节的数据。当服务器收到数据时,会向客户端返回ACK号(图2.13⑤)。在最初的阶段,服务器只是不断接收数据,随着数据收发的进行,数据不断传递给应用程序,接收缓冲区就会被逐步释放。这时,服务器需要将新的窗口大小告知客户端。当服务器收到客户端的请求消息后,会向客户端返回响应消息,这个过程和刚才的过程正好相反(图2.13⑥⑦)。
服务器的响应消息发送完毕之后,数据收发操作就结束了,这时就会开始执行断开操作。以Web为例,服务器会先发起断开过程[插图]。在这个过程中,服务器先发送一个FIN为1的TCP包(图2.13⑧),然后客户端返回一个表示确认收到的ACK号(图2.13⑨)。接下来,双方还会交换一组方向相反的FIN为1的TCP包(图2.13⑩)和包含ACK号的TCP包(图2.13⑪)。最后,在等待一段时间后,套接字会被删除。