《网络编程实战》笔记 | 12 连接无效:使用Keep-Alive还是应用心跳来检测?
0 前言
之所以要进行连接无效的检查,是因为有应用场景需要保持 TCP 连接处于可用状态,如果连接断开,可能需要报错或者重新发起连接。就比如即时通信软件,当与服务器断开连接而不自知,当你想发消息才发现断开连接,可能你追了很久的那个人突然给你发了一条消息,“我有点想你”,过了几分钟就撤回了,你断开连接而不自知,就错过了进一步发展的机会。
1 TCP Keep-Alive 选项
Keep-Alive 是 TCP 协议本身有一个保活机制,设定一个保活计时器,当有数据交互时,会重置保活计时器。当连接上一直没有数据,保活时间到,会每个一段时间就发送探测报文,若连续几个探测报文都没有相应,则认为TCP 连接死亡,系统内核将错误信息通知给上层应用程序。
该机制有三个可定义变量:保活时间、保活时间间隔和保活探测次数,在 Linux 系统中,这些变量分别对应 sysctl 变量:net.ipv4.tcp_keepalive_time、net.ipv4.tcp_keepalive_intvl、 net.ipv4.tcp_keepalve_probes,默认设置是 7200 秒(2小时)、75 秒和 9 次探测。也就是2个小时都没有报文交互,则发送探测报文,探测报文间隔75 秒发送,当9次都没有回复,则判断连接死亡。
matt@ubuntu:~$ sudo sysctl -a | grep net.ipv4.tcp_keepalive
[sudo] password for matt:
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_time = 7200
因为早起网络带宽资源有限,所以默认的保活机制的频率很低,很多时延敏感系统中,默认的这个时间间隔不可接受,需要重新配置参数,或者在应用层寻找解决方案。
2 应用层探活
应用层设计保活程序,可以设计 PING-PONG 的机制,需要保活则发起一个 PING 报文,得到 PONG 回复则重置保活时间,否则探测次数计数,继续发送,达到预设次数,则判定连接无效。
关键点:
1. 需要使用定时器,可以使用 I/O 多路复用(select,epoll等都带有超时机制)自身的机制实现;如使用 select 时,成功则返回监听到时间的描述符个数,失败返回-1,超时返回 0 ;
#define KEEP_ALIVE_PROBETIMES 3 //探测次数
#define KEEP_ALIVE_INTERVAL 3 //探测间隔
#define KEEP_ALIVE_TIME 10 //保活时间
struct timeval tv;
int heartbeats = 0;
tv.tv_sec = KEEP_ALIVE_TIME;
tv.tv_usec = 0;
int rc = select(socket_fd + 1, &readmask, NULL, NULL, &tv);
if(rc<0){
perror("select failed");
exit(1);
}
if(rc == 0){
if(++heartbeats >KEEP_ALIVE_PROBETIMES){
printf("connection dead");
exit(0);
}
printf("sending heartbead #%d\n", heartbeats);
ping_message.type = htonl(MSG_PING);
rc = send(socket_fd,(char *)&ping_message, sizeof(messageObject),0);
if(rc<0){
perror("send failed");
exit(1);
}
tv.tv_sec = KEEP_ALIVE_INTERVAL;
continue;
}
2. 需要设计一个 PING-PONG 协议。
typedef struct {
u_int32_t type;
char data[1024];
} messageObject;
#define MSG_PING 1
#define MSG_PONG 2
#define MSG_TYPE1 11
#define MSG_TYPE2 21