服务端的系统设置中经常会和底层协议打交道,我们有必要重温一下曾经那些“听过”却不熟悉的名词。 今天聊的话题是 KeepAlive,在实际应用中又是怎么使用的?
为什么有Keepalive?
大家都做过电梯吧,假设电梯来了你先进去,你朋友还没进来,过一段时间电梯门就会自动关闭, 你应该没遇到过哪个电梯会一直等你朋友来了才关门的。如果真是那样,那别的楼层的小姐姐们会炸了~
我们举个编程中的例子来解释下,我编写了一个服务端程序S和一个客户端程序C,客户端向服务端发送 一个消息:
服务端收到消息后一看,瞧给你牛*的,然后没理客户端,傻狗客户端一直在等待,但是不知道是不是服务器挂掉了? 这时候TCP协议提出一个办法,当客户端端等待超过一定时间后自动给服务端发送一个空的报文, 如果对方回复了这个报文证明连接还存活着,如果对方没有报文返回且进行了多次尝试都是一样, 那么就认为连接已经丢失,客户端就没必要继续保持连接了。 如果没有这种机制就会有很多空闲的连接占用着系统资源。
KeepAlive并不是TCP协议规范的一部分,但在几乎所有的TCP/IP协议栈(不管是Linux还是Windows)中,都实现了KeepAlive功能
如何设置它?
在设置之前我们先来看看KeepAlive都支持哪些设置项
- KeepAlive默认情况下是关闭的,可以被上层应用开启和关闭
- tcp_keepalive_time: KeepAlive的空闲时长,或者说每次正常发送心跳的周期,默认值为7200s(2小时)
- tcp_keepalive_intvl: KeepAlive探测包的发送间隔,默认值为75s
- tcp_keepalive_probes: 在tcp_keepalive_time之后,没有接收到对方确认,继续发送保活探测包次数,默认值为9(次)
我们讲讲在Linux操作系统和使用Java、C语言和Nginx中如何设置
在Linux内核设置
KeepAlive默认不是开启的,如果想使用KeepAlive,需要在你的应用中设置SO_KEEPALIVE才可以生效。
查看当前的配置:
cat /proc/sys/net/ipv4/tcp_keepalive_time cat /proc/sys/net/ipv4/tcp_keepalive_intvl cat /proc/sys/net/ipv4/tcp_keepalive_probes
在Linux中我们可以通过修改 /etc/sysctl.conf 的全局配置:
net.ipv4.tcp_keepalive_time=7200 net.ipv4.tcp_keepalive_intvl=75 net.ipv4.tcp_keepalive_probes=9
添加上面的配置后输入 sysctl -p 使其生效,你可以使用 sysctl -a | grep keepalive 命令来查看当前的默认配置
如果应用中已经设置SO_KEEPALIVE,程序不用重启,内核直接生效
使用Netty4设置
这里我们使用常用的Java网络框架Netty来设置,只需要在服务端设置即可:
EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100) .childOption(ChannelOption.SO_KEEPALIVE, true) .handler(new LoggingHandler(LogLevel.INFO)); // Start the server. ChannelFuture f = b.bind(8088).sync(); // Wait until the server socket is closed. f.channel().closeFuture().sync(); } finally { // Shut down all event loops to terminate all threads. bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); }
这段代码来自经典的echo服务器,我们在childOption中开启了SO_KEEPALIVE。 Java程序只能做到设置SO_KEEPALIVE选项,其他配置项只能依赖于sysctl配置,系统进行读取。
C语言设置
函数原型:
#include <sys/socket.h> int setsockopt(int socket, int level, int option_name, const void *option_value, socklen_t option_len);
我们在需要使能Keepalive的socket上面调用setsockopt函数便可以打开该socket上面的keepalive。
- 第一个参数是要设置的套接字
- 第二个参数是SOL_SOCKET
- 第三个参数必须是SO_KEEPALIVE
- 第四个参数必须是一个布尔整型值,0表示关闭,1表示打开
- 最后一个参数是第四个参数值的大小。
调用例子:
int socket(int domain, int type, int protocol) { int (*libc_socket)(int, int, int); int s, optval; char *env; *(void **)(&libc_socket) = dlsym(RTLD_NEXT, "socket"); if(dlerror()) { errno = EACCES; return -1; } if((s = (*libc_socket)(domain, type, protocol)) != -1) { if((domain == PF_INET) && (type == SOCK_STREAM)) { if(!(env = getenv("KEEPALIVE")) || strcasecmp(env, "off")) { optval = 1; } else { optval = 0; } if(!(env = getenv("KEEPALIVE")) || strcasecmp(env, "skip")) { setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)); } #ifdef TCP_KEEPCNT if((env = getenv("KEEPCNT")) && ((optval = a