这节我们来探讨一下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中也同时提供了这两种计时器可供选择。在网络编程中不乏缺少心跳检测,当客户端数量十分庞大时,小根堆定时器的好处就很明显了。