這節我們來探討一下linux開發過程中常用的定時器,尤其在網絡編程中被常常用到如heartbeat,斷線重連等等。這里提供了三種定時器的方案,分別是鏈表形式的計時器,環型計時器,最小堆計時器。每個都有不同的作用和優勢,可以結合實際項目選擇或者改良。
鏈表計時器:
鏈表計時器是一個實現很簡單的一種計時器,可以使用單鏈表或者雙鏈表來實現,我這里有一個雙鏈表實現的例子

/** * timer list * * * * * */ #ifndef LIST_TIMER_H #define LIST_TIMER_H #include <time.h> typedef struct util_time{ struct util_time * prev; // 雙鏈表的前驅 struct util_time * next; //雙鏈表后驅 //Client data point void * cdata; //其他數據 //timeout value time_t out_time; //定時時間 int persist; //是否是堅持定時 //if timeout callback void (*timeout_callback)(void * data); //定時回調函數 }UTIL_TIME; struct list_timer; struct TimerOP { int (*add)(struct list_timer * lt, UTIL_TIME * ut); int (*del)(struct list_timer * lt, UTIL_TIME ** ut); int (*adjust)(struct list_timer * lt, UTIL_TIME * ut, time_t _time); void (*_tick)(struct list_timer * lt); }; //Timer list //定時器鏈表頭 typedef struct list_timer { struct TimerOP timer_op; UTIL_TIME * head; UTIL_TIME * tail; }LIST_TIMER; //timer list operator, all function only handle list and free timer, not free clientdata int add_timer(LIST_TIMER * lt, UTIL_TIME * ut); int del_timer(LIST_TIMER * lt, UTIL_TIME **ut); int adjust_timer(LIST_TIMER * lt, UTIL_TIME * ut, time_t _time); void tick(LIST_TIMER * lt); void init_List_Timer(LIST_TIMER * lt); void destroy_list_Timer(LIST_TIMER * lt); #endif

