TCP Keepalive

叁叁肆2018-10-25 12:35

此文已由作者徐城利授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。


什么是TCP keepalive



TCP在设计上是一个有状态的可靠的协议,连接双方通过通过三次握手进入Established状态后,即使没有数据或中间网络出现中断的情况下一直保持连接,直到一方开始关闭连接。 现实中往往会发生各种意外,导致连接没有正常关闭:


  • 一个连接方因为掉电、系统崩溃等原因导致没有关闭连接

  • 中间网络因为各种问题导致两方无法连通,如果硬件故障、网络掉了等

  • 另外比较常见的是,如果中间网络上使用了NAT/防火墙等设备,往往需要对连接进行状态跟踪,而这些设备因为硬件上的限制,会丢弃掉一定时间不活跃的连接信息,也可能导致这条连接上的包被丢弃。


而按TCP协议的设计,Established状态下的socket在没有收到任何数据的情况下是可以保持非常长的时间 (Conntrack中默认对established的连接信息保持五天,socket看起来是没有超时机制的),会造成资源的泄漏甚至影响新连接的建立,尤其在大流量的服务端更加明显。


为了解决这个问题,大部分操作系统的协议栈都提供了TCP keepalive功能(大部分默认情况下都是不使用),用于解决上面提到的两个问题:


  • 用于探测另外一端是否已经断开

  • 定时发送数据对中间NAT/防火墙等设备中的连接信息保活


TCP keepalive如何工作


TCP keepalive是由操作系统层自动完成的,当keepalive的定时触发时,系统会往对端发送一个没有数据的ACK包(利用了协议中的Duplicate ACK),对端在收到这个包后也会回复一个ACK,如果一定时间和次数内都没有收到对端的回复,那就可以认为连接已经断开。 同时因为使用的标准的TCP行为,所以对端不需要做任何设置(即不用要求对端也一定要开启TCP keepalive),而且上层应用也不会有任何感知和影响。 TCP keepalive有一般有几个参数:


  • Keepalive time:上次数据包发送后多少时间开始做检测(闲置时间)

  • Keepalive interval: 每次探测包之间间隔多少时间

  • Keepalive probes: 总共检测多少次才判断为连接断开


所以keepalive检测到连接断开的时间为: time + (probes - 1) * interval


什么时候使用TCP keepalive


TCP keepalive是操作系统提供的功能,而且对应用程序没有任务影响,最多产生少量的额外的网络流量,所以大部分情况下,除非有明确理由不希望使用它,开启TCP keepalive没有什么坏处。 因为TCP keepalive只会检测连接本身,而没法检测上层应用本身有没有问题,因此应用本身对这方面有要求的话,就需要实现应用层的keepalive。 当然,如果你的应用使用的不是TCP协议,自然TCP keepalive无从谈起了。


Linux下的TCP keepalive


Linux下默认创建的socket一般是没有启用TCP keepalive的,我们可以通过ss等命令来检查socket的option:


@dev-1~ ss -4to src :22State       Recv-Q Send-Q  Local Address:Port    Peer Address:Port
ESTAB       0      0        10.0.1.2:ssh         10.0.1.1:49460                 timer:(keepalive,12min,0)

而对于启用TCP keepalive的socket,系统通过proc和sysctl提供了配置默认参数的接口


procfs:


 @dev-1~ cat /proc/sys/net/ipv4/tcp_keepalive_time7200
 @dev-1~ cat /proc/sys/net/ipv4/tcp_keepalive_intvl75
 @dev-1~ cat /proc/sys/net/ipv4/tcp_keepalive_probes9

sysctl:


 @dev-1~ sudo sysctl -a | grep net.ipv4.tcp_keepalive
net.ipv4.tcp_keepalive_intvl = 75net.ipv4.tcp_keepalive_probes = 9net.ipv4.tcp_keepalive_time = 7200

如果不想使用全局的配置,也可以在代码中分别对每个socket进行设置


应用程序支持


自己开发的程序


在自己的代码中,可以通过setsockopt这个方法进行设置


int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen)

在客户端创建socket或服务端通过accept获取到socket后,通过setsockopt启用keepalive


  /* Set the option active */
  optval = 1;
  optlen = sizeof(optval);  if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) {
      perror("setsockopt()");
      close(s);      exit(EXIT_FAILURE);
  }

也可以设置keepalive的几个参数


...setsockopt(s, SOL_TCP, TCP_KEEPCNT, ...)    // probes
...setsockopt(s, SOL_TCP, TCP_KEEPIDLE, ...)    // time...setsockopt(s, SOL_TCP, TCP_KEEPINTVL, ...)    // interval

第三方程序


对有些无法修改代码的第三方程序,一个方法是使用LD_PRELOAD替代掉标准的socket调用(这个方法只对使用动态链接库的程序有效),更多信息可参考libkeepalive这个项目。


网易云免费体验馆,0成本体验20+款云产品! 

更多网易技术、产品、运营经验分享请点击


相关文章:
【推荐】 DDoS防护之TCP防护
【推荐】 一篇文章看懂Facebook和新浪微博的智能FEED