在以往的开发中,我会通过 recv() 接口的返回值来判断连接是否断开。但有时候我发现TCP连接已经无法正常收发数据了 recv() 的返回值仍然不是0。

在TCP连接正常关闭时 recv() 才会返回 0

按照我以往的理解,在TCP连接断开时 recv() 接口就会返回0,事实上并不是这样的。

When a stream socket peer has performed an orderly shutdown, the return value will be 0 (the traditional "end-of-file" return).

以上摘自 recv() 接口的帮助文档。只有在对端正常关闭TCP连接时 recv() 接口才会返回 0 ,表示对端不会再有数据发过来了。这里的“正常关闭”指的是完成TCP的“四次挥手”流程。

但是在一些特殊情况下,对端程序无法正常关闭连接,这导致本端程序无法及时检测到连接断开。常见的有:网线松动(链路故障)、程序崩溃。

使用心跳机制检测连接是否正常

为了能尽快察觉到TCP连接异常,常见的做法是周期性的发送心跳包验证与对端的连接状态。心跳包是应用层的特殊命令,属于TCP协议之上。在连接异常时发送心跳包 send() 接口会返回 -1 ,这样在本端就能感知连接异常了。

在应用层协议中增加心跳机制的另一个作用是检测对端的运行状态是否正常,即与对端的TCP连接正常但是对端程序卡死了。

借助TCP的keepalive特性识别异常断开

还有另一种非侵入的方式检测异常连接,即借助TCP的keepalive特性定期发送数据包,但是 并不 推荐使用这种方法。

有以下几点需要注意:

  • keepalive特性默认是关闭的,需要手动开启。
  • keepalive的发送周期默认比较长(linux下是2小时),做心跳机制使用时需要调整到一个较小的值。
  • TCP有重传机制,可能要通过修改 tcp_retries2 配置(默认是15)以减少重传次数,这样才能获得更快的检测到连接的异常。