TCP连接的优雅关闭

本文主要参照MSDN文档,以windows为例,讲述TCP连接的优雅关闭,Linux也是同理

本文主要讨论TCP连接中socket的优雅关闭,在实际使用时,明白关闭一个socket连接(socket connection)及关闭一个socket自身的区别是很重要的。

关闭一个socket连接(shutdown a socket connection)需要连接双方进行信息交互,这个交互即是关闭序列shutdown sequence,这中序列主要分为两种:优雅退出及强制退出。在优雅关闭中,任何已经在队列中但尚未发出的数据可以在连接被真正关闭之前被发出,而在强制退出中,任何未被发送的数据都会被丢弃。同时关闭序列也可以用来给相关应用程序发送FD_CLOSE指令,用以表示正在进行关闭连接操作。

而关闭一个socket本身(close a socket)是将这个socket删除,使得之后没有任何应用可以再使用该socket。

在Windows编程中,shutdownWSASendDisconnect这两个函数都可以用来初始化一个关闭序列,而closesocket这个函数用以释放一个socket句柄并回收对应的资源。然而容易造成疑惑的是,再调用closesocket时,如果还没有关闭序列产生,该函数也会隐式生成一个关闭序列。事实上,依赖此特性来使用closesocket来生成一个关闭序列和回收句柄已经成为一个非常常见的用法。为了简化使用,socket编程接口会提供相应机制供使用者指定是产生优雅关闭序列还是强制关闭序列,以及closesocket函数是否应等待(linger,不是立即关闭)优雅关闭的完成。

通过为SO_LINGERSO_DONTLINGER这两个选项设置合适的值,closesocket可以具有以下行为:

  • 生成强制关闭序列,closesocket函数立即返回
  • 优雅关闭,closesocket函数会等待优雅关闭完成或者直到超时时间,如果超时后优雅关闭还未完成,就会产生一个强制关闭序列,此后函数会返回
  • 优雅关闭,但是函数立即返回,在后台执行优雅关闭任务,这也是windows及Linux中的默认行为,然而这种模式下,应用程序是无法知道优雅关闭是否完成了

其中,linger的使用方式及具体含义可参考SO_LINGER选项使用方式

为了减少关闭TCP连接过程中可能产生的问题,我们应该尽量避免依赖closesocket来隐式调用shutdown,而是现实调用shutdown或者WSASendDisconnect来关闭连接。这样可以是对端应用程序收到FD_CLOSE指令,标识所有数据都已发送。

总体来说,如下表所示展示了应该如何优雅关闭一个TCP连接:

客户端侧 服务端侧
1. 调用shutdown(s, SD_SEND)来关闭一个连接,同时表明自身再无数据需要发送
2. 接收到FD_CLOSE事件,表示有优雅关闭正在进行,同时所有数据都已经达到
3. 将服务端待发送数据都发送给客户端
(时间上同右侧无必然联系)获取到FD_READ事件 4. 调用shutdown(s, SD_SEND),表明服务端以无数据需要发送
5. 收到FD_CLOSE事件 调用closesocket(时间上同左侧无必然联系)
调用closesocket

Reference