程序项目经常用到定时功能,如网络程序中,每隔固定的时间将发送缓冲中的数据一次性发往对端.
下面介绍一个用posix timerfd实现的定时器, timerfd将定时器当做一个文件描述符,当定时器
到时fd变为可读,可以将这个描述符交给epoll监听,timeout的时候由epoll返回并执行回调.
timer.h
#ifndef _TIMER_H#define _TIMER_H#define MAX_TIMER 4096typedef struct Timer *Timer_t;typedef void (*timer_callback)(Timer_t,void*);typedef struct TimerMgr *TimerMgr_t;extern TimerMgr_t CreateTimerMgr();extern void DestroyTimerMgr(TimerMgr_t*);//如果once=1则调用后RunTimerMgr马上返回,否则之后等TerminateTimerMgr调用之后才会返回extern void RunTimerMgr(TimerMgr_t,int once);extern void TerminateTimerMgr(TimerMgr_t);extern int AddTimer(TimerMgr_t,Timer_t);extern int RemoveTimer(TimerMgr_t,Timer_t);extern Timer_t CreateTimer(struct itimerspec*,timer_callback,void *arg);extern void DestroyTimer(Timer_t*);//默认的itimerspec结构初始器,用于创建固定间隔定时器extern void DefaultInit(struct itimerspec*,long interval);#ifndef TIMERMGR_RUNONCE#define TIMERMGR_RUNONCE(TIMERMGR) RunTimerMgr(TIMERGER,1)#endif#ifndef TIMERMGR_RUN#define TIMERMGR_RUN(TIMERMGR) RunTimerMgr(TIMERGER,0)#endif//创建一个默认的固定间隔定时器,最小单位毫秒#ifndef DEFAULT_TIMER#define DEFAULT_TIMER(INTERVAL,CALLBACK,ARG)\({Timer_t __ret;struct itimerspec __spec;\ DefaultInit(&__spec,INTERVAL);\ __ret = CreateTimer(&__spec,CALLBACK,ARG);\ __ret;})#endif#endif
TimerMgr_t用于管理所有的定时器,RunTimerMgr用于启动epoll主循环,另一种实现方式是CreateTimerMgr
后在后台自动创建一个线程运行epoll主循环,但我倾向于只提供机制,让用户按自己的需求使用接口.
timer.c
#include#include #include #include "SocketWrapper.h"#include "timer.h"#include "epoll.h"struct TimerMgr{ int epollfd; volatile int terminated; struct epoll_event events[MAX_TIMER];};struct Timer{ int fd; void *arg;//callback的第二个参数 timer_callback callback;};TimerMgr_t CreateTimerMgr(){ int epollfd = TEMP_FAILURE_RETRY(epoll_create(MAX_TIMER)); if(epollfd>=0) { TimerMgr_t t = malloc(sizeof(*t)); t->epollfd = epollfd; memset(t->events,0,sizeof(t->events)); return t; } return 0;}void DestroyTimerMgr(TimerMgr_t *t){ close((*t)->epollfd); free(*t); *t = 0;}void TerminateTimerMgr(TimerMgr_t t){ t->terminated = 1;}void RunTimerMgr(TimerMgr_t t,int once){ t->terminated = 0; long long tmp; while(!t->terminated && !once) { int nfds = TEMP_FAILURE_RETRY(epoll_wait(t->epollfd,t->events,MAX_TIMER,100)); if(nfds < 0) { t->terminated = 1; break; } int i; for(i = 0 ; i < nfds ; ++i) { Timer_t _timer = (Timer_t)t->events[i].data.ptr; read(_timer->fd,&tmp,sizeof(tmp)); if(_timer->callback) _timer->callback(_timer,_timer->arg); } } t->terminated = 1;}int AddTimer(TimerMgr_t t,Timer_t _timer){ int ret; struct epoll_event ev; ev.data.ptr = _timer; ev.events = EV_IN | EV_OUT; TEMP_FAILURE_RETRY(ret = epoll_ctl(t->epollfd,EPOLL_CTL_ADD,_timer->fd,&ev)); if(ret != 0) return -1; return 0;}int RemoveTimer(TimerMgr_t t,Timer_t _timer){ int ret; struct epoll_event ev; TEMP_FAILURE_RETRY(ret = epoll_ctl(t->epollfd,EPOLL_CTL_DEL,_timer->fd,&ev)); if(ret != 0) return -1; return 0;}void DefaultInit(struct itimerspec *new_value,long interval){ struct timespec now; clock_gettime(/*CLOCK_REALTIME*/CLOCK_MONOTONIC, &now); int sec = interval/1000; int ms = interval%1000; long long nosec = (now.tv_sec + sec)*1000*1000*1000 + now.tv_nsec + ms*1000*1000; new_value->it_value.tv_sec = nosec/(1000*1000*1000); new_value->it_value.tv_nsec = nosec%(1000*1000*1000); new_value->it_interval.tv_sec = sec; new_value->it_interval.tv_nsec = ms*1000*1000;}Timer_t CreateTimer(struct itimerspec *spec,timer_callback callback,void *arg){ int fd = timerfd_create(/*CLOCK_REALTIME*/CLOCK_MONOTONIC,0); if(fd < 0) return 0; Timer_t t = malloc(sizeof(*t)); if(!t) { close(fd); return 0; } t->callback = callback; t->fd = fd; t->arg = arg; timerfd_settime(fd,TFD_TIMER_ABSTIME,spec,0); return t;}void DestroyTimer(Timer_t *t){ free(*t); *t = 0;}
test.c
#include#include #include #include "timer.h"static int total = 0;void test_callback(Timer_t t,void *arg){ struct timespec now; clock_gettime(CLOCK_REALTIME, &now); printf("%d,%ld\n",now.tv_sec,now.tv_nsec); ++total; TimerMgr_t tmgr = (TimerMgr_t)arg; if(total == 20) { RemoveTimer(tmgr,t); DestroyTimer(&t); TerminateTimerMgr(tmgr); }}int main(){ TimerMgr_t t = CreateTimerMgr(); Timer_t _timer = DEFAULT_TIMER(500,test_callback,(void*)t); AddTimer(t,_timer); RunTimerMgr(t); DestroyTimerMgr(&t); return 0;}
默认的DefaultInit只提供了初始化固定间隔定时器的功能,只需要提供合适的初始化函数便可实现
整点报时,闹钟等定时器。