此文已由作者徐城利授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
TCP在设计上是一个有状态的可靠的协议,连接双方通过通过三次握手进入Established状态后,即使没有数据或中间网络出现中断的情况下一直保持连接,直到一方开始关闭连接。 现实中往往会发生各种意外,导致连接没有正常关闭:
一个连接方因为掉电、系统崩溃等原因导致没有关闭连接
中间网络因为各种问题导致两方无法连通,如果硬件故障、网络掉了等
另外比较常见的是,如果中间网络上使用了NAT/防火墙等设备,往往需要对连接进行状态跟踪,而这些设备因为硬件上的限制,会丢弃掉一定时间不活跃的连接信息,也可能导致这条连接上的包被丢弃。
而按TCP协议的设计,Established状态下的socket在没有收到任何数据的情况下是可以保持非常长的时间 (Conntrack中默认对established的连接信息保持五天,socket看起来是没有超时机制的),会造成资源的泄漏甚至影响新连接的建立,尤其在大流量的服务端更加明显。
为了解决这个问题,大部分操作系统的协议栈都提供了TCP keepalive功能(大部分默认情况下都是不使用),用于解决上面提到的两个问题:
用于探测另外一端是否已经断开
定时发送数据对中间NAT/防火墙等设备中的连接信息保活
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只会检测连接本身,而没法检测上层应用本身有没有问题,因此应用本身对这方面有要求的话,就需要实现应用层的keepalive。 当然,如果你的应用使用的不是TCP协议,自然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