永远记住 TCP 不是轮询的协议

网络故障或者系统宕机都将使得对端无法得知这个消息。如果应用程序不发送数据,可能永远无法得知该连接已经失效。

TCP 的 half open

某一方网络连接断开, 而另一方不知道.

这一个情况就是如果在未告知另一端的情况下通信的一端关闭或终止连接,那么就认为该条TCP连接处于半打开状态。 这种情况发现在通信的一方的主机崩溃、电源断掉的情况下。 只要不尝试通过半开连接来传输数据,正常工作的一端将不会检测出另外一端已经崩溃。

TCP 的 keepalive

TCP 协议的设计者考虑到了这种检测长时间死连接的需求,于是乎设计了 keepalive 机制.

相关配置:

  1. // 30s没有数据包交互发送 keepalive 探测包
  2. echo 30 > /proc/sys/net/ipv4/tcp_keepalive_time
  3. // 每次探测TCP 包间隔
  4. echo 10 > /proc/sys/net/ipv4/tcp_keepalive_intvl
  5. // 探测多少次
  6. echo 5 > /proc/sys/net/ipv4/tcp_keepalive_probes

偷梁换柱之 LD_PRELOAD

LD_PRELOAD 是一个 Linux 的环境变量,运行在程序运行前优先加载动态链接库,类似于 Java 的字节码改写 instrument。通过这个环境变量,我们可以修改覆盖真正的系统调用,达到我们的目的。 这个过程如下:

image.png

image.png

  1. 1 ~ 3:三次握手,随后模拟客户端断网
  2. 4:30s 以后服务端发送第一个探测包(对应 tcp_keepalive_time)
  3. 5 ~ 8:因探测包一直没有回应,每隔 10s 发出剩下的 4 次探测包
  4. 9:5 次探测包以后,服务端觉得没有希望了,发出 RST 包,断掉这个连接

为什么大部分应用程序都没有开启 keepalive 选项

现在大部分应用程序(比如我们刚用的 nc)都没有开启 keepalive 选项,一个很大的原因就是默认的超时时间太长了,从没有数据交互到最终判断连接失效,需要花 2.1875 小时(7200 + 75 * 9),显然太长了。但如果修改这个值到比较小,又违背了 keepalive 的设计初衷(为了检查长时间死连接)

对我们的启示

在应用层做连接的有效性检测是一个比较好的实践,也就是我们常说的心跳包。