#include <stdlib.h> #include <string.h> #include <signal.h> #include <unistd.h> #include <stdio.h> #include "list_timer.h" /** * @brief init_List_Timer * @param lt timer list * initialize list timer, head and tail are NULL * 初始化定時器 */ void init_List_Timer(LIST_TIMER *lt) { lt->timer_op.add = add_timer; lt->timer_op.del = del_timer; lt->timer_op.adjust = adjust_timer; lt->timer_op._tick = tick; lt->head = NULL; lt->tail = NULL; } /** * @brief add_timer * @param lt * @param ut * @return * 添加一個定時器 並跟定時器排序 */ int add_timer(LIST_TIMER * lt, UTIL_TIME * ut) { if (lt && ut) { if (!lt->head && !lt->tail) { lt->head = ut; lt->tail = ut; ut->prev = NULL; ut->next = NULL; return 1; } else if (lt->head && lt->tail) { UTIL_TIME * temp = lt->head; UTIL_TIME * tempbak = NULL; while(temp) { if((temp->out_time) > (ut->out_time)) { if(lt->head == temp) { ut->next = temp; temp->prev = ut; lt->head = ut; ut->prev = NULL; return 1; } } tempbak = temp; temp = temp->next; } tempbak->next = ut; ut->prev = tempbak; ut->next = NULL; lt->tail = ut; return 1; } return 0; } return 0; } /** * @brief del_timer * @param lt * @param ut * @return * 由相應定時器的位置刪除定時器 */ int del_timer(LIST_TIMER * lt, UTIL_TIME **ut) { if(lt && ut && *ut) { if(lt->head) { UTIL_TIME *temp = *ut; if((temp == lt->head) && (temp != lt->tail)) //頭 { lt->head = temp->next; temp->next->prev = NULL; } else if((temp == lt->tail) && (temp != lt->head)) //尾 { temp->prev->next = NULL; lt->tail = temp->prev; } else if((temp == lt->tail) && (temp == lt->head)) //只有一個定時器 { lt->head = NULL; lt->tail = NULL; } else { temp->next->prev = temp->prev; temp->prev->next = temp->next; } (*ut)->cdata = NULL; (*ut)->next = NULL; (*ut)->prev = NULL; (*ut)->timeout_callback = NULL; free(*ut); *ut = NULL; ut = NULL; return 1; } } return 0; } /** * @brief adjust_timer * @param lt * @param ut * @param _time * @return * if a timer be deleted or addition time to a timer, adjust timer list * 移除指針並插入 */ int adjust_timer(LIST_TIMER * lt, UTIL_TIME * ut, time_t _time) { if(!lt || !ut){ return 0; } ut->out_time = time(NULL) + _time; if(!ut->prev && !ut->next) { return 1; //only have single Node }else if (ut->prev && !ut->next) { ut->prev->next = NULL; //if ut is tail Node, remove it. ut->prev = NULL; }else if (!ut->prev && ut->next) { lt->head = ut->next; //if ut is head Node ut->next->prev = NULL; ut->next = NULL; ut->prev = NULL; }else{ ut->next->prev = ut->prev; //ut is middle ut->prev->next = ut->next; ut->next = NULL; ut->prev = NULL; } // Can be optimized , insert after this Node. if(add_timer(lt, ut)) //reinsert it { return 1; } return 0; } /** * @brief tick * @param lt * Timer list tick, depend on callback function, adjust timer. */ void tick(LIST_TIMER * lt) { if(lt){ time_t now = time(NULL); UTIL_TIME *temp = lt->head; UTIL_TIME *tempbak = temp; while(temp) { tempbak = temp; temp = temp->next; if(tempbak->out_time <= now) //檢查時間 { tempbak->timeout_callback(tempbak->cdata); if(!(tempbak->persist)) del_timer(lt, &tempbak); else{ //persist time 重新調整 adjust_timer(lt, tempbak, tempbak->persist); } }else{ break; } } return; } return; } /** * @brief destroy_list_Timer * @param lt * destroy timer list */ void destroy_list_Timer(LIST_TIMER *lt) { if(!lt){ return; } UTIL_TIME * temp = lt->head; UTIL_TIME * tempnext = NULL; while(temp){ tempnext = temp->next; temp->cdata = NULL; temp->next = NULL; temp->prev = NULL; temp->timeout_callback = NULL; free(temp); temp = tempnext; } lt->head = NULL; lt->tail = NULL; }
測試用例

