轉載位址:http://blog.csdn.net/w616589292/article/details/45694987
在開發Linux網絡程式時,通常需要維護多個定時器,如維護用戶端心跳時間、檢查多個資料包的逾時重傳等。如果采用Linux的SIGALARM信号實作,則會帶來較大的系統開銷,且不便于管理。
本文在應用層實作了一個基于時間堆的高性能定時器,同時考慮到定時的粒度問題,由于通過alarm系統調用設定的SIGALARM信号隻能以秒為機關觸發,是以需要采用其它手段實作更細粒度的定時操作,當然,這裡不考慮使用多線程+sleep的實作方法,理由性能太低。
通常的做法還有采用基于升序的時間連結清單,但升序時間連結清單的插入操作效率較低,需要周遊連結清單。是以本實作方案使用最小堆來維護多個定時器,插入O(logn)、删除O(1)、查找O(1)的效率較高。
首先是每個定時器的定義:
[cpp] view plain copy
- class heap_timer
- {
- public:
- heap_timer( int ms_delay )
- {
- gettimeofday( &expire, NULL );
- expire.tv_usec += ms_delay * 1000;
- if ( expire.tv_usec > 1000000 )
- {
- expire.tv_sec += expire.tv_usec / 1000000;
- expire.tv_usec %= 1000000;
- }
- }
- public:
- struct timeval expire;
- void (*cb_func)( client_data* );
- client_data* user_data;
- ~heap_timer()
- {
- delete user_data;
- }
- };
包括一個逾時時間expire、逾時回調函數cb_func以及一個user_data變量,user_data用于存儲與定時器相關的使用者資料,使用者資料可以根據不同的應用場合進行修改,這裡實作的是一個智能博物館的網關,網關接收來自zigbee協調器的使用者資料,并為每個使用者維護一段等待時間T,在T到來之前,同一個使用者的所有資料都存放到user_data的target_list中,當T到來時,根據target_list清單選擇一個适當的target并發送到ip_address,同時删除定時器(有點扯遠了=。=)。總之,要實作的功能就是給每個使用者維護一個定時器,定時值到來時做一些操作。
[cpp] view plain copy
- class client_data
- {
- public:
- client_data(char *address):target_count(0)
- {
- strcpy(ip_address,address);
- }
- private:
- char ip_address[32];
- target target_list[64];
- int target_count;
- ......
- };
以下是時間堆的類定義,包括了一些基本的堆操作:插入、删除、擴容,還包括了定時器溢出時的操作函數tick()
[cpp] view plain copy
- class time_heap
- {
- public:
- time_heap( int cap = 1) 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()
- {
- for ( int i = 0; i < cur_size; ++i )
- {
- delete array[i];
- }
- delete [] array;
- }
- public:
- int get_cursize()
- {
- return cur_size;
- }
- void add_timer( heap_timer* timer ) throw ( std::exception )
- {
- if( !timer )
- {
- return;
- }
- if( cur_size >= capacity )
- {
- resize();
- }
- int hole = cur_size++;
- int parent = 0;
- for( ; hole > 0; hole=parent )
- {
- parent = (hole-1)/2;
- if ( timercmp( &(array[parent]->expire), &(timer->expire), <= ) )
- {
- break;
- }
- array[hole] = array[parent];
- }
- array[hole] = timer;
- }
- void del_timer( heap_timer* timer )
- {
- if( !timer )
- {
- return;
- }
- // lazy delelte
- timer->cb_func = NULL;
- }
- int top(struct timeval &time_top) const
- {
- if ( empty() )
- {
- return 0;
- }
- time_top = array[0]->expire;
- return 1;
- }
- 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];
- struct timeval cur;
- gettimeofday( &cur, NULL );
- while( !empty() )
- {
- if( !tmp )
- {
- break;
- }
- if( timercmp( &cur, &(tmp->expire), < ) )
- {
- break;
- }
- if( array[0]->cb_func )
- {
- array[0]->cb_func( array[0]->user_data );
- }
- pop_timer();
- tmp = array[0];
- }
- }
- bool empty() const
- {
- return cur_size == 0;
- }
- heap_timer** get_heap_array()
- {
- return array;
- }
- private:
- 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)) && timercmp( &(array[child+1]->expire), &(array[child]->expire), < ) )
- {
- ++child;
- }
- if ( timercmp( &(array[child]->expire), &(temp->expire), < ) )
- {
- array[hole] = array[child];
- }
- else
- {
- break;
- }
- }
- array[hole] = temp;
- }
- 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;
- };
如何用epoll實作多個定時器的操作是本設計的關鍵,我們知道,epoll_wait的最後一個參數是阻塞等待的時候,機關是毫秒。可以這樣設計:
1、當時間堆中沒有定時器時,epoll_wait的逾時時間T設為-1,表示一直阻塞等待新使用者的到來;
2、當時間堆中有定時器時,epoll_wait的逾時時間T設為最小堆堆頂的逾時值,這樣可以保證讓最近觸發的定時器能得以執行;
3、在epoll_wait阻塞等待期間,若有其它的使用者到來,則epoll_wait傳回n>0,進行正常的處理,随後應重新設定epoll_wait為小頂堆堆頂的逾時時間。
為此,本實作對epoll_wait進行了封裝,名為tepoll_wait,調用接口與epoll_wait差不多,但傳回值有所不同:tepoll_wait不傳回n=0的情況(即逾時),因為逾時事件在tepoll_wait中進行處理,隻有等到n>0(即在等待過程中有使用者資料到來)或者n<0(出現錯誤)才進行傳回。
廢話不多說,看代碼最清楚:
[cpp] view plain copy
- void timer_handler()
- {
- heap.tick();
- //setalarm();
- }
- int tepoll_wait( int epollfd, epoll_event *events, int max_event_number )
- {
- struct timeval now;
- struct timeval tv;
- struct timeval *tvp;
- //tevent_t *tp;
- int n;
- for ( ;; )
- {
- if ( gettimeofday( &now, NULL ) < 0 )
- perror("gettimeofday");
- struct timeval time_top;
- if ( heap.top(time_top) )
- {
- tv.tv_sec = time_top.tv_sec - now.tv_sec;;
- tv.tv_usec = time_top.tv_usec - now.tv_usec;
- if ( tv.tv_usec < 0 )
- {
- tv.tv_usec += 1000000;
- tv.tv_sec--;
- }
- tvp = &tv;
- }
- else
- tvp = NULL;
- if(tvp == NULL)
- n = epoll_wait( epollfd, events, max_event_number, -1 );
- else
- n = epoll_wait( epollfd, events, max_event_number, tvp->tv_sec*1000 + tvp->tv_usec/1000 );
- if ( n < 0 )
- return -1;
- if ( n > 0 )
- return n;
- timer_handler();
- }
- }
代碼一目了然,在tepoll_wait中,是個死循環,隻有等到上述兩種情況發生時,才進行傳回,此時在調用方進行處理,處理過程跟epoll_wait一樣。
[cpp] view plain copy
- while( !stop_server )
- {
- number = tepoll_wait( epollfd, events, MAX_EVENT_NUMBER);
- for ( i= 0; i < number; i++ )
- {
- int fd = events[i].data.fd;
- if ( (events[i].events & EPOLLIN)&& (fd == uart_fd) )
- {
- //讀取使用者資料
- if( (timer_id = find_exist_timer(ip_address)) != -1)
- {
- //add to the exist timer
- heap_timer ** heap_array = heap.get_heap_array();
- heap_array[timer_id]->user_data->add_target(RSSI,target_id);
- continue;
- }
- <span style="white-space:pre"> </span>//new timer
- heap_timer *timer = new heap_timer(200);
- timer->cb_func = cb_func;
- timer->user_data = new client_data(ip_address);
- timer->user_data->add_target(RSSI,target_id);
- heap.add_timer(timer);
- }
- else if( ( fd == pipefd[0] ) && ( events[i].events & EPOLLIN ) )
- {
- //此處進行了統一信号源處理,通過雙向管道來擷取SIGTERM以及SIGINT的信号,在主循環中進行統一處理
- <span style="white-space:pre"> </span>char signals[1024];
- ret = recv( pipefd[0], signals, sizeof( signals ), 0 );
- if( ret == -1 )
- {
- continue;
- }
- else if( ret == 0 )
- {
- continue;
- }
- else
- {
- for( int i = 0; i < ret; ++i )
- {
- switch( signals[i] )
- {
- case SIGTERM:
- case SIGINT:
- {
- stop_server = true;
- }
- }
- }
- }
- }
- }
- }
- 頂
- 1
- 踩