硬性关闭TCP连接

硬性关闭是指:使用非正常的手法立即终止当前TCP连接,只求关闭,不考虑后果,丢失数据也无所谓。这种方式在处理一些紧急状况时或许是必要的。

正常的关闭流程是在连接双方的配合下经历四次握手完成的。应用程序里的操作就是调用close接口。

对socket实施close操作,实际上会产生一个FIN报文段,这个段当然也要遵从TCP的规则,它会在发送缓冲区中排队。显然,调用close的一方要想实际发出这个FIN段,当然要先将排在它之前的那些字节发出去才行。也就是说,调用close后,到实际发出FIN之前,发送缓冲区中的数据已经发送到对端了。

上面说的是正常的关闭,用close接口即可发起。非正常关闭,也是用这个接口,只不过要配置一下socket的LINGER选项。

linger有“苟延残喘”的意思,close的缺省行为是让连接“苟延残喘”直至发送缓冲区中的数据均被发送到对端后再死亡。启用了socket的LINGER选项,就可以通过参数来设定这个“苟延残喘”的时间,若设定为0,则连接立即“死亡”。具体做法就是TCP向对端发送一个RST报文段,并清除本端的连接状态,对端TCP收到该报文段后不予理会(不进行ACK),当应用进行recv操作即产生“Connection reset by peer”错误。

socket(7) manpage中有对SO_LINGER选项的介绍。它的配置参数有两个字段,放在一个叫做linger的结构体里:

struct linger {
    int l_onoff;
    int l_linger;
};

显然,lonoff是开关,0是不启用,非0是启用。第二个参数l_linger用来指定“苟延残喘”的时长(时间单位依赖于实现,Linux单位为秒)。

以下是设置不同参数时所产生的效果(实际测试结果),系统环境是Linux(在其他系统上可能会表现出不同的行为):

一、l_onoff置0

不启用选项。不对close的行为造成任何影响。

二、l_onoff非0且l_linger大于0(假设值为n)

发送缓冲区中有残留数据的情况。

  • 对阻塞式socket而言,调用close会发生阻塞。当残留数据被全部发送至对端并得到ACK时,close返回。否则,close会在阻塞时长满n秒时返回,返回后,TCP仍会先将残留的数据全部发送至对端,然后再使用FIN段结束连接。

    这个行为似乎只是将close的缺省行为推迟了n秒进行,如果n秒过后发送缓冲区中仍有数据残留的话。

  • 对非阻塞式socket而言,推测close会立即返回。但实际测试发现,它也是会阻塞的。经测试发现,该场景下出现的行为与上面的一样。

发送缓冲区中无残留数据的情况。

  • 对阻塞式socket而言,调用close立即返回,走正常关闭流程。

  • 对非阻塞式socket而言,close同样不会阻塞,走正常关闭流程。

三、l_onoff非0且l_linger置0

不论socket是阻塞还是非阻塞式,发送缓冲区是否有残留数据,调用close均立即返回。TCP会丢弃本端发送缓冲区中的残留数据,同时向对端发送一个RST报文段。即:所谓的硬性关闭(异常关闭)连接。

小结

缺省情况:close调用立即返回,正常关闭流程。

启用LINGER {非0, n(n>0)}:发送缓冲区若无残留数据close立即返回。若有残留数据,close会阻塞最多n个时间单位,超过n以后,若还有残留数据存在的话,将执行缺省的close动作完成连接关闭。

启用LINGER {非0, 0}:丢弃残留数据,向对端发RST段。

说明一下,这里使用的是Linux(Debian6.0)系统,在其它系统上测试的结果可能会有差别。