#include "list_timer.h" #include <stdio.h> #include <unistd.h> #include <signal.h> #include <stdlib.h> enum {CONNECTED, READ, WROTE, NORMAL}; typedef struct ClientData{ int fd; char ipaddr[4]; char * data; //This client monitor status int flags; //Timer point, if ut is null that mean not included into timer list. struct util_time * ut; }CLIENTDATA; LIST_TIMER lt; void doit(void * mydata) { CLIENTDATA * data = (CLIENTDATA *)mydata; if(data){ switch(data->flags){ case CONNECTED: fprintf(stderr, "don`t delete\n"); break; case READ: fprintf(stderr, "delete \n"); break; case WROTE: break; case NORMAL: break; default: break; } } } int main() { init_List_Timer(<); CLIENTDATA * cl1 = (CLIENTDATA *)malloc(sizeof(CLIENTDATA)); cl1->data = NULL; cl1->fd = 23; cl1->flags = CONNECTED; CLIENTDATA * cl2 = (CLIENTDATA *)malloc(sizeof(CLIENTDATA)); cl2->data = NULL; cl2->fd = 2324; cl2->flags = READ; UTIL_TIME *ut2 = (UTIL_TIME *)malloc(sizeof(UTIL_TIME)); ut2->timeout_callback = doit; ut2->cdata = cl2; ut2->out_time = time(NULL) + 2; ut2->persist = 0; UTIL_TIME *ut1 = (UTIL_TIME *)malloc(sizeof(UTIL_TIME)); ut1->timeout_callback = doit; ut1->cdata = cl1; ut1->out_time = time(NULL)+ 6; ut1->persist = 10; add_timer(<, ut1); add_timer(<, ut2); while(1){ tick(<); usleep(500); } return 0; }
list_timer.c list_timer.h 其中list_main.c為測試實例。 其中的tick函數 可以放在一個死循環中,這樣就實現了簡單的定時,每個定時器按照事件間隔的從小到大的順序並使用雙鏈表進行組織,若定時器為persist的話就反復調整計時器,加上每次間隔時間並重新調整鏈表。在tick中比對頭個計時器的時間如果小於目前時間就執行相應回調函數。
如在epoll模型的服務器中若要加入某個定時器,可以這樣做
while(1) { tick(timer); epoll_wait(); //此處得到定時器的最短計時, 即雙鏈表表頭定時間隔作為 阻塞時間。 //其他 for(;;) { } }
此種定時器的效率跟鏈表操作相同插入 刪除都是logN,調整計時器在鏈表的位置花費較多的時間(去掉重新插入)。
環形定時器:
這種定時器就像鍾表一樣,如秒針一樣一秒移動一次,當秒針移動到相應位置時,查看此位置是否有定時器,若有就不斷的check時間。這里的某個指針位置的定時器可以以鏈表形式由圈數量從小到大排列。指針走到某個位置查看鏈表的圈數若為0則表示時間到了 否則將圈數減1等待下次調用。如圖所示:
這里給出實例代碼如下:

#ifndef ROUND_TIMER_H #define ROUND_TIMER_H #include <time.h> typedef struct aw_timer{ void *data; struct aw_timer * prev; struct aw_timer * next; time_t expect; //current slot number int slotnumber; //timer loop cercle number int loopcercle; //weather persist int persist; //if return 0 : mean don`t delete timer //return 1 : delete timer. void (*timeout_callback)(void * data); }AW_TIMER; #define N 60 typedef struct wh_timers{ //time gap 1 sec or 1 min or 1 hour int timegap; //current slot point locetion int curslotpoint; //timer list point AW_TIMER *slot[N]; }WH_TIMERS; void init_wh_timer(WH_TIMERS * wt, int timegap); int add_wh_timer(WH_TIMERS * wt, AW_TIMER *at); int del_wh_timer(WH_TIMERS * wt, AW_TIMER **at, int remove); int adjust_wh_timer(WH_TIMERS * wt, AW_TIMER *at); void wh_tick(WH_TIMERS * wt); void destory_wh_timer(WH_TIMERS * wt); #endif

#include <time.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <signal.h> #include "round_timer.h" /** * @brief insert_sort_timer * @param aw_list * @param at * sort timer depend on loopcercle * 按照圈數插入 */ static void insert_sort_timer(AW_TIMER ** aw_list, AW_TIMER **at) { if(aw_list && at) { AW_TIMER * atr = *aw_list; while(atr){ if(((*at)->loopcercle <= (atr->loopcercle)) && (!atr->prev)){ (*at)->next = atr; atr->prev = *at; (*at)->prev = NULL; *aw_list = *at; return; } else if(((*at)->loopcercle >= (atr->loopcercle)) && (!atr->next)) { atr->next = *at; (*at)->prev = atr; (*at)->next = NULL; return; } else if(((*at)->loopcercle <= (atr->loopcercle)) && atr->next && atr->prev){ (*at)->next = atr; (*at)->prev = atr->prev; atr->prev->next = *at; atr->prev = *at; return; } atr = atr->next; } } } /** * @brief init_wh_timer * @param wt * @param timegap * Initialize wheel timers, timegap mean seconds one step. * If timegap < 0 , timegap = 1 */ void init_wh_timer(WH_TIMERS * wt, int timegap) { if(wt){ wt->curslotpoint = 0; wt->timegap = timegap ? timegap : 1; int i; for(i = 0; i < N; ++i){ wt->slot[i] = NULL; } } } /** * @brief add_wh_timer * @param wt * @param at * @return * add a timer into wheel timers */ int add_wh_timer(WH_TIMERS *wt, AW_TIMER *at) { if(wt && at){ if(at->expect){ //get timer loop cercles. at->loopcercle = (at->expect) / ((wt->timegap) * N); //get timer slot number in wheel at->slotnumber = (wt->curslotpoint + ((at->expect) / (wt->timegap))) % N; int index_t = at->slotnumber; if(wt->slot[index_t]) { //If this slot is not empty, insert it sortable. insert_sort_timer(&(wt->slot[index_t]), &at); }else{ wt->slot[index_t] = at; at->prev = NULL; at->next = NULL; } } } } /** * @brief del_wh_timer * @param wt * @param at * @param remove * @return * delete a timer, and the remove flag mean weather free it or not. */ int del_wh_timer(WH_TIMERS *wt, AW_TIMER **at, int remove) { if(wt && at){ if(*at){ if(!((*at)->prev)){ wt->slot[(*at)->slotnumber] = (*at)->next; if((*at)->next) (*at)->next->prev = NULL; } else if(!((*at)->next)) { if((*at)->prev) (*at)->prev->next = NULL; else wt->slot[(*at)->slotnumber] = NULL; }else{ (*at)->prev->next = (*at)->next; (*at)->next->prev = (*at)->prev; (*at)->prev = NULL; (*at)->next = NULL; } //是否真的移除 if(remove){ free(*at); *at = NULL; } } return 0; } return 0; } /** * @brief adjust_wh_timer * @param wt * @param at * @return * reset timer, fristly, will remove timer, then insert wheel again. */ int adjust_wh_timer(WH_TIMERS *wt, AW_TIMER *at) { //調整計時器 if(wt && at) { del_wh_timer(wt, &at, 0); add_wh_timer(wt, at); } } /** * @brief wh_tick * @param wt * point move step by step, if slot is not null, run timeout callback function. * if loop cercle sum > 0, will skip it. */ void wh_tick(WH_TIMERS *wt) { if(wt) { wt->curslotpoint = (wt->curslotpoint + 1)%N; if(wt->slot[wt->curslotpoint]) { AW_TIMER * at = wt->slot[wt->curslotpoint]; AW_TIMER *attemp = at; while(at) { attemp = at->next; if(0 >= at->loopcercle){ //圈數為0 開始調用 at->timeout_callback(at->data); if(at->persist) { adjust_wh_timer(wt, at); }else{ del_wh_timer(wt, &at, 1); } }else{ //等待下次被調 並停止循環 (at->loopcercle)--; break; } at = attemp; } } alarm(wt->timegap); } } /** * @brief destory_wh_timer * @param wt * free timer wheel. */ void destory_wh_timer(WH_TIMERS *wt) { int i; for(i = 0; i < N; ++i) { if(wt->slot[i]){ AW_TIMER * timer = wt->slot[i]; AW_TIMER * temptimer = timer; while(timer) { temptimer = timer->next; free(timer); timer = NULL; timer = temptimer; } temptimer = NULL; wt->slot[i] = NULL; } } }

#include "list_timer.h" #include <stdio.h> #include <unistd.h> #include <signal.h> #include <stdlib.h> enum {CONNECTED, READ, WROTE, NORMAL}; typedef struct ClientData{ int fd; char ipaddr[4]; char * data; //This client monitor status int flags; //Timer point, if ut is null that mean not included into timer list. struct util_time * ut; }CLIENTDATA; LIST_TIMER lt; void doit(void * mydata) { CLIENTDATA * data = (CLIENTDATA *)mydata; if(data){ switch(data->flags){ case CONNECTED: fprintf(stderr, "don`t delete\n"); break; case READ: fprintf(stderr, "delete \n"); break; case WROTE: break; case NORMAL: break; default: break; } } } int main() { init_List_Timer(<); CLIENTDATA * cl1 = (CLIENTDATA *)malloc(sizeof(CLIENTDATA)); cl1->data = NULL; cl1->fd = 23; cl1->flags = CONNECTED; CLIENTDATA * cl2 = (CLIENTDATA *)malloc(sizeof(CLIENTDATA)); cl2->data = NULL; cl2->fd = 2324; cl2->flags = READ; UTIL_TIME *ut2 = (UTIL_TIME *)malloc(sizeof(UTIL_TIME)); ut2->timeout_callback = doit; ut2->cdata = cl2; ut2->out_time = time(NULL) + 2; ut2->persist = 0; UTIL_TIME *ut1 = (UTIL_TIME *)malloc(sizeof(UTIL_TIME)); ut1->timeout_callback = doit; ut1->cdata = cl1; ut1->out_time = time(NULL)+ 6; ut1->persist = 10; add_timer(<, ut1); add_timer(<, ut2); while(1){ tick(<); usleep(500); } return 0; }
我們可以看到在例子中 我用了定時信號來模擬一秒走一次,我們注意到這里信號可能會造成某些系統調用的中斷 這里可以將它該為類似鏈表計時器中那樣,在循環中得到倆次時間間隔 若大於等於1s就將秒針加1, 不過這樣可能就不太准確了。可以將時間間隔設置的稍微大一些。
最小堆計時器:
最小堆計時器是比較常用的一種計時器,在libevent中可以看到它的使用。這種數據結構每次返回的是最小值時間間隔定時器。Libevent 事件循環(1) 這里可以簡單看一下它的用法。當添加計時器時,就不斷調整計時器在堆中的位置保證堆頂是一個最小計時。當計時器從堆中刪除時就不斷的向下不斷找最小的計時器放在堆頂,至於最小堆的實現這里有一篇不錯的文章分享給大家 : 二叉堆(一)之 圖文解析 和 C語言的實現
我這里給出一個實例代碼:
#include <time.h> typedef struct { int persist; //是否堅持 time_t timeout; //timeout = 時間間隔 + 現在時間 time_t timegap; //時間間隔 void * data; //用戶數據 }Timer; typedef struct { Timer *timer; size_t size; size_t capacity; }HeapTimer; //動態數組 最小堆 HeapTimer * initHeapTimer(size_t s); void min_heap_push(HeapTimer * ht, Timer * _timer); void min_heap_pop(HeapTimer * ht, Timer * _timer); int check_had_timeout(HeapTimer *ht, time_t now);
#include <stdlib.h> #include <stdio.h> #include <string.h> #include "heap_time.h" //添加計時器后 需要不斷上調二茶堆 保證堆頂最小 static void flow_up(HeapTimer *ht, Timer *_timer) { if (ht && _timer) { if (ht->size == 0) { return; } else if (ht->size == 1) { memcpy(&(ht->timer[ht->size]), _timer, sizeof(Timer)); _timer = NULL; return; } int temp = ht->size; while(temp) {
//與父節點比較 if (ht->timer[temp/2].timeout > (_timer->timeout)) { ht->timer[temp].timeout = ht->timer[temp/2].timeout; ht->timer[temp].data = ht->timer[temp/2].data; ht->timer[temp].persist = ht->timer[temp/2].persist; ht->timer[temp].timegap = ht->timer[temp/2].timegap; }else{ break; } temp = temp/2; } memcpy(&(ht->timer[temp]), _timer, sizeof(Timer)); _timer = NULL; } } //刪除堆頂不斷調整 static void flow_down(HeapTimer *ht) { unsigned int start = 1; unsigned int end = ht->size; unsigned int current = start; unsigned int l = 2*current; while(l <= end) {
//當節點存在右孩子,比較左右兩個孩子找到最小的一個 if (l < end && (ht->timer[l+1].timeout < ht->timer[l].timeout)) { l++; }
//拿最后一個與上次比較最小的一個比較 如果比它小就停止將最后一個放到此位置 if (ht->timer[end].timeout < ht->timer[l].timeout) { break; } else{
//將最小的上浮繼續比對 ht->timer[current].timeout = ht->timer[l].timeout; ht->timer[current].data = ht->timer[l].data; ht->timer[current].persist = ht->timer[l].persist; ht->timer[current].timegap = ht->timer[l].timegap; current = l; l = 2*current; } } ht->timer[current].timeout = ht->timer[end].timeout; ht->timer[current].data = ht->timer[end].data; ht->timer[current].persist = ht->timer[end].persist; ht->timer[current].timegap = ht->timer[end].timegap; } HeapTimer * initHeapTimer(size_t s) { if (s <= (size_t)0) { return NULL; } HeapTimer * ht = (HeapTimer *)malloc(sizeof(HeapTimer)); if (!ht) { return NULL; } ht->size = 0; ht->capacity = s+1; ht->timer = (Timer *)malloc(sizeof(Timer) * (s+1)); if (ht->timer) { memset(ht->timer, 0, sizeof(Timer)*(s+1)); return ht; } return NULL; } //插入一個定時器 void min_heap_push(HeapTimer * ht, Timer * _timer) { if (ht && _timer) { if (ht->size == ht->capacity) { size_t need = ht->size + (ht->size)/3; Timer *temp = (Timer *)malloc(sizeof(Timer)*(need)); if(temp){ memset(temp, 0, sizeof(Timer)*(need)); memcpy(temp, ht->timer, sizeof(ht->size)); ht->capacity = need; free(ht->timer); ht->timer = temp; temp = NULL; } } (ht->size)++; flow_up(ht, _timer); } } //刪除一個定時器 void min_heap_pop(HeapTimer * ht, Timer * _timer) { if ( ht && ht->size) { memset(_timer, 0, sizeof(Timer)); memcpy(_timer, &(ht->timer[1]), sizeof(Timer)); ht->timer[1].timeout = ht->timer[ht->size].timeout; ht->timer[1].data = ht->timer[ht->size].data; ht->timer[1].persist = ht->timer[ht->size].persist; ht->timer[1].timegap = ht->timer[ht->size].timegap; if (ht->size > 1) { flow_down(ht); } ht->timer[ht->size].timeout = 0; ht->timer[ht->size].data = NULL; ht->timer[ht->size].persist = 0; ht->timer[ht->size].timegap = 0; (ht->size)--; } } //檢查是否超時 int check_had_timeout(HeapTimer *ht, time_t now) { if (ht) { if (ht->size > (size_t)0) { return (ht->timer[1]).timeout < now; } } return 0; } //為測試使用 int main() { HeapTimer *timer = initHeapTimer(50); Timer t1; t1.timegap = 3; t1.timeout = time(NULL) + t1.timegap; t1.persist = 1; Timer t2; t2.timegap = 6; t2.timeout = time(NULL) + 6; t2.persist = 0; min_heap_push(timer, &t1); min_heap_push(timer, &t2); while(1) { if (check_had_timeout(timer, time(NULL))) //不斷檢查和調整 { Timer temp; min_heap_pop(timer, &temp); // if (temp.persist) { temp.timeout = time(NULL) + temp.timegap; min_heap_push(timer, &temp); } printf("timeout %u\n", timer->size); } usleep(100); } return 0; }
堆計時器的效率還是可以的,插入刪除logN。目前在網絡編程中 最小堆計時常用到。
總結:
綜上所述這三種常用的計時器做了簡單介紹,其中實例代碼仍有許多改進之處,存在不足之處敬請見諒及反饋,可以在github上找到: https://github.com/BambooAce/MyEvent/tree/master/src比較這三種定時器,一般在網絡編程中較推薦的是鏈表計時器和最小堆計時器,在最新的libevent中也同時提供了這兩種計時器可供選擇。在網絡編程中不乏缺少心跳檢測,當客戶端數量十分龐大時,小根堆定時器的好處就很明顯了。