原文链接:

ev_timer

ev_timer就是定时器,如果你有如下需求,你就可能需要用到它。

  • n秒之后需要调用某个函数(如:tcp连接超时断开连接)
  • 每隔y秒就调用某个函数(如:定时清理资源)


可使用的函数如下:

ev_timer_init (timer, callback, 60., 0.);
ev_timer_start (loop, timer);
ev_timer_stop (loop, timer);
ev_timer_set (timer, 60., 0.);
ev_timer_start (loop, timer);
ev_timer_remaining (loop, ev_timer *)

符号定义:

typedef double ev_tstamp;

#if EV_MINPRI == EV_MAXPRI
# define EV_DECL_PRIORITY
#elif !defined (EV_DECL_PRIORITY)
# define EV_DECL_PRIORITY int priority;
#endif

#ifndef EV_COMMON
# define EV_COMMON void *data;
#endif

#ifndef EV_CB_DECLARE
# define EV_CB_DECLARE(type) void (*cb)(EV_P_ struct type *w, int revents);
#endif

#define EV_WATCHER(type)			    \
    int active;     /* private */		\
    int pending;    /* private */		\
    EV_DECL_PRIORITY /* private */	    \
    EV_COMMON       /* rw */			\
    EV_CB_DECLARE (type) /* private */

#define EV_WATCHER_TIME(type)			\
    EV_WATCHER (type)				    \
    ev_tstamp at;     /* private */

typedef struct ev_timer
{
    EV_WATCHER_TIME (ev_timer)
    ev_tstamp repeat; /* rw */
} ev_timer;

sample1

这个例子有一个循环loop,等待了2.5秒之后调用timeout_cb,执行完这个函数就退出程序了。


#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <sys/unistd.h>
#include "ev.h"
 
void timer_action(struct ev_loop *main_loop,ev_timer *time_w,int e)
{
    puts("In Time action");
    ev_timer_stop(main_loop,time_w);
}
 
int main(int argc,char **argv)
{
    ev_timer timer_w;   
    struct ev_loop *main_loop = ev_default_loop(0);
 
    ev_init(&timer_w,timer_action);
    ev_timer_set(&timer_w,5,5);
 
    ev_timer_start(main_loop,&timer_w);
 
    ev_run(main_loop,0);
    return 0;
}
实现功能:每5s触发一次超时,执行回调函数。


sample2

这个例子有一个循环loop,等待了2.5秒之后调用timeout_cb,之后每隔5秒就执行一次这个函数。这个程序将无限循环,不会退出。可以猜出来ev_timer_init的最后一个参数为0时表示不重复回调,大于0为间隔回调时间,是个浮点数。

#include <ev.h>
#include <stdio.h>

ev_timer timeout_watcher;

void timeout_cb (struct ev_loop *loop, ev_timer *w, int revents)
{
    puts("timeout_cb has been called");   
}

int main()
{
    struct ev_loop *loop = EV_DEFAULT;

    // 改一下最后一个参数试试
    ev_timer_init (&timeout_watcher, timeout_cb, 2.5, 5.0);
    ev_timer_start (loop, &timeout_watcher);
    
    ev_run (loop, 0);
    return 0;
}

sample3

sample2不会退出,以下sample会来让它退出。以下程序其实和sample1一样,调用了一次timeout_cb后就退出了。
哦,libev有个宏EV_A_,它其实是loop,,用起来不太好看,不过方便一点。

#include <ev.h>
#include <stdio.h>

ev_timer timeout_watcher;

void timeout_cb (struct ev_loop *loop, ev_timer *w, int revents)
{
    puts("timeout_cb has been called");   

    // 这里能让循环终止
    ev_break (loop, EVBREAK_ONE);
    // ev_break (EV_A_ EVBREAK_ONE); 效果同上
}

int main()
{
    struct ev_loop *loop = EV_DEFAULT;

    ev_timer_init (&timeout_watcher, timeout_cb, 2.5, 5.0);
    ev_timer_start (loop, &timeout_watcher);
    
    ev_run (loop, 0);
    return 0;
}

sample4

有时候并不是固定的时间间隔呀,动态地改变超时行不行?可以的,在回调里边再设一次timeout呗。
其实ev_timer_init调用的就是ev_timer_set,它们两的最后一个参数意义一样。

#include <ev.h>
#include <stdio.h>

ev_timer timeout_watcher;

void timeout_cb (struct ev_loop *loop, ev_timer *w, int revents)
{
    puts("timeout_cb has been called");   

    ev_timer_stop (loop, w);
    ev_timer_set (w, 1., 0.);   // 第2个参数为下次timeout,可以是变化的
    ev_timer_start (loop, w);
    ev_run (loop, 0);           // 别忘了再run起来
}

int main()
{
    struct ev_loop *loop = EV_DEFAULT;

    ev_timer_init (&timeout_watcher, timeout_cb, 2.5, 0);
    ev_timer_start (loop, &timeout_watcher);
    
    ev_run (loop, 0);
    return 0;
}

sample5

sample4看起来有点麻烦,而且还耗时,每次都要将结构体移除再插入,不妨用以下的方式代替。

#include <ev.h>
#include <stdio.h>

ev_timer timeout_watcher;

void timeout_cb (struct ev_loop *loop, ev_timer *w, int revents)
{
    puts("timeout_cb has been called");   

    w->repeat = 7.;     // 这里可以改变下次回调超时
    ev_timer_again(loop, w);
}

int main()
{
    struct ev_loop *loop = EV_DEFAULT;

    ev_init(&timeout_watcher, timeout_cb);
    timeout_watcher.repeat = 2.;
    ev_timer_again(loop, &timeout_watcher);

    ev_run (loop, 0);
    return 0;
}

sample6

其实sample5所用的ev_timer_again是每次都会回调的,比如下面这样写就是每间隔2秒就回调一次。

#include <ev.h>
#include <stdio.h>

ev_timer timeout_watcher;

void timeout_cb (struct ev_loop *loop, ev_timer *w, int revents)
{
    puts("timeout_cb has been called");   
}

int main()
{
    struct ev_loop *loop = EV_DEFAULT;

    ev_init(&timeout_watcher, timeout_cb);
    timeout_watcher.repeat = 2.;
    ev_timer_again(loop, &timeout_watcher);

    ev_run (loop, 0);
    return 0;
}

时间过早的问题

官方文档提到了一个例子,比如一个OS是以秒计时的,即对外提供的最小单位就是秒,然后在第500.9秒的时候我们设置了一个1秒的timeout,那么应该在什么时候触发这个回调比较好呢?

如果在第501秒触发,那么实际上只过去了0.1秒而已,如果在502秒触发,那么实际上是过去了1.1秒。libev就是后者这样的,它只能保证至少已经过去了所设的timeout秒,但是不能保证刚好就是timeout秒。比如进程接收到STOP信号,那么无法避免,它只能在恢复运行的时候触发回调,可能已经晚了很多,但这是没法保证的。

关于时间更新

获取系统时间是个耗时的系统调用,libev是自己维护了个时间,只有在ev_run收集到新事件的前后才会更新,这样会导致的问题就是时间偏差会越来越大。ev_now()用于获得libev的当前时间,ev_time()用于获取系统的当前时间。

如果想矫正libev的当前时间,可以通过ev_timer_set (&timer, after + (ev_time () - ev_now ()), 0.);这样来矫正它。也可以通过ev_now_update ()来强迫矫正ev_now

`