何为心跳检测
字体变大心跳检测,顾名思义,就像心跳一样客户端每隔几秒钟发送一个数据包(心跳包)给服务器,告诉服务器,客户端还在线。如果服务器在规定时间内没有收到客户端发来的心跳包,则认为客户端已经掉线。
在我们日常生活中,很多地方都用到了心跳检测机制,比如QQ,腾讯服务器是怎样知道你QQ登录状态,就是客户端不停的发送心跳包。心跳包的作用一方面用于检测客户端是否在线,另一方面还用于维持长连接。因为在长连接情况下,很可能因为长时间没有数据传输,而被防火墙关掉。在这种情况下,就是用到心跳包了,客户端通过不停的发送心跳包,维持客户端和服务器的连接。一般的应用下,判定时间在30-40秒比较不错。如果实在要求高,那就在6-9秒。
发送心跳包有两种方式
1.在TCP中使用SO_KEEPALIVE套接字选项
在TCP的机制中包含心跳检测的机制。客户端或服务器只要一方开启KeepAlive功能后,就会自动在规定时间内向对方发送心跳包, 而另一方在收到心跳包后就会自动回复,以告诉对方我仍然在线。由于心跳包会占据一定的带宽,所以系统默认是设置的2小时的心跳频率,探测次数为5次。但是,我们可以根据需要手自己设置合理的KeepAlive参数。
虽然这种机制代码实现比较简单,但它不能判断客户端掉线的原因,也不能在一些特殊的场景下不能使用(例如某些环境下不能使用tcp)
2.应用层自己实现心跳包发送
由应用程序自己发送心跳包来检测连接是否正常,客户端每隔一定时间向客户端发送一个心跳包。服务器启动一个线程,在线程中不断检测客户端的回应, 如果在一定时间内没有收到客户端的回应,即认为客户端已经掉线。
心跳检测机制实现
本文采用应用层发送心跳包的方式来实现对客户端的掉线检测。程序设计思路为:
1. 客户端每隔5s发生一个心跳包给服务器。心跳包内容为客户端的IP地址和主机名。
2 服务器开启一个接收线程,解析客户端发来的心跳包,建立在线主机表。主机表信息包括主机IP,主机名和该主机发送的心跳包到达服务器时间。每收到一个心跳包,就要更新主机列表中相应的主机信息。
3 服务器开启一个检测线程,每隔15s检查一次在线主机列表。通过计算主机列表中心跳包到达的时间和检测时间的时间差,如果时间差超过15s,则认为客户端掉线,并将掉线的客户端从主机列表中删除。
由于本文中发送的心跳包为UDP包,所以本文从UDP编程框架、客户端实例和服务器实例三个方面展开阐述。
1.UDP编程框架
有图可知,服务器和客户端没有太大区分,这里以服务器为例做讲解。
- 建立套接字文件描述符
int sock = socket(AF_INET, SOCK_DGRAM, 0);
2.设置服务器地址和端口
sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
3.绑定侦听端口
bind(sockfd, (sockaddr *)&servaddr, sizeof(servaddr));
4.接收客户端数据
int count = recvfrom(sockfd, recv_buf, MAXLINE, 0, (sockaddr *)&servaddr, &servaddr_len);
5.向客户端发送数据
sendto(sockfd, send_buf, sizeof(send_buf), 0, (sockaddr *)&servaddr, sizeof(servaddr));
6.关闭套接字
close(sockfd);
2.客户端实例
客户端每隔5s发生一个心跳包给服务器。心跳包内容为客户端的IP地址和主机名。以下为客户端完整代码。
/*
* File: HeartPackageSendAgent.cpp
* Author: Pangxiaojian
*
*
* 主要实现:向服务器发送心跳包,每5s向服务器发送一个心跳包
* File: HeatPackageAgent.c
* Author: Pangxiaojian
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define MAXLINE 80
#define SERV_PORT 8000
const int HeatPort = 6789;
const char ServerIP[255] = "192.168.18.128";
void getIPHost(char** iphost)
{
int sock;
struct sockaddr_in sin;
struct ifreq ifr;
sock = socket(AF_INET, SOCK_DGRAM, 0);
for(int i = 0; i < 10; i++)
{
char* ENAME = (char*)malloc(5*sizeof(char));
bzero(ENAME, 5);
sprintf(ENAME, "%s%d", ETH_NAME, i);
strncpy(ifr.ifr_name, ENAME, IFNAMSIZ);
free(ENAME);
ifr.ifr_name[IFNAMSIZ - 1] = 0;
if (ioctl(sock, SIOCGIFADDR, &ifr) >= 0)
goto HERE;
}
for(int i = 0; i < 10; i++)
{
char* WNAME = (char*)malloc(6*sizeof(char));
bzero(WNAME, 6);
sprintf(WNAME, "%s%d", WTH_NAME, i);
strncpy(ifr.ifr_name, WNAME, IFNAMSIZ);
free(WNAME);
ifr.ifr_name[IFNAMSIZ - 1] = 0;
if (ioctl(sock, SIOCGIFADDR, &ifr) >= 0)
goto HERE;
}
HERE:
memcpy(&sin, &ifr.ifr_addr, sizeof(sin));
char* hostname = (char*)malloc(256*sizeof(char));
bzero(hostname, 256);
gethostname(hostname, 256*sizeof(char));
char* ip = inet_ntoa(sin.sin_addr);
int lenhost = strlen(hostname);
int lenip = strlen(ip);
*iphost = (char*)malloc((lenhost+lenip+2)*sizeof(char));
bzero(*iphost, (lenhost+lenip+2)*sizeof(char));
sprintf(*iphost, "%s:%s", ip, hostname);
free(hostname);
}
int heart_send()
{
char send_buf[MAXLINE];
char recv_buf[MAXLINE];
char *iphost = NULL;
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
sockaddr_in cliaddr;
cliaddr.sin_family = AF_INET;
cliaddr.sin_addr.s_addr = inet_addr(ServerIP);
cliaddr.sin_port = htons(SERV_PORT);
bind(sockfd, (sockaddr *)&cliaddr, sizeof(cliaddr));
getIPHost(&iphost);
memcpy(send_buf,iphost,strlen(iphost));
while (1)
{
sockaddr_in servaddr;
socklen_t servaddr_len = sizeof(servaddr);
int count = recvfrom(sockfd, recv_buf, MAXLINE, 0, (sockaddr *)&servaddr, &servaddr_len);
if (count < 0)
{
printf("recvfrom error");
continue;
}
printf("received msg is %s\n",recv_buf);
sendto(sockfd, send_buf, sizeof(send_buf), 0, (sockaddr *)&servaddr, sizeof(servaddr));
sleep(5);
}
close(sockfd);
return ((void*)1);
}
int main()
{
pthread_t m_threadHeartSend;
int *ret_join = NULL;
if (pthread_create(&m_threadHeartSend, NULL, &heart_send, NULL) != 0)
return -1;
pthread_join(m_threadHeartSend,(void*)&ret_join);
}
3.服务器实例
服务器端功能:
1. 服务器开启一个接收线程,解析客户端发来的心跳包,建立在线主机表。主机表信息包括主机IP,主机名和该主机发送的心跳包到达服务器时间。每收到一个心跳包,就要更新主机列表中相应的主机信息。
2. 服务器开启一个检测线程,每隔15s检查一次在线主机列表。通过计算主机列表中心跳包到达的时间和检测时间的时间差,如果时间差超过15s,则认为客户端掉线,并将掉线的客户端从主机列表中删除。
以下是服务器端完整代码:
/*
* File: HeartPackageCheckServer.cpp
* Author: Pangxiaojian
*
*
* 主要实现:检测心跳包
* File: HeartPackageCheckServer.cpp
* Author: Pangxiaojian
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/timeb.h>
#include <assert.h>
#include <sys/time.h>
#define MAXLINE 80
#define SERV_PORT 8000
const int HeatPort = 6789;
const char IP[255] = "192.168.18.128";
typedef struct online_hosts
{
char IP[256];
struct timeval time;//心跳包接收时间
}
OnlineHosts;
map<string,OnlineHosts> online_hosts_map;
void* is_host_online(void*)
{
struct timeval now_time;
now_time = gettimeofday(&now_time,NULL);
map<string,OnlineHosts>::iterator it ;
for(it = online_hosts_map.begin();it!=online_hosts_map.end();it++)
{
if(now_time - it->second.time > 5)
{
online_hosts_map.erase(*it);
//报告函数
}
}
return ((void*)1);
}
void* heart_recv(void*)
{
char recv_buf[MAXLINE];
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
sockaddr_in cliaddr;
cliaddr.sin_family = AF_INET;
cliaddr.sin_addr.s_addr = htonl(INADDR_ANY);
cliaddr.sin_port = htons(SERV_PORT);
bind(sockfd, (sockaddr *)&cliaddr, sizeof(cliaddr));
while (1)
{
sockaddr_in servaddr;
socklen_t servaddr_len = sizeof(servaddr);
map<string,OnlineHosts>::iterator it;
struct timeval now_time;
now_time = gettimeofday(&now_time,NULL);
int count = recvfrom(sockfd, recv_buf, MAXLINE, 0, (sockaddr *)&servaddr, &servaddr_len);
if (count < 0)
{
printf("recvfrom error");
continue;
}else{
it = online_hosts_map.find(recv_buf);
if(it != online_hosts_map.end())
it->second.time = now_time;
}
printf("received msg is %s\n",recv_buf);
sleep(15);
}
close(sockfd);
return ((void*)1);
}
int main()
{
char clientIP = "192.168.12.123";
struct timeval start_time;
OnlineHosts onlineClient;
pthread_t m_threadHeartRecv,m_threadHeartCheck;
int *ret_join_heart_recv = NULL;
int *ret_join_heart_check = NULL;
start_time = gettimeofday(&now_time,NULL);
onlineClient.IP = clientIP;
onlineClient.time = start_time;
online_hosts_map.insert(map<string,OnlineHosts>::value_type(clientIP,onlineClient));
if (pthread_create(&m_threadHeartRecv, NULL, &heart_recv, NULL) != 0)
return -1;
if (pthread_create(&m_threadHeartCheck, NULL, &is_host_online, NULL) != 0)
return -1;
pthread_join(m_threadHeartRecv,(void*)&ret_join_heart_recv );
pthread_join(m_threadHeartCheck,(void*)&ret_join_heart_check );
}