在Socket心跳機制中,心跳包可以由服務器發送給客戶端,也可以由客戶端發送給服務器,不過比較起來,前者開銷可能較大。本文實現的是由客戶端給服務器發送心跳包,服務器不必返回應答包,而是通過判斷客戶在線會話記錄中的計數標志值來實現心跳異常的檢測,以此決定客戶端是否已經斷開連接以及刪除其在線會話記錄。
基本思路:
- 客戶端定時給服務器發送心跳包(案例中定時時間為3秒);
- 服務器創建一個心跳檢測的線程,線程中每隔3秒對用戶在線會話記錄中的計數器進行加1操作(初始值為0);
- 服務器每次收到客戶端的心跳包后,都將其在線會話記錄中的計數器清零;
- 當心跳檢測線程中檢測到某用戶計數器已經累加到數值為5時(說明已經有15秒未收到該用戶心跳包),就判定該用戶已經斷線,並將其從會話記錄中清除出去。
(注:案例中的會話記錄是用鏈表實現的)
/* **功能:客戶端心跳包發送線程函數 **參數:線程傳參(可傳套接字) **返回值:空 */ void *send_heart(void *addr) { while(1){ pd->data_type = HEART; //HEART:數據包類型,pd為數據包結構體指針 write(client_sockfd,pd,sizeof(DATA_PACK)); sleep(3); //定時3秒 } return NULL; }
服務端
typefdef struct session{ char peerip[16]; char name[10]; int sockfd; int count; struct session *next; }s_t; /* **功能:處理用戶心跳包事件,將其會話記錄中的計數器清零 **參數:套接字和數據包指針 **返回值:無 */ void heart_handler(int sockfd,DATA_PACK *pd) { s_t *cur = shead->next; // shead為用戶在線會話記錄全局變量頭指針 while( NULL != cur){ if(strcmp(cur->name,pd->name) == 0){ cur->count = 0; //將計數器清零,表明用戶名為pd->name的客戶端還活着 printf("客戶端IP: %s :用戶 %s 連接正常\n",cur->peerip,pd->name); } cur = cur->next; } } /* **功能:心跳檢測線程函數 **參數:無 **返回值:無 */ void *heart_check(void *p) { printf("心跳檢測線程已開啟!\n"); while(1){ check_handler(); // 心跳檢測處理函數 sleep(3); //定時3秒 } return NULL; } /* **功能:心跳檢測處理函數 **參數:無 **返回值:無 */ void check_handler(){ s_t *temp = NULL; // 用於釋放結點 s_t **ppNode = &shead->next; while(NULL != (*ppNode)){ if((*ppNode)->count == 5){ printf("客戶端IP: %s :用戶 %s 已經掉線!!\n",(*ppNode)->peerip,(*ppNode) ->name); close((*ppNode)->sockfd); //關閉對端套接字 temp = *ppNode; //存儲本結點地址 *ppNode = (*ppNode)->next; //移動指針 free(temp); //釋放結點 temp = NULL; continue; } else if((*ppNode)->count > 0){ printf("客戶端IP: %s :用戶 %s 連接異常!\n",(*ppNode)->peerip,(*ppNode)- >name); (*ppNode)->count++; printf("count = %d\n",(*ppNode)->count); //查看計數器內容 ppNode = &((*ppNode)->next); // 成員指針 continue; } else if((*ppNode)->count == 0){ (*ppNode)->count++; printf("count = %d\n",(*ppNode)->count); //查看計數器內容 ppNode = &((*ppNode)->next); // 成員指針 } else; } }