ortp定时器源码分析
ortp中的定时器模块仅仅在数据收发的调度器中有被使用,所以非常简单。由于要跨平台所以定时器的代码实现都是调用了各个平台上的原生定时器接口。本文主要基于windows平台实现进行分析。
架构
定时器是一个结构体,结构体中含有初始化,等待超时,销毁三个函数指针,使用的时候通过调用这几个函数来使用。还有一个周期成员但是其实并没有任何作用,真正的周期已经写死在代码里无法修改。
结构体
RtpTimer
typedef void (*RtpTimerFunc)(void);
struct _RtpTimer
{
int state;
#define RTP_TIMER_RUNNING 1
#define RTP_TIMER_STOPPED 0
RtpTimerFunc timer_init;
RtpTimerFunc timer_do;
RtpTimerFunc timer_uninit;
struct timeval interval;
};
typedef struct _RtpTimer RtpTimer;
interval
是一个timeval
类型的时间间隔,表示定时器的周期。
其他三个可以看出是函数指针,因为ortp是一个跨平台库,所以这三个函数指针分别指向本平台的三个定时器函数。timer_init
函数是初始化函数,需要在使用前调用一次。timer_uninit
函数是销毁函数,需要在使用结束后调用一次。timer_do
是一个阻塞函数,调用后会阻塞interval
所设定的时间。
函数实现
window平台函数实现
#ifdef ORTP_WINDOWS_DESKTOP
#include <windows.h>
#include <mmsystem.h>
MMRESULT timerId;
HANDLE TimeEvent;
int late_ticks;
static DWORD posix_timer_time;
static DWORD offset_time;
#define TIME_INTERVAL 50
#define TIME_RESOLUTION 10
#define TIME_TIMEOUT 100
void CALLBACK timerCb(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
{
// Check timerId
if (timerId == uID)
{
SetEvent(TimeEvent);
posix_timer_time += TIME_INTERVAL;
}
}
void win_timer_init(void)
{
timerId = timeSetEvent(TIME_INTERVAL,10,timerCb,0,TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
TimeEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
late_ticks = 0;
offset_time = GetTickCount();
posix_timer_time=0;
}
void win_timer_do(void)
{
DWORD diff;
// If timer have expired while we where out of this method
// Try to run after lost time.
if (late_ticks > 0)
{
late_ticks--;
posix_timer_time+=TIME_INTERVAL;
return;
}
diff = GetTickCount() - posix_timer_time - offset_time;
if( diff>TIME_INTERVAL && (diff<(1<<31)))
{
late_ticks = diff/TIME_INTERVAL;
ortp_warning("we must catchup %i ticks.",late_ticks);
return;
}
WaitForSingleObject(TimeEvent,TIME_TIMEOUT);
return;
}
void win_timer_close(void)
{
timeKillEvent(timerId);
}
RtpTimer posix_timer={ 0,
win_timer_init,
win_timer_do,
win_timer_close,
{0,TIME_INTERVAL * 1000}};
定时器在不同平台各有实现,此处分析windows平台上的实现。从代码可以看出,定时器调用了windows的多媒体定时器接口。可以看到定时器所用到的变量都是全局的,说明这个定时器是全局唯一的。
posix_timer_time
记录了定时器运行的毫秒数
offset_time
记录了定时器启动时的时间(开机后的毫秒数)
late_ticks
是定时器发生异常需要追赶的次数,通常情况是不会异常的。
timerId
windows多媒体定时器ID
TimeEvent
用于唤醒的事件
posix_timer
是存储了本平台上实现的相关定时器函数指针的结构体
win_timer_init
函数用来启动定时器,函数创建了周期50毫秒windows多媒体定时器和一个事件,并记录了此时的时间。此时定时器就已经开始运行并定期回调了。
timerCb
函数是windwos多媒体定时器的回调函数,每50毫秒就激活事件并把时间数记录加50。
win_timer_do
函数首先检查是否需要追赶,然后计算当前时间减去定时器开始的时间再减去定时器运行的时长和50毫秒周期比较以检查是否定时器出现异常,如果都没有异常就等待事件被激发。如果定时器运行正常那么在外部不断调用此函数每次会等待到50毫秒的周期后返回。
注意这里使用了GetTickCount
函数获取时间,众所周知GetTickCount
获取的是开机的时长,并且只有4个字节,所以开机49.7天后这个函数获取的值就会清零重新开始,所以这个定时器运行到开机49.7天之后会出问题,如果是长时间不关机的服务器程序可一定得注意这点。
win_timer_close
函数销毁定时器,注意此函数只关闭了windows多媒体定时器,并没有销毁TimeEvent
事件,所以在多次反复的调用win_timer_init
和win_timer_close
创建销毁定时器后会导致句柄泄漏。不过定时器其实并没有销毁的必要,不建议销毁它。
linux平台函数实现
static struct timeval orig,cur;
static uint32_t posix_timer_time=0; /*in milisecond */
void posix_timer_init()
{
posix_timer.state=RTP_TIMER_RUNNING;
ortp_gettimeofday(&orig,NULL);
posix_timer_time=0;
}
void posix_timer_do()
{
int diff,time;
struct timeval tv;
ortp_gettimeofday(&cur,NULL);
time=((cur.tv_usec-orig.tv_usec)/1000 ) + ((cur.tv_sec-orig.tv_sec)*1000 );
if ( (diff=time-posix_timer_time)>50){
ortp_warning("Must catchup %i miliseconds.",diff);
}
while((diff = posix_timer_time-time) > 0)
{
tv.tv_sec = diff/1000;
tv.tv_usec = (diff%1000)*1000;
#if defined(_WIN32) || defined(_WIN32_WCE)
/* this kind of select is not supported on windows */
Sleep(tv.tv_usec/1000 + tv.tv_sec * 1000);
#else
select(0,NULL,NULL,NULL,&tv);
#endif
ortp_gettimeofday(&cur,NULL);
time=((cur.tv_usec-orig.tv_usec)/1000 ) + ((cur.tv_sec-orig.tv_sec)*1000 );
}
posix_timer_time+=POSIXTIMER_INTERVAL/1000;
}
void posix_timer_uninit()
{
posix_timer.state=RTP_TIMER_STOPPED;
}
RtpTimer posix_timer={ 0,
posix_timer_init,
posix_timer_do,
posix_timer_uninit,
{0,POSIXTIMER_INTERVAL}};
可以看到linux平台下的实现更简单,连和操作系统相关的定时器都没有,直接在定时器的执行函数里用select
函数进行了睡眠。
rtp_timer_set_interval
void rtp_timer_set_interval(RtpTimer *timer, struct timeval *interval)
{
if (timer->state==RTP_TIMER_RUNNING){
ortp_warning("Cannot change timer interval while it is running.\n");
return;
}
timer->interval.tv_sec=interval->tv_sec;
timer->interval.tv_usec=interval->tv_usec;
}
此函数是用来设置定时器的周期的,但是根据前文可以知道定时器周期是不能更改的。所以这个函数看起来修改了周期其实并没有任何作用,所以这个函数永远不会被使用。估计是作者挖的坑忘了填。
总结
从上文可以看出,这个ortp的定时器功能非常少,只有一个全局唯一的对象posix_timer
,并且定时器周期默认初始化为50毫秒不能更改,也不能在多个地方共用。但是因为这个定时器只为ortp中的数据收发调度器模块服务,并不在其他地方使用,所以这么简陋也是可以理解的。
不过windows实现的定时器因为使用了GetTickCount
函数不能运行超过开机49.7天这点要注意。