初入libevent的人,很可能是第一次接触异步编程。Libevent的编程思想,建议还是多看前人的程序,或者是看libevent本身的文档学习。或者是介绍另外一个库,那就是libuv,它是libev某种意义上的的替代品(而libev又可以算是libevent的某种替代品笑)。libuv的文档我记得也有对异步编程的介绍。好了,这不是本文的内容。
本文地址:https://segmentfault.com/a/1190000005359466
Reference
Programming with Libevent
翻译:Libevent参考手册第一章:设置libevent
序章和知识准备
在文档序章中举了一个例子,介绍了异步编程。这里面列举了几个典型的函数,如下:
头文件
#include <event2/event.h>
结构体
struct event
函数
event_new(), event_free(), event_base_new(), event_add(), event_base_dispatch()
Libevent的基本思路是使用select()
。但是使用select很多的话,往往系统开销很大,所以各个UNIX和类UNIX系统是使用了select的替代。但是不同的系统使用的替代各不相同,这导致程序移植起来很麻烦。
Linux:epoll()
BSD:kqueue()
Solaris:/dev/poll
, evports
Libevent使用BSD License而不是GPL,这使得它可以很方便地用在一个闭源的项目里面
Libevent的特性
-
evutil
:不同平台的抽象层,主要是网络接口部分 -
event
,event_base
:libevent的核心API -
bufferevent
:缓存化的read/write接口,并且可以与SSL互相封装。
另外还有几个我就不列出了,个人很少用
Libevent的库
-
libevent_core
:Libevent的核心API和功能,包含了上述主要模块 -
libevent_extra
:Libevent提供的额外功能包,这些目前还用不上 -
libevent
:包含上述两个,但是建议以后不要使用
之所以还有event2
文件夹,是因为这个文件夹下是新的(也就是目前使用的)libevent库
创建一个event_base
每一个event_base
持有和管理多个event
,并且判断哪些event
是被激活了的。但是这发生在一个单一的线程内。如果你想哟啊在多个线程内并行处理event,则需要在每个线程中各创建一个event_base。
获得一个默认的event_base
struct event_base *event_base_new(void);
这个函数alloc并返回一个带默认配置的event base。
获得一个复杂的event_base
struct event_config *event_config_new(void);
struct event_base *event_base_new_with_config(const struct event_config *cfg);
void event_config_free(struct event_config *cfg);
这几个函数的基本功能就是:获得一个config,配置好后作为event_base创建的参数。用完后记得将config给free掉。
而配置config则需要用到要以下两个函数:
int event_config_require_features(struct event_config *cfg, enum event_method_feature feature);
这里的枚举值有以下几个:
EV_FEATURE_ET
:需要边沿触发的I/O
EV_FEATURE_OI
:在add和delete event的时候,需要后端(backend)方法
EV_FEATURE_FDS
:需要能够支持任意文件描述符的后端方法
上述的值都是mask,可以位与(|)。
int event_config_set_flag(struct event_config *cfg, enum event_base_config_flag flag);
这里的枚举值有以下几个:
EVENT_BASE_FLAG_NOLOCK
:event_base不使用lock初始化
EVENT_BASE_FLAG_IGNORE_ENV
:挑选backend方法时,不检查EVENT_xxx标志。这个功能慎用
EVENT_BASE_FLAG_STARTUP_IOCP
:仅用于Windows。起码我不关心
EVENT_BASE_FLAG_NO_CACHE_TIME
:不检查timeout,代之以为每个event loop都准备调用timeout方法。这会导致CPU使用率偏高
EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST
:告诉libevent如果使用epoll的话,可以使用基于“changlist”的backend。
EVENT_BASE_FLAG_PRECISE_TIMER
:使用更加精确的定时机制
以上两个函数成功时返回0,失败时返回-1;
设置event_base的分离时间
int event_config_set_max_dispatch_interval (
struct event_config *cfg,
const struct timerval *mac_interval,
int max_callbacks,
int min_priority);
在检查高优先级的event之前,通过限制低优先级调用来防止优先级反转(priority inversion)。而另一个限制条件是max_interval
参数,这是表示高优先级事件被调用的最大延时。
成功时返回0,失败时返回-1。默认情况下,`event_base`被设置成默认优先级
检查event_base的backend方法
const char **event_get_supported_methods(void);
返回一个char*数组,说明了libevent支持的所有backend方法。数组以NULL
结尾。
读取指定event_base的配置
const char *event_base_get_method (const struct event_base *base);
enum event_method_feature event_base_get_features (const struct event_base *base);
释放一个event_base资源
int event_base_priority_init (structr event_base *base, int n_priorities);
第二个参数表示可支持的优先级,至少为1。0是最优先。这样,event_base中的优先级即为0 ~ nPriorities-1
。
最大支持的优先级数为EVENT_MAX_PRIORITIES
。
int event_base_get_npriorities(struct event_base *base);
上一个函数的读版本
使用事件循环event loop
当event_base和其中的event都配置好了之后,就可以启用libevent了。Event的配置在后文说,这里先讲event loop的运行。
运行loop
int event_base_loop (struct event_base *base, int flags);
int event_base_dispatch (struct event_base *base);
函数的行为是:运行event_base,直到没有event被注册在event_base中位置。这是默认的行为,也就是说,当没有pending
或active
事件时,函数返回。
参数flags
可以用来修改默认行为,有以下掩码值:
-
EVLOOP_ONCE
:等待并执行active事件,循环执行到没有事件可执行 -
EVLOOP_NONBLOCK
:循环不等待事件触发,而是即检查即回调 -
EVLOOP_NO_EXIT
:没有事件仍不退出,而是由其他函数触发退出
第二个函数的作用是相同的,等效于使用默认的flags。
停止loop
int event_base_loopexit (struct event_base *base, const struct timeval *tv);
int event_base_loopbreak (struct event_base *base);
第一个函数要求event_base在指定时间后立即停止,如果tv为NULL,则立即停止。但这个函数实际上会使得event_base在执行完全部的callback之后才返回。 第二个函数的不同之处是使event_base在执行完当前的callback之后,无视其他active事件而立即停止。但需要注意的是,如果当前没有callback,这会导致event_base等到执行完下一个callback之后才退出。
int event_base_got_exit (struct event_base *base);
int event_base_got_break (struct event_base *base);
用来判断是否获得了exit或者是break请求。注意返回值实际上应该是BOOL而不是int
检查event_loop内部的时间
int event_base_gettimeofday_cached (struct event_base *base, struct timeval *tv);
int event_base_update_cache_time (struct event_base *base);
获得event_loop时间。第一个函数获得event_loop最近的时间,比较快,但是不精确。第二个函数可以强制event_loop立即同步更新时间。
将event_base的状态存入文件
void event_base_dump_events (struct event_base *base, FILE *f);
实际上我觉得这个函数能够保存的信息还是不够多。另外,你可以将/dev/stdout
句柄打开之后传入也是可以的
在event_base中运行每一个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中所有的active或pending的Event,然后调用传入的一个回调,逐个调用。
注意这里的callback只允许用来检查event的状态,不允许修改。此外,callback的执行时间不应太长,因为会有锁保护。
为什么说这个函数谜呢?因为我实际上在libevent的头文件中找-不-到这个函数啊!资料里面是有的,但是.h文件中不存在,强行调用,也是找不到函数。从描述来看这个函数其实很好用,也非常适合初学者们跟踪学习event_base及其event的状态。但是……用不了。
使用事件“event”
Libevent的基本处理单元是event,每个event代表一组条件:
-
做好read或write准备的文件描述符
-
正在准备好read或write准备的文件描述符(边沿触发I/O,没用过,不懂)
-
超时
-
信号被触发
-
用户触发的事件
当一个event被设置好,并且关联到一个event_base里面时,它被称为“initialized”。此时你可以执行add
,这使得它进入pending
状态。当event被触发或超时时,它的状态称为active
,这个情况下对应的callback会被调用。如果event被配置为persist
,那么它在callback执行前后都会保持pending的状态。可以通过delete
来使得一个event从pending状态重新变成nonpending
。
创建一个event对象
typedef void (*event_callback_fn) (evutil_socket_t, short, void *);
struct event *event_new (struct event_base *base,
evutil_socket_t fd,
short what,
event_callback_fn cb,
void *arg);
void event_free (struct event *event);
创建一个新的event。其中fd是文件描述符,需要自行初始化之后再作为参数传入。event_free()
释放event的资源。如果event是active或者是pending状态,则函数会将event先变成非active且非pending的状态,然后再释放它。
参数what
表示这个event的需要关注绑定在该fd上的哪些事件。有以下几个掩码值:
-
EV_TIMEOUT:超时
-
EV_READ:有数据可读
-
EV_WRITE:数据可写
-
EV_SIGNAL:系统发出的信号(
signal
) -
EV_PERSIST:持续事件
-
EV_ET:边沿触发
关于EV_PERSIST
int event_del (struct event *event);
前文所说的add
动作的反动作。EV_PERSIST使得一个event不会自动被event_base给delete掉,除非显式地调用这个函数。
使用event自己的ID作为回调参数
回调的时候会传入自定义的arg。但是如果想要让回调能够接受自己的struct event指针作为参数,那么应该使用以下函数:
void *event_self_cbarg();
比如:
struct event *aEvent = event_new (pBase, aFd,
EV_READ | EV_TIMEOUT,
aCallback,
event_self_cbarg());
Note on 2016-11-21: 在最新版本的 libevent 中,这个函数已经被停用了。并且目前并没有这个函数的合适替代。
超时事件
纯超时事件不需要fd(传入-1即可)。Libevent定义了一些方便的用于创建超时事件的宏:
evtimer_new (base, callback, arg)
evtimer_add (ev, tv)
evtimer_del (ev)
evtimer_pending (ev, tv_out)
从测试结果来看,这里的timer event都是一次性执行的,所以请自行在callback中重新add
信号事件
信号时间也不需要fd,但不是传入-1,而是对应的signum
。
evsignal_new (base, signum, callback, arg) // 自带EV_PERSIST
evsignal_add (ev, tv)
evsignal_del (ev)
evsignal_pending (ev, what, tv_out)
signal事件的callback是异步的,所以很安全,不像signal()
中的回调那样有诸多限制。
注意:不要给signal事件设置timeout
使事件进入pending和non-pending
int event_add (struct event *ev, const struct timeval *tv);
int event_del (struct event *ev);
int event_remove_timer (struct event *ev);
使用优先级的事件
int event_priority_set (struct event *event, int priority);
将事件优先级设置在event base的优先级和0之间。
观察event状态
int event_pending (const struct event *ev, short what, struct timeval *tv_out);
注意这个函数的返回值实际上是BOOL。函数判断event是否为给定的flag(what
)而被pending或active。如果tv_out
非空,并且EV_TIMEOUT
被设置了,那么判断完状态后,event得timeout也会被通过这个参数返回回来。
以下是其他一目了然的get函数:
evutil_short_t event_get_fd (const struct event *ev);
struct event_base *event_get_base (const struct event *ev); // 注意这个很常用
short event_get_events (const struct event *ev);
event_callback_fn event_get_callback (const struct event *ev);
void *event_get_callback_arg (const struct event *ev);
int event_get_priority (const struct event *ev);
void event_get_assigement (const struct event *event,
struct event_base **base_out,
evutil_short_t *fd_out,
short *events_out,
event_callback_fn *callback_out,
void *arg_out);
寻找当前运行中event
struct event *event_base_get_running_event (struct event_base *base);
注意这个函数只支持在event的loop中调用,在其它线程调用的话是不支持的
快速配置一个一次性的事件
int event_base_once (struct event_base *base,
evutil_socket_t fd,
short what,
event_callback_fn cb,
void *arg,
const struct timeval *tv);
这个函数完成前面的event_new
和event_add
动作,并且不返回event对象。调用这个函数创建的对象直接加入到event_base中,并且执行了一次callback之后,自动delete
和free
掉。
Event的优先级默认,what不支持EV_SIGNAL和EV_PERSIST。
从libevent 2.1.2之后,即便一个event还没有被activate过,在event_base被释放时,也会一并被释放掉。在这之前这算是一个bug。不过也请注意其他所有不由libevent管理的资源(比如arg),要防止内存泄漏。
手动激活一个event
void event_active (struct event *ev, int what, short ncalls);
用指定的标志来激活一个event,慎用该功能,特别是不要再callback中使用。但这是另一个比较谜的函数,因为我在我自己设计的字符设备驱动中使用这个函数,但是实际上我监听着这个设备的event却根本没有被激活。我的调用方式应该是没问题的,难道是还需要内核驱动做什么支持吗?
(2016-7-5 笔记:这里说的可能只是不能在目标event的callback中使用,但是可以在其它无关事件的callback中调用。还没有确认,但是猜测的可能性是:这个函数只有在libevent的loop中调用才能生效,不能异步地在loop外部activate。已经确认了在loop内部是生效的,至于在外部无法生效的原因,是不是我所猜测的那样,目前我还找不到确认的答案)
优化了的公共超时事件
Libevent对add/del超时事件具有 O(logN)的性能,使用与多个不同超时时间的队列。但是如果有多个使用同一个超时值的事件,这样做就显得很低效了。此时libevent使用二进制堆算法(binary heep algorrithm)来完成这种任务。
此时libevent要涉及一个“common timeout
”了:
const struct timeval *event_base_init_common_timeout (
struct event_base *base,
const struct timeval *duration);
这个函数返回一个持续的timeval指针,指向common timeout。使用这个timeout的事件会被加入到binary heep中,并且只有你指定的特定event_base中有效。返回的timeval指针,可以把timeval值复制出来使用。