1. 程式人生 > >Linux下使用UDP做心跳檢測(斷線檢測)

Linux下使用UDP做心跳檢測(斷線檢測)

何為心跳檢測

  字型變大心跳檢測,顧名思義,就像心跳一樣客戶端每隔幾秒鐘傳送一個數據包(心跳包)給伺服器,告訴伺服器,客戶端還線上。如果伺服器在規定時間內沒有收到客戶端發來的心跳包,則認為客戶端已經掉線。
  在我們日常生活中,很多地方都用到了心跳檢測機制,比如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程式設計框架


有圖可知,伺服器和客戶端沒有太大區分,這裡以伺服器為例做講解。

  1. 建立套接字檔案描述符
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 );
}