网络那些事儿2-数据途经网卡发生的事儿3

上一节我们讲到网络的连通和数据的收发操作,这次讲一下数据收发完成后,网络的断开操作。

从套接字的创建、收发数据到断开连接、删除套接字这是一个完整的网络传输过程。当应用程序判断到数据已经发送完毕后,就会发起断开连接的操作,需要注意的是连接的断开操作正常情况下是有发送发发起的,当然这不排除接收方因为异常情况断开连接。而且不同的应用程序发起断开连接的时机也会不同,而且基于HTTP1.0版本和HTTP1.1版本,断开的时机也是不同的。比如WEB服务器在HTTP1.0写一下响应完客户端的请求后就会发起断开连接的操作,但是在HTTP1.1版本中服务端返回相应消息后客户端会再发送一次请求消息,服务端没有响应后由客户端发起断开操作;另外当一些数据有客户端发送完后客户端主动发起断开连接的操作,这种情况也是存在的,总之就是协议栈允许任何一方发起断开过程,由数据发送方发起断开过程。

这里以服务端发起断开连接操作为例说明一下断开连接的过程。服务端会调用Socket库的close程序,随后服务端的协议栈就会生成包含断开连接相关信息的TCP头(控制位中的FIN置为1),然后委托IP模块将包含有该TCP头的数据包发送到客户端,服务端的套接字这时也会记录下断开操作的相关信息。

客户端收到服务端发来的包含有断开连接的TCP的数据包时,根据TCP头部的FIN位判断出服务端要断开连接,此时客户端协议栈会将自己的套接字标记为进入断开操作的状态,然后返回给服务端一个包含ACK号的数据包。这些操作结束后,协议栈就等待应用程序来取数据。当应用程序调用Socket的read函数读取数据时,协议栈不会给应用程序返回数据,而是告知应用程序来自服务端的数据已经接收完了,然后客户端应用程序会调用close来结束数据收发操作,在调用close的同时,客户端会生成一个包含FIN位为1的TCP包,并委托IP模块发送给服务端。服务端收到后返回一个ACK号,到此,客户端和服务端通信就完全结束了。
断开连接
通信结束后还有一个问题,客户端和服务端的套接字哪里去了?其实他们还都存在,只不过不再使用,因此需要删除,防止内存资源浪费。不过套接字不是在通信结束后立即删除,而是会等待一段时间后再删除,这是为了防止误操作,误操作的原因由很多种可能性,比如客户端返回给服务端的ACK信号丢失了。正常情况下,断开连接的操作应该是这样的:

  • 客户端发送FIN
  • 服务端返回ACK
  • 服务端发送FIN
  • 客户端返回ACK

如果客户端返回的ACK信号在传输过程中丢失了呢?服务按没有接收到ACK信号可能会重传一次FIN,如果在第一次ACK丢失后客户端就把套接字删除了,那么套接字中保存的控制信息也就消失了,套接字绑定的端口就会被释放掉,如果别的程序这个时候绑定到这个套接字,那么服务端重传的FIN就会被这个程序接收到,当前这个新建的程序判断收到的是包含FIN位1的TCP数据包就会执行断开操作,这是不合理的,我们的断开操作不是针对这个新建的程序的。所以连接断开后套接字不会立即被删除,具体等待多久删除套接字,这个跟网络包的重传机制有关,网络中数据包丢失后重传一般要持续几分钟,如果重传几分钟后仍然无效,就会停止重传,一般来说,套接字也是在断开连接后的几分钟被被删除掉。

数据收发小结

数据收发第一步是创建套接字,一般来说,服务端在启动时就会创建好套接字进入监听状态,等待客户端的连接,客户端由应用程序触发连接操作,创建套接字,这个阶段没有网络包的传输。

客户端创建完套接字后,向服务端发起连接操作,操作的过程:客户端生成一个SYN位为1的TCP包头放入网络包中,这个TCP头中还包括客户端给服务端发送数据时使用的初始序列号以及服务端向客户端发送数据时需要用到的窗口的大小,由IP模块发送给服务端。服务端接收到这个网络包后,返回一个SYN位为1的TCP包,这个包内包含了序号和窗口大小,还包括确认收到客户端数据包的ACK号。服务端发送的网络包到达客户端后,客户端再想服务端返回一个包含ACK号的TCP包,这时连接完成,接下来进入数据收发阶段。

以WEB为例,数据收发阶段,客户端应用程序向服务端请求消息,TCP根据数据包的大小决定是否分片发送数据包,分片的情况下会在每片数据包前面加上响应的TCP头部信息,头部中包含序列号(表示发送的是从第几个字节开始的数据),然后由IP模块发送给服务端。服务端收到数据后,返回一个ACK号给客户端。起始阶段,服务端不断接受数据,然后将数据放入服务端开辟的缓冲区中,同时服务端的应用程序会不断从缓冲区中取出数据,当服务端的应用程序从缓冲区中取出数据时,服务端会通知客户端将缓冲区剩余空间告知客户端,也就是窗口大小。服务端接收到客户端请求消息时,会向客户端返回响应消息,跟上面的过程正好相反。

上面的数据传输过程结束后,客户端就要断开与服务端的连接了。这里以服务端响应消息发送完毕作为数据传输结束的标志,服务端发送完相应消息,然后进行断开连接操作,生成一个包含FIN位为1的TCP包发送给客户端,客户端接收到这个包后返回一个ACK响应。然后客户端应用程序会调用close再向服务端发送一个包含FIN位为1的TCP包,服务端返回一个ACK给客户端,然后连接断开,大约几分钟后,客户端和服务端的套接字被删除。