Linux提供了3种定时方法:
1,socket选项SO_RCVTIMEO和SO_SNDTIMEO。
2,SIGALRM信号。
3,I/O复用系统调用的超时参数。
此文额外包含了2种高效定时器:
时间轮与时间堆
socket选项SO_RCVTIMEO和SO_SNDTIMEO
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <assert.h> #include <stdio.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> #include <string.h> //超时连接函数 int timeout_connect(const char* ip, int port, int time) { int ret = 0; struct sockaddr_in address; bzero(&address,sizeof(address)); address.sin_family = AF_INET; inet_pton(AF_INET,ip,&address.sin_addr); address.sin_port = htons(port); int sockfd = socket(PF_INET,SOCK_STREAM,0); assert(sockfd >= 0); struct timeval timeout; timeout.tv_sec = time; timeout.tv_usec = 0; socklen_t len = sizeof(timeout); ret = setsockopt(sockfd,SOL_SOCKET,SO_SNDTIMEO,&timeout,len); assert(ret != -1); ret = connect(sockfd,(struct sockaddr*)&address,sizeof(address)); if(ret == -1) { if(errno == EINPROGRESS) { printf("connectine timeout,process timeout logic\n"); return -1; } printf("error occur when connecting to server\n"); return -1; } return sockfd; } int main(int argc,char* argv[]) { if(argc <= 2) { printf("usage: %s ip_address port_number\n",basename(argv[0])); return 1; } const char* ip = argv[1]; int port = atoi(argv[2]); int sockfd = timeout_connect(ip,port,10); if(sockfd < 0) { return 1; } return 0; }
基于升序的链表的定时器
sort_timer_lst是一个升序链表。其核心函数tick相当于一个心搏函数,它每隔一段固定的时间就执行一次,以检测并处理到期的任务。判定定时任务到期的依据是定时器的expire值小于当前的系统事件。从执行效率来看,添加定时器的时间复杂度是O(n),删除定时器的时间复杂度是O(1)。
#ifndef LST_TIMER #define LST_TIMER #include <time.h> #define BUFFER_SIZE 64 class util_timer; /* * 用户数据结构:客户端socket地址,socket文件描述符,读缓存和定时器 */ struct client_data { sockaddr_in address; int sockfd; char buf[BUFFER_SIZE]; util_timer* timer; }; /*定时器类*/ class util_timer { public: util_timer() : prev(NULL), next(NULL) { } public: time_t expire;/*任务的超时时间,这里使用绝对时间*/ void (*cb_func)(client_data*);/*任务回调函数*/ /*回调函数处理的客户数据,由定时器的执行者传递给回调函数*/ client_data* user_data; util_timer* prev;/*指向前一个定时器*/ util_timer* next;/*指向下一个定时器*/ }; /*定时器链表.它时一个升序,双向链表,且带有头结点和尾节点*/ class sort_timer_lst { public: sort_timer_lst() : head(NULL), tail(NULL) { } /*链表被销毁时,删除其中所有的定时器*/ ~sort_timer_lst() { util_timer* tmp = head; while (tmp) { head = tmp->next; delete tmp; tmp = head; } } /*将目标定时器timer添加到链表中*/ void add_timer(util_timer* timer) { if (!timer) { return; } if (!head) { head = tail = timer; return; } /*如果目标定时器的超时事件小于当前链表中所有定时器的超时事件,则把该定时器*插入链表头部 *作为链表新的头节点。否则就需要调用重载函数add_timer(util_timer* timer, * util_timer* lst_head),把它插入链表中合适的位置,以保证链表的升序特性 */ if (timer->expire < head->expire) { timer->next = head; head->prev = timer; head = timer; return; } add_timer(timer, head); } /*当某个定时任务发生变化时,调整对应的定时器在链表中的位置。这个函数只考虑被调整的定时器的超时 时间延长情况,即该定时器需要往链表的尾部移动*/ void adjust_timer(util_timer* timer) { if (!timer) { return; } util_timer* tmp = timer->next; /*如果被调整的目标定时器处在链表尾部,或者该定时器新的超时值仍然小于其下一个定时器的超时值,则 * 不用调整 */ if (!tmp || (timer->expire < tmp->expire)) { return; } /*如果目标定时器时链表头节点,则将该定时器从链表中取出并重新插入链表*/ if (timer == head) { head = head->next; head->prev = NULL; timer->next = NULL; add_timer(timer, head); } /*如果目标定时器不是链表头节点,则将该定时器从链表中取出,然后插入其原来所在位置之后 * 的部分链表中 */ else { timer->prev->next = timer->next; timer->next->prev = timer->prev; add_timer(timer, timer->next); } } /*将目标定时器timer从链表中删除*/ void del_timer(util_timer* timer) { if (!timer) { return; } /*下面这个条件成立表示链表中只有一个定时器,即目标定时器*/ if ((timer == head) && (timer == tail)) { delete timer; head = NULL; tail = NULL; return; } /*如果链表中至少有两个定时器,且目标定时器时链表的头节点,则将链表的头节点重置为 * 原头节点的下一个节点,然后删除目标定时器*/ if (timer == head) { head = head->next; head->prev = NULL; delete timer; return; } /*如果链表中至少有两个定时器,且目标定时器是链表的尾节点,则将链表的尾节点重置为 * 原尾节点的前一个节点,然后删除目标定时器*/ if (timer == tail) { tail = tail->prev; tail->next = NULL; delete timer; return; } /*如果目标定时器位于链表的中间,则把它前后的定时器串联起来,然后删除目标定时器*/ timer->prev->next = timer->next; timer->next->prev = timer->prev; delete timer; } /*SIGALRM信号每次被触发就在其信号处理函数(如果使用统一事件源,则主函数)中执行一次tick函数 * 以处理链表上到期的任务*/ void tick() { if (!head) { return; } printf("timer tick\n"); time_t cur = time(NULL); util_timer* tmp = head; /*从头节点开始依次处理每个定时器,直到遇到一个尚未到期的定时器,这就是定时器的核心逻辑*/ while (tmp) { /*因为每个定时器都使用绝对时间作为超时值,所以我们可以把定时器的超时值和系统的当前事件, * 比较以判定定时器是否到期*/ if (cur < tmp->expire) { break; } /*调用定时器的回调函数,以执行定时任务*/ tmp->cb_func(tmp->user_data); /*执行完定时器中的定时任务之后,就将它从链表中删除,并重置链表头节点*/ head = tmp->next; if (head) { head->prev = NULL; } delete tmp; tmp = head; } } private: /*一个重载的辅助函数,它被公有的add_timer函数和adjust_timer函数调用.该函数表示将 * 目标定时器timer添加到节点lst_head之后的部分链表中*/ void add_timer(util_timer* timer, util_timer* lst_head) { util_timer* prev = lst_head; util_timer* tmp = prev->next; /*遍历lst_head节点之后的部分链表,直到找到一个超时时间大于目标定时器的超时时间的节点, * 并将目标定时器插入该节点之前*/ while (tmp) { if (timer->expire < tmp->expire) { prev->next = timer; timer->next = tmp; tmp->prev = timer; timer->prev = prev; break; } prev = tmp; tmp = tmp->next; } /*如果遍历完lst_head节点之后的部分链表,仍未找到超时时间大于目标定时器的超时时间的节点, * 则将目标定时器插入链表尾部,并把它设置为链表新的尾节点*/ if (!tmp) { prev->next = timer; timer->prev = prev; timer->next = NULL; tail = timer; } } private: util_timer* head; util_timer* tail; }; #endif
/** * 处理非活动连接 * 上述升序定时器链表的实际应用---处理非活动连接。服务器程序通常要定期处理非活动连接:给客户端发一个重连请求,或者关闭该连接,或者其他。Linux在内核中提供了对连接是否处于活动状态的定期检查机制,我们可以通过socket选项KEEPALIVE来激活它。不过使用这种方式将使得应用程序对连接的管理变得复杂。因此,我们可以考虑在应用层实现类似于KEEPALIVE机制,以管理所有长时间处于非活动状态的连接。 */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <signal.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <stdlib.h> #include <sys/epoll.h> #include <pthread.h> #include "lst_timer.h" #define FD_LIMIT 65535 #define MAX_EVENT_NUMBER 1024 #define TIMESLOT 5 static int pipefd[2]; static sort_timer_lst timer_lst; static int epollfd = 0; int setnonblocking( int fd ) { int old_option = fcntl( fd, F_GETFL ); int new_option = old_option | O_NONBLOCK; fcntl( fd, F_SETFL, new_option ); return old_option; } void addfd( int epollfd, int fd ) { epoll_event event; event.data.fd = fd; event.events = EPOLLIN | EPOLLET; epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event ); setnonblocking( fd ); } void sig_handler( int sig ) { int save_errno = errno; int msg = sig; send( pipefd[1], ( char* )&msg, 1, 0 ); errno = save_errno; } void addsig( int sig ) { struct sigaction sa; memset( &sa, '\0', sizeof( sa ) ); sa.sa_handler = sig_handler; sa.sa_flags |= SA_RESTART; sigfillset( &sa.sa_mask ); assert( sigaction( sig, &sa, NULL ) != -1 ); } void timer_handler() { /*定时处理任务,实际上就是调用tick函数*/ <span style="white-space:pre"> </span>timer_lst.tick(); <span style="white-space:pre"> </span>/*因为一次alarm调用只会引起一次SIGALRM信号,所以我们要重新定时,以不断触发 <span style="white-space:pre"> </span> * SIGALRM信号*/ alarm( TIMESLOT ); } /*定时器回调函数,它删除非活动连接socket上的注册事件,并关闭之*/ void cb_func( client_data* user_data ) { epoll_ctl( epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0 ); assert( user_data ); close( user_data->sockfd ); printf( "close fd %d\n", user_data->sockfd ); } int main( int argc, char* argv[] ) { if( argc <= 2 ) { printf( "usage: %s ip_address port_number\n", basename( argv[0] ) ); return 1; } const char* ip = argv[1]; int port = atoi( argv[2] ); int ret = 0; struct sockaddr_in address; bzero( &address, sizeof( address ) ); address.sin_family = AF_INET; inet_pton( AF_INET, ip, &address.sin_addr ); address.sin_port = htons( port ); int listenfd = socket( PF_INET, SOCK_STREAM, 0 ); assert( listenfd >= 0 ); ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) ); assert( ret != -1 ); ret = listen( listenfd, 5 ); assert( ret != -1 ); epoll_event events[ MAX_EVENT_NUMBER ]; int epollfd = epoll_create( 5 ); assert( epollfd != -1 ); addfd( epollfd, listenfd ); ret = socketpair( PF_UNIX, SOCK_STREAM, 0, pipefd ); assert( ret != -1 ); setnonblocking( pipefd[1] ); addfd( epollfd, pipefd[0] ); // add all the interesting signals here //设置信号处理函数 addsig( SIGALRM ); addsig( SIGTERM ); bool stop_server = false; client_data* users = new client_data[FD_LIMIT]; bool timeout = false; alarm( TIMESLOT );/*定时*/ while( !stop_server ) { int number = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 ); if ( ( number < 0 ) && ( errno != EINTR ) ) { printf( "epoll failure\n" ); break; } for ( int i = 0; i < number; i++ ) { int sockfd = events[i].data.fd; /*处理新到的客户连接*/ if( sockfd == listenfd ) { struct sockaddr_in client_address; socklen_t client_addrlength = sizeof( client_address ); int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength ); addfd( epollfd, connfd ); users[connfd].address = client_address; users[connfd].sockfd = connfd; /*创建定时器,设置其回调函数与超时时间,然后绑定定时器与用户数据,最后 *将定时器添加到链表timer_lst中*/ util_timer* timer = new util_timer; timer->user_data = &users[connfd]; timer->cb_func = cb_func; time_t cur = time( NULL ); timer->expire = cur + 3 * TIMESLOT; users[connfd].timer = timer; timer_lst.add_timer( timer ); } /*处理信号*/ else if( ( sockfd == pipefd[0] ) && ( events[i].events & EPOLLIN ) ) { int sig; char signals[1024]; ret = recv( pipefd[0], signals, sizeof( signals ), 0 ); if( ret == -1 ) { // handle the error continue; } else if( ret == 0 ) { continue; } else { for( int i = 0; i < ret; ++i ) { switch( signals[i] ) { case SIGALRM: { /*用timeout变量标记有定时任务需要处理,但不立即处理定时任务,这是因为 *定时任务的优先级不是很高,我们优先处理更重要的任务*/ timeout = true; break; } case SIGTERM: { stop_server = true; } } } } } /*处理客户连接上接收到的数据*/ else if( events[i].events & EPOLLIN ) { memset( users[sockfd].buf, '\0', BUFFER_SIZE ); ret = recv( sockfd, users[sockfd].buf, BUFFER_SIZE-1, 0 ); printf( "get %d bytes of client data %s from %d\n", ret, users[sockfd].buf, sockfd ); util_timer* timer = users[sockfd].timer; if( ret < 0 ) { /*如果发生读错误,则关闭连接,并移除其对应的定时器*/ if( errno != EAGAIN ) { cb_func( &users[sockfd] ); if( timer ) { timer_lst.del_timer( timer ); } } } else if( ret == 0 ) { /*如果对方已经关闭连接,则我们也关闭连接,并移除对应的定时器*/ cb_func( &users[sockfd] ); if( timer ) { timer_lst.del_timer( timer ); } } else { /*如果某个客户连接上数据可读,则我们要调整该连接对应的定时器,以延迟该连接被关闭的时间*/ //send( sockfd, users[sockfd].buf, BUFFER_SIZE-1, 0 ); if( timer ) { time_t cur = time( NULL ); timer->expire = cur + 3 * TIMESLOT; printf( "adjust timer once\n" ); timer_lst.adjust_timer( timer ); } } } else { // others } } /*最后处理定时事件,因为I/O事件有更高的优先级。当然,这样做将导致定时任务不能精确地按照预定的时间执行*/ if( timeout ) { timer_handler(); timeout = false; } } close( listenfd ); close( pipefd[1] ); close( pipefd[0] ); delete [] users; return 0; }
I/O复用系统调用的超时参数
/* *Linux下的3组I/O服用系统调用都带有超时参数,因此它们不仅能统一处理信号和I/O事件,也能统一处理定时事件。但是由于I/O复用系统调用可能在超时时间到期之前就返回,所以如果我们要利用它们来定时,就需要不断更新定时参数以反映剩余的时间。 */ #define TIMEOUT 5000 int timeout = TIMEOUT; time_t start = time( NULL ); time_t end = time( NULL ); while( 1 ) { printf( "the timeout is now %d mill-seconds\n", timeout ); start = time( NULL ); int number = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, timeout ); if( ( number < 0 ) && ( errno != EINTR ) ) { printf( "epoll failure\n" ); break; } /*如果epoll_wait成功返回0,则说明超时时间到,此时便可处理定时任务,并重置定时 * 时间*/ if( number == 0 ) { // timeout timeout = TIMEOUT; continue; } end = time( NULL ); /*如果epoll_wait返回值大于0,则本次epoll_wait调用持续的时间时(end - start) *1000ms,我们需要将定时时间timeout减去这段时间,以获得下次epoll_wait调用的超时 参数 */ timeout -= ( end - start ) * 1000; /* * 重新计算之后的timeout值有可能等于0,说明本次epoll_wait调用返回时,不仅有文件描述 * 符就绪,并且其超时时间也刚好到达,此时我们也要处理定时任务,并重置定时时间。 * */ if( timeout <= 0 ) { // timeout timeout = TIMEOUT; } // handle connections }
高效的定时器
时间轮
#ifndef TIME_WHEEL_TIMER #define TIME_WHEEL_TIMER #include <time.h> #include <netinet/in.h> #include <stdio.h> #define BUFFER_SIZE 64 class tw_timer; /*绑定socket和定时器*/ struct client_data { sockaddr_in address; int sockfd; char buf[BUFFER_SIZE]; tw_timer* timer; }; /*定时器类*/ class tw_timer { public: tw_timer(int rot, int ts) : next(NULL), prev(NULL), rotation(rot), time_slot(ts) { } public: int rotation;/*记录定时器在时间轮转多少圈后生效*/ int time_slot;/*记录定时器属于时间轮上哪个槽(对应的链表,下同)*/ void (*cb_func)(client_data*);/*定时器回调函数*/ client_data* user_data;/*客户数据*/ tw_timer* next;/*指向下一个定时器*/ tw_timer* prev;/*指向前一个定时器*/ }; class time_wheel { public: time_wheel() : cur_slot(0) { for (int i = 0; i < N; ++i) { slots[i] = NULL;/*初始化每个槽的头结点*/ } } ~time_wheel() { /*遍历每个槽,并销毁其中的定时器*/ for (int i = 0; i < N; ++i) { tw_timer* tmp = slots[i]; while (tmp) { slots[i] = tmp->next; delete tmp; tmp = slots[i]; } } } /*根据定时值timeout创建一个定时器,并把它插入合适的槽中*/ tw_timer* add_timer(int timeout) { if (timeout < 0) { return NULL; } int ticks = 0; /*下面根据待插入定时器的超时值计算它将在时间轮转动多少个滴答后被触发,并将 该滴答数存储于变量ticks中,如果待插入定时器的超时值小于时间 轮的槽间隔SI,则将ticks向上折合为1,否则就将ticks向下折合为timeout/SI*/ if (timeout < TI) { ticks = 1; } else { ticks = timeout / TI; } /*计算传入插入的定时器在时间轮转动多少圈后被触发*/ int rotation = ticks / N; /*计算待插入的定时器应该被插入哪个槽中*/ int ts = (cur_slot + (ticks % N)) % N; /*创建新的定时器,它在时间轮转动rotation圈之后被触发,且位于第 ts个槽上*/ tw_timer* timer = new tw_timer(rotation, ts); /*如果第ts个槽中尚无任何定时器,则把新建的定时器插入其中,并将该定时器设置为该槽的头结点*/ if (!slots[ts]) { printf("add timer, rotation is %d, ts is %d, cur_slot is %d\n", rotation, ts, cur_slot); slots[ts] = timer; } /*否则,将定时器插入第ts个槽中*/ else { timer->next = slots[ts]; slots[ts]->prev = timer; slots[ts] = timer; } return timer; } /*删除目标定时器timer*/ void del_timer(tw_timer* timer) { if (!timer) { return; } int ts = timer->time_slot; /*slots[ts]是目标定时器所在槽的头结点。如果目标定时器就是该头节点,则需要重置第 ts个槽的头节点*/ if (timer == slots[ts]) { slots[ts] = slots[ts]->next; if (slots[ts]) { slots[ts]->prev = NULL; } delete timer; } else { timer->prev->next = timer->next; if (timer->next) { timer->next->prev = timer->prev; } delete timer; } } /*SI时间到后,调用该函数,时间轮向前滚动一个槽的间隔*/ void tick() { /*取得时间轮上当前槽的头结点*/ tw_timer* tmp = slots[cur_slot]; printf("current slot is %d\n", cur_slot); while (tmp) { printf("tick the timer once\n"); /*如果定时器的rotation值大于0,则它在这个轮不起作用*/ if (tmp->rotation > 0) { tmp->rotation--; tmp = tmp->next; } /*否则,说明定时器已经到期,于是执行定时任务,然后删除该定时器*/ else { tmp->cb_func(tmp->user_data); if (tmp == slots[cur_slot]) { printf("delete header in cur_slot\n"); slots[cur_slot] = tmp->next; delete tmp; if (slots[cur_slot]) { slots[cur_slot]->prev = NULL; } tmp = slots[cur_slot]; } else { tmp->prev->next = tmp->next; if (tmp->next) { tmp->next->prev = tmp->prev; } tw_timer* tmp2 = tmp->next; delete tmp; tmp = tmp2; } } } cur_slot = ++cur_slot % N; /*更新时间轮的当前槽,以反映时间轮的转动*/ } private: /*时间轮上槽的数目*/ static const int N = 60; /*每1s时间轮转动一次,即槽间隔为1s*/ static const int TI = 1; /*时间轮的槽,其中每个元素指向一个定时器链表,链表无序*/ tw_timer* slots[N]; /*时间轮的当前槽*/ int cur_slot; }; #endif
时间堆
将所有定时器中超时时间最小的一个定时器的超时值作为心搏间隔。这样,一旦心搏函数tick函数被调用,超时时间最小的定时器必然到期,我们就可以在tick函数中处理该定时器。然后,再次从剩余的定时器中找到 超时时间最小的一个,并将这段最小时间设置为下一次心搏间隔。
插入节点:
在空闲位置创建一个空穴。如果X可以放入空穴中而不破坏堆序,则插入完成。否则旧执行上虑操作,即 交换空穴和它的父节点的元素。不断执行上述过程,直到X可以被放入空穴,则插入完成。
删除节点:
需要在根节点处创建一个空穴。由于堆现在少了一个元素,则删除操作完成,否则执行下虑操作,即交换空穴 和它的两个子节点中的较小者,不断进行上述过程,直到被放入空穴,则删除操作完成。
#ifndef intIME_HEAP #define intIME_HEAP #include <iostream> #include <netinet/in.h> #include <time.h> using std::exception; #define BUFFER_SIZE 64 class heap_timer; /*绑定socket和定时器*/ struct client_data { sockaddr_in address; int sockfd; char buf[ BUFFER_SIZE ]; heap_timer* timer; }; /*定时器类*/ class heap_timer { public: heap_timer( int delay ) { expire = time( NULL ) + delay; } public: time_t expire;/*定时器生效的绝对时间*/ void (*cb_func)( client_data* );/*定时器的回调函数*/ client_data* user_data;/*用户数据*/ }; /*时间堆类*/ class time_heap { public: /*构造器函数之一,初始化一个大小为cap的空堆*/ time_heap( int cap ) throw ( std::exception ) : capacity( cap ), cur_size( 0 ) { array = new heap_timer* [capacity];/*创建堆数组*/ if ( ! array ) { throw std::exception(); } for( int i = 0; i < capacity; ++i ) { array[i] = NULL; } } /*构造器函数之二,用已有数组来初始化堆*/ time_heap( heap_timer** init_array, int size, int capacity ) throw ( std::exception ) : cur_size( size ), capacity( capacity ) { if ( capacity < size ) { throw std::exception(); } array = new heap_timer* [capacity];/*创建堆数组*/ if ( ! array ) { throw std::exception(); } for( int i = 0; i < capacity; ++i ) { array[i] = NULL; } if ( size != 0 ) { /*初始化堆数组*/ for ( int i = 0; i < size; ++i ) { array[ i ] = init_array[ i ]; } for ( int i = (cur_size-1)/2; i >=0; --i ) { /*对数组中的第[(cur_size - 1)/2]~0个元素执行下虑操作*/ percolate_down( i ); } } } /*销毁时间堆*/ ~time_heap() { for ( int i = 0; i < cur_size; ++i ) { delete array[i]; } delete [] array; } public: /*添加目标定时器timer*/ void add_timer( heap_timer* timer ) throw ( std::exception ) { if( !timer ) { return; } /*如果当前堆数组容量不够,则将其扩大1倍*/ if( cur_size >= capacity ) { resize(); } /*新插入了一个元素,当前堆大小加1,hole是新建的空穴位置*/ int hole = cur_size++; int parent = 0; /*对从空穴到根节点的路径上的所有节点执行上虑操作*/ for( ; hole > 0; hole=parent ) { parent = (hole-1)/2; if ( array[parent]->expire <= timer->expire ) { break; } array[hole] = array[parent]; } array[hole] = timer; } /*删除目标定时器timer*/ void del_timer( heap_timer* timer ) { if( !timer ) { return; } // lazy delelte /*仅仅将目标定时器回调函数设置为空,即所谓的延迟销毁。这将节省真正删除该定时器造成的开销 *但这样做容易使堆数组膨胀*/ timer->cb_func = NULL; } /*删除堆顶部的定时器*/ heap_timer* top() const { if ( empty() ) { return NULL; } return array[0]; } void pop_timer() { if( empty() ) { return; } if( array[0] ) { delete array[0]; /*将原来的堆顶元素替换为堆数组中的最后一个元素*/ array[0] = array[--cur_size]; /*对新的堆顶元素执行下虑操作*/ percolate_down( 0 ); } } void tick() { heap_timer* tmp = array[0]; /*循环处理堆中到期的定时器*/ time_t cur = time( NULL ); while( !empty() ) { if( !tmp ) { break; } /*如果堆顶定时器没到期,则退出循环*/ if( tmp->expire > cur ) { break; } /*否则就执行堆顶定时器中的任务*/ if( array[0]->cb_func ) { array[0]->cb_func( array[0]->user_data ); } /*将堆顶元素删除,同时生成新的堆顶定时器(array[0])*/ pop_timer(); tmp = array[0]; } } bool empty() const { return cur_size == 0; } private: /*最小堆的下虑操作,它确保堆数组中以第hole个节点作为根的子树拥有最小堆性质*/ void percolate_down( int hole ) { heap_timer* temp = array[hole]; int child = 0; for ( ; ((hole*2+1) <= (cur_size-1)); hole=child ) { child = hole*2+1; if ( (child < (cur_size-1)) && (array[child+1]->expire < array[child]->expire ) ) { ++child; } if ( array[child]->expire < temp->expire ) { array[hole] = array[child]; } else { break; } } array[hole] = temp; } /*将堆数组容量扩大1倍*/ void resize() throw ( std::exception ) { heap_timer** temp = new heap_timer* [2*capacity]; for( int i = 0; i < 2*capacity; ++i ) { temp[i] = NULL; } if ( ! temp ) { throw std::exception(); } capacity = 2*capacity; for ( int i = 0; i < cur_size; ++i ) { temp[i] = array[i]; } delete [] array; array = temp; } private: heap_timer** array;/*堆数组*/ int capacity;/*堆数组的容量*/ int cur_size;/*堆数组当前包含元素的个数*/ }; #endif