event loop
事件循环
一旦你向event_base注册了一些event,那你接下来会希望Libevent等待事件的发生并且通知你。
接口
#define EVLOOP_ONCE 0x01 #define EVLOOP_NONBLOCK 0x02 #define EVLOOP_NO_EXIT_ON_EMPTY 0x04 int event_base_loop(struct event_base *base, int flags);
默认情况下,event_base_loop()会一直运行event_base,直到把里面的event全部运行结束。运行循环时,它会重复的检查是否有已注册的event被触发(例:如果注册了读的文件描述符准备好读,或如果注册了超时的事件已经过期)。一旦有event被触发,它就会把它们标记为"活跃的",并且开始去运行他们。
你可以通过设置flags参数去改变event_base_loop()的行为。如果EVLOOP_ONCE被设置,那循环会发生阻塞,去等一些event变成“活跃的”,然后遍历运行这些活跃的event。如果EVLOOP_NONBLOCK被设置,循环不会阻塞去等待event被触发:它只会检查是否有event准备好去触发,如果有直接调用它们的回调。
通常,如果已经当前没有活跃或还未处理完的event,那么循环就会退出。你可以通过设置EVLOOP_NO_EXIT_ON_EMPTY去覆盖默认的退出。例如,如果你想稍后从其他的线程增加event。如果设置了EVLOOP_NO_EXIT_ON_EMPTY,循环会持续运行直到人为的调用了event_base_loopbreak(),或event_base_loopexit(),或发生错误。
当它结束时,正常退出event_base_loop()返回0, 如果发生错误返回-1,如果是由于没有活跃的event而退出,则返回1。
为了增加理解,这里有个对于event_base_loop的总结:
伪代码
while (any events are registered with the loop, or EVLOOP_NO_EXIT_ON_EMPTY was set) { if (EVLOOP_NONBLOCK was set, or any events are already active) If any registered events have triggered, mark them active. else Wait until at least one event has triggered, and mark it active. for (p = 0; p < n_priorities; ++p) { if (any event with priority of p is active) { Run all active events with priority of p. break; /* Do not run any events of a less important priority */ } } if (EVLOOP_ONCE was set or EVLOOP_NONBLOCK was set) break; }
为了方便,你也可以调用:
接口
int event_base_dispatch(struct event_base *base);
event_base_dispatch()相当于无flags的event_base_loop()。因此,它会持续运行,直至没再有被注册的event或有人调用event_base_loopbreak()或event_base_loopexit()。
循环终止
如果你想提前终止一个event循环。你可以有两个选择,这两个方法稍有不同。
接口
int event_base_loopexit(struct event_base *base, const struct timeval *tv); int event_base_loopbreak(struct event_base *base);
event_base_loopexit()可以延迟一段时间再停止。如果tv参数为空,event_base马上停止循环,不会延迟。如果当前有激活的event,它将会持续运行,直至这些被激活的event都运行结束。
event_base_loopbreak()可以直接退出循环。不同于event_base_loopexit(base, NULL),它会在结束当前event后直接退出。
注意当没有event循环在运行时,调用event_base_loopexit(base, NULL)和event_base_loopbreak(base)产生的处理是不同的:如果下一个循环(使用了EVLOOP_ONCE)被调用loopexit会使下一个启动的event 循环停止。而loopbreak只对当前正在运行的event循环有影响,如果当前没有循环在运行,则不会有任何作用。
这两个方法均成功返回0,失败返回-1。
例子:直接结束
#include <event2/event.h> /* Here's a callback function that calls loopbreak */ void cb(int sock, short what, void *arg) { struct event_base *base = arg; event_base_loopbreak(base); } void main_loop(struct event_base *base, evutil_socket_t watchdog_fd) { struct event *watchdog_event; /* Construct a new event to trigger whenever there are any bytes to read from a watchdog socket. When that happens, we'll call the cb function, which will make the loop exit immediately without running any other active events at all. */ watchdog_event = event_new(base, watchdog_fd, EV_READ, cb, base); event_add(watchdog_event, NULL); event_base_dispatch(base); }
例子:运行一个event循环10秒,然后终止
#include <event2/event.h> void run_base_with_ticks(struct event_base *base) { struct timeval ten_sec; ten_sec.tv_sec = 10; ten_sec.tv_usec = 0; /* Now we run the event_base for a series of 10-second intervals, printing "Tick" after each. For a much better way to implement a 10-second timer, see the section below about persistent timer events. */ while (1) { /* This schedules an exit ten seconds from now. */ event_base_loopexit(base, &ten_sec); event_base_dispatch(base); puts("Tick"); } }
有时你可能需要知道event_base_dispatch()和event_base_loop()是否正常退出。下面的方法可以判断是loopexit或break被调用:
接口
int event_base_got_exit(struct event_base *base); int event_base_got_break(struct event_base *base);
如果调用了它们,这两个方法会返回true,否则返回false。它们的值会在下次启动event循环时被重置。
重新检测event
通常,Libevent会检测event,然后运行高优先级的被激活的event,然后再重新检测,这样循环。但有时你可能想终止Libevent后,再继续运行它。通过类比event_base_loopbreak(),你可以通过下面这个方法:
接口
int event_base_loopcontinue(struct event_base *);
如果当前没有运行在event回调,event_base_loopcontinue()将不会产生作用。
获取内部缓存时间
有时你想在event回调里面获得一个粗略时间,你不需要调用gettimeofday()(因为gettimeofday()是系统调用,未避免内核和用户的上下文切换,你应该尽量避免系统调用)。
在一个回调内部,你可以请求Libevent提供它开始运行这轮回调时的时间:
接口
int event_base_gettimeofday_cached(struct event_base *base, struct timeval *tv_out);
如果event_base正在执行回调,event_base_gettimeofday_cached()会设置缓存时间到tv_out参数上。否则它会调用evutil_gettimeofday()去提供准确的当前时间。成功返回0,失败返回负数。
注意当Libevent开始运行回调时,时间就会被缓存,它多多少少都会有些不准确。如果你的回调花了很长时间,会导致时间更加不准确。你可以通过以下方法去强制更新缓存时间:
接口
int event_base_update_cache_time(struct event_base *base);
成功返回0,失败返回-1。如果event_base没有运行在循环里,不会产生作用。
输出event_base状态
接口
void event_base_dump_events(struct event_base *base, FILE *f);
为了调试程序(或调试Libevent!)你有时可能想知道一份添加到event_base中的所有事件完整的列表。调用event_base_dump_events()会把这些信息写到f中。
为每个event都运行一个方法
接口
typedef int (*event_base_foreach_event_cb)(const struct event_base *, const struct event *, void *); int event_base_foreach_event(struct event_base *base, event_base_foreach_event_cb fn, void *arg);
你可以通过event_base_foreach_event()去迭代event_base()中的每个活跃的event。所提供的回调将会被每个event都调用一次。event_base_foreach_event()的第三个参数会作为回调的第三个参数。
如果想继续运行,回调方法必须返回0,返回其他值会终止迭代。无论回调方法最终返回什么值,都会被event_base_foreach_function()方法返回。
你提供的的回调方法一定不要去修改任何它接收的event,也不要从event_base中添加或删除任何event,也不要修改其他任何在event_base中的event。否则未定义的情况可能会发生,包括宕机等。
在调用event_base_foreach_event()期间,会持有event_base的锁:这会阻止其他线程去使用event_base,所以确保你的回调不会运行很长时间。