Linux内置支持keepalive机制,为了使用它,你需要使能TCP/IP网络,为了能够配置内核在运行时的参数,你还需要procfs和sysctl的支持。
这个过程涉及到keepalive使用的三个用户驱使的变量:
tcp_keepalive_time:表示的是最近一次数据包(简单的不含数据的ACKs包)发送与第一次keepalive探针发送之间的时间间隔;当连接被标记为keepalive之后,这个计数器就不会再使用。
tcp_keepalive_intvl:表示的是并发keepalive探针之间的时间间隔。
tcp_keepalive_probes:在确定连接已经断开并且通知应用层之前所发送的没有得到回复的探针数。
对于这三个参数可以在Linux系统的终端中查看和修改它们的缺省值:
查看三个参数的值:
[root@Server3 ~]# cat /proc/sys/net/ipv4/tcp_keepalive_time 7200 [root@Server3 ~]# cat /proc/sys/net/ipv4/tcp_keepalive_intvl 75 [root@Server3 ~]# cat /proc/sys/net/ipv4/tcp_keepalive_probes 9 |
通过命令对这三个参数值进行修改(图中将三个参数值分别设为:600、60、20):
[root@Server3 ~]# echo 600 > /proc/sys/net/ipv4/tcp_keepalive_time
[root@Server3 ~]# echo 60 > /proc/sys/net/ipv4/tcp_keepalive_intvl
[root@Server3 ~]# echo 6 > /proc/sys/net/ipv4/tcp_keepalive_probes
这种方式重置三个参数值,在系统重启后三个参数的值又会恢复到默认值,具体如何让系统永远记住自己设置的值,可参考其他资料,我们现在关系的是如何在程序中使用keepalive机制并设置这三个参数的值。
在程序中使用keepalive机制
想在程序中使用这种机制,只需要使用setsockopt()函数。
setsockopt()函数用于任意类型、任意状态套接口的设置选项值。尽管在不同协议层上存在选项,但本函数仅定义了最高的“套接口”层次上的选项。选项影响套接口的操作,诸如加急数据是否在普通数据流中接收,广播数据是否可以从套接口发送等等。
下面为setsockopt()函数的原型:
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);
参数:
sock:将要被设置或者获取选项的套接字。
level:选项所在的协议层。
optname:需要访问的选项名。
optval:对于getsockopt(),指向返回选项值的缓冲。对于setsockopt(),指向包含新选项值的缓冲。
optlen:对于getsockopt(),作为入口参数时,选项值的最大长度。作为出口参数时,选项值的实际长度。对于setsockopt(),现选项的长度。
为了使用函数setsockopt()将某个特定的套接字的keepalive机制打开,参数s是一个socket文件描述符,必须要在这之前使用socket()函数进行创建;参数level必须设置为SOL_SOCKET;第三个参数必须设置为SO_KEEPALIVE;optval参数必须是一个布尔型整型变量,表示想要使能这个选项;最后一个参数表示第四个参数的大小。
程序实现心跳包检测机制
首先要在备份机和源机之间建立一个专门的socket链路来进行心跳检测。
在源机端,在进行数据迁移之前,会建立一个socket来监听备份机的连接,并将这个socket和对应的处理函数放入原软件的io处理列表中,代码如下:
int listenfd; struct sockaddr_in server_sin; /* establish socket */ listenfd=socket(AF_INET,SOCK_STREAM,0); server_sin.sin_family=AF_INET; server_sin.sin_addr.s_addr=htonl(INADDR_ANY); server_sin.sin_port=htons(PORT); bind(listenfd,(struct sockaddr *)&server_sin,sizeof(server_sin)); /* establish end */ listen(listenfd,1024); qemu_set_fd_handler2(listenfd, NULL, tcpkeepalive_server, NULL, (void *)(intptr_t)listenfd); 该socket对应的处理函数如下: static void tcpkeepalive_server(void *opaque) { int connfd; struct sockaddr_in client_sin; socklen_t client_len=sizeof(client_sin); int listenfd = (intptr_t)opaque; connfd=accept(listenfd,(struct sockaddr *)&client_sin,&client_len); } |
在备份机端,当其开始作为备份机时,会建立socket连接源机的监听端,并设置对应的tcpkeepalive参数,然后将socket和对应的处理函数加入io处理列表。
我们建立的socket是一个心跳检测专用链路,其上不会有数据流动,只有一种情况备份机端会收到数据,那就是源端出现了故障,tcpkeepalive机制会返回一个错误信息,所以捕捉到了这个信息,备份机就会跳转到对应的处理函数,接替源机开始运行。
对应代码如下:
int sockfd; struct sockaddr_in sin; int optval; socklen_t optlen = sizeof(optval); sockfd=socket(AF_INET,SOCK_STREAM,0); sin.sin_family=AF_INET; sin.sin_addr.s_addr=addr.sin_addr.s_addr; sin.sin_port=htons(PORT); optval = 1; setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen); optval = 5; setsockopt(sockfd, SOL_TCP, TCP_KEEPCNT, &optval, optlen); optval = 1; setsockopt(sockfd, SOL_TCP, TCP_KEEPIDLE, &optval, optlen); optval = 1; setsockopt(sockfd, SOL_TCP, TCP_KEEPINTVL, &optval, optlen); connect(sockfd,(struct sockaddr *)&sin,sizeof(sin)); qemu_set_fd_handler2(sockfd, NULL, tcpkeepalive_vm_start, NULL, (void *)(intptr_t)sockfd); |
该socket对应的处理函数很简单,就是让备份机开始运行:
static void tcpkeepalive_vm_start(void *opaque)
{
vm_start();
}