C++实现简单的定时器
来源:互联网 发布:网易数据分析师笔试题 编辑:程序博客网 时间:2024/05/16 05:20
定时器概念:
使用定时器的目的是周期性的执行一个任务,或者是到某一时间去执行某一任务。本章用来处理断开连接超时的客户端,为此,将每个定时时间封装成定时器,并使用链表,时间轮(也是链表),堆等容器类数据结构,对定时时间统一管理。
在网络编程中,我们通过socket创建套接字,然后通过setsockopt()函数设置套接口选项。
函数原型
setsockopt( SOCKET s, int level, int optname, const char FAR* optval, int option);
第三个参数optname可用来指定超时接受(SO_RCVTIMEO)或者超时发送(SO_SNDTIMEO),与其关联的第四个参数此时为timeout类型,指定具体的超时时间。然后用connect()函数去连接客户端,超时对应的errno是EINPROGRESS。检测到此errno则关闭连接。此处根据系统调用的返回值来判断超时时间是否已到,据此处理定时任务即关闭连接。
除了通过系统调用判断,更多的使用信号。SIGALRM是在定时器终止时发送给进程的信号。由alarm()和setitimer()函数设置的实时闹钟一旦超时,将触发此信号,然后在其处理函数中处理到期的任务。
为了便于处理多个同类型不同时间的定时事件,我们可以设计基于升序链表的定时器,即保持一个超时时间从小到大的时间有序的定时器链表。
主要部分有三个
1./*用户数据结构*/struct client_data{ sockaddr_in address; /*客户端socket地址*/ int sockfd; /*客户端套接字*/ char buffer[ BUFFER_SIZE ]; /*读缓冲区*/ util_timer* timer; /*定时器*/};2./*定时器类*/struct util_timer{ public: util_timer() : prev( NULL ), next( NULL ) { } public: time_t expire; /*任务的超时时间*/ void ( *cd_func )(client_data*); /*任务回调函数*/ client_data* user_data; /*用户数据结构*/ util_timer* prev; /*双向链表*/ util_timer* next;};3./*定时器链表。它是一个升序、双向链表,且带有头结点和尾节点*/class sort_timer_lst{ public: /*构造函数*/ sort_timer_lst() : head( NULL ), tail( NULL ) {} /*析构函数,清空定时器链表*/ ~sort_timer_lst(); /*将目标定时器timer添加到链表中*/ void add_timer( util_timer* timer ); /*当某个定时任务发生变化时,调整对应的定时器在链表的位置,这个函数只考虑被调整的定时器事件延长情况,即只需向尾部移*/ void adjust_timer( util_timer* timer ); /*将目标定时器timer从链表中删除*/ void del_timer( util_timer* timer ); /*SIGALRM信号每次被触发就在其信号处理函数中执行一次tick函数,以处理链表上到期的任务*/ void tick(); private: /*一个重载的辅助函数,被函数add和adjust调用,该函数表示将time添加到head之后的位置*/ void add_timer( util_timer* timer, util_timer* lst_head ); private: util_timer* head; util_timer* tail;};
信号处理函数
void tick(){ time_t cur = time(NULL); /*获取当前时间*/ util_timer* temp = head; while( temp ) { if( cur < temp-> expire ) /*超过当前时间则为超时*/ { break; } temp -> cb_func( tmp -> user_data ); /*...删除处理完的定时器,处理头结点...*/ }}
SIGALRM信号每次触发就执行一次tick()函数,通过与当前时间比较来判断是否为超时事件。如果是则处理类中的信号处理函数。
基于升序链表的定时器用起来很方便,但一旦定时事件多起来,效率就会很低。
因此,通过改进,就有了时间轮。
指针每转动一下为一个滴答,图中指向1的指针为当前槽,指向2的指针(本来是虚线来着,技术渣没画出来)指向下一个槽。
一个滴答的时间称为时间轮的槽间隔si(心博时间),时间轮共N个槽,因此运转一周时间为 N*si . 每个槽指向一个定时器链表,每个链表上的定时器具有相同的特征,他们的定时时间相差 N*si的整数倍。
如果N=12,指针指向了tick=2处,要加一个再经过tick为15的定时任务,怎么放置?
计算可得:2 + 15%12 = 5,所以将其放置在tick=5的槽中,待tick从2跳一轮回到2再跳3下到tick=5,这个事件就被执行。
简单的时间轮类(仅有一个轮子):
class tw_timer{public: tw_timer(int rot, int 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(); ~time_wheel(); /*根据定时值timeout创建一个定时器并插入合适槽中*/ tw_timerr* add_timer( int timeout ); /*删除目标定时器*/ void del_timer( tw_timer* timer ); /*SI时间到后,调用该哈数,时间轮像前滚动一个槽*/ void tick();}
添加或者删除一个定时器的复杂度是O(1),执行一个定时器的时间复杂度为O(n)。
时间堆
这章的学习中,感觉效率比较高的是时间堆。
因为处理定时器事件的时候都是超时时间最小的,所以可以采用最小堆的方式来打理这群定时器,即每个节点的值都小于或等于他的子节点的值的二叉树。
插入定时器
在树下创建一个空穴,将新节点插入,如果不影响堆序,则插入完成,否则执行上滤操作。即交换空穴和它的父节点上的元素,不断执行直到插入成功。例如插入值为14的元素。时间复杂度为O(lgn)。
时间堆的删除很简单,即删除堆的根结点即可。删除后调整堆序,先在根结点建立空穴,此时根结点已被删除。此时堆中少了一个元素,我们可以把堆最后一个元素X移动到该堆之外(但不能丢),如果X可以插入根据诶点,则删除成功。但一般不能,此时就对堆中元素执行上滤操作,即交换空穴和他的两个儿子中的较小者,不断进行此过程,直到X可以被成功插入,删除成功。删除一个定时器的时间复杂度为O(1)。
下滤操作代码:(二叉树采用数组存储,对于任意i位置,2i+1为左孩子)
time_heap::percolate_down( int hole ) //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( arrry[child] -> expire < temp -> expire ) { array[hole] = array[child]; } else { break; } } array[hole] = temp;}
定时器中的时间轮优化下,可以有多层轮子,提高精确度。比如初始化一个三层时间轮:秒刻盘:0~59个SecList, 分刻盘:0~59个MinList, 时刻盘:0~12个HourList。
SecTick由外界推动,每跳一轮(60格),SecTick复位至0,同时MinTick跳1格;
同理MinTick每跳一轮(60格),MinTick复位至0,同时HourTick跳1格;
最高层:HourTick跳一轮(12格),HourTick复位至0,一个时间轮完整周期完成。恩,有时间了回来实现吧。
- 一个简单定时器的实现(C++)
- 一个简单定时器的实现(C++)
- 简单定时器的实现
- Android简单定时器的实现
- C++实现简单的定时器
- java简单的定时器实现
- Golang 实现简单的定时器
- Golang实现简单的定时器
- 多媒体定时器 C的实现
- [Java定时器]用Spring Task实现一个简单的定时器.
- linux 实现简单定时器的功能
- 用线程实现简单的定时器
- Java web中简单的定时器实现
- libevent实现的简单定时器功能
- 多线程之简单定时器的实现代码
- java开发中简单定时器的实现
- 简单定时器实现
- 简单定时器实现
- 【poj3258】River Hopscotch
- svn版本回退
- AngularJs1.X 项目文件结构
- 最新spring 4 框架和 security相关包下载
- Red packet (二分)
- C++实现简单的定时器
- 数据结构HASH总结一:理论学习篇
- Hamburgers
- Android - 文件读写操作 总结
- 设计模式的7原则及23种设计模式概要
- 饭卡
- k-Multiple Free Set
- css3选择器
- 第一个JNI开发