初入libevent的人,极可能是第一次接触异步编程。Libevent的编程思想,建议仍是多看前人的程序,或者是看libevent自己的文档学习。
或者是介绍另一个库,那就是libuv,它是libev某种意义上的的替代品(而libev又能够算是libevent的某种替代品笑)。libuv的文档我记得也有对异步编程的介绍。好了,这不是本文的内容。html
本文地址:https://segmentfault.com/a/1190000005359466算法
Programming with Libevent
翻译:Libevent参考手册第一章:设置libevent编程
在文档序章中举了一个例子,介绍了异步编程。这里面列举了几个典型的函数,以下:segmentfault
头文件#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,这使得它能够很方便地用在一个闭源的项目里面网络
evutil
:不一样平台的抽象层,主要是网络接口部分event
, event_base
:libevent的核心APIbufferevent
:缓存化的read/write接口,而且能够与SSL互相封装。另外还有几个我就不列出了,我的不多用异步
libevent_core
:Libevent的核心API和功能,包含了上述主要模块libevent_extra
:Libevent提供的额外功能包,这些目前还用不上libevent
:包含上述两个,可是建议之后不要使用之因此还有event2
文件夹,是由于这个文件夹下是新的(也就是目前使用的)libevent库
每个event_base
持有和管理多个event
,而且判断哪些event
是被激活了的。可是这发生在一个单一的线程内。若是你想哟啊在多个线程内并行处理event,则须要在每一个线程中各建立一个event_base。
struct event_base *event_base_new(void);
这个函数alloc并返回一个带默认配置的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/OEV_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;
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`被设置成默认优先级
const char **event_get_supported_methods(void);
返回一个char*数组,说明了libevent支持的全部backend方法。数组以NULL
结尾。
const char *event_base_get_method (const struct event_base *base); enum event_method_feature event_base_get_features (const struct event_base *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_base和其中的event都配置好了以后,就能够启用libevent了。Event的配置在后文说,这里先讲event 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。
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
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当即同步更新时间。
void event_base_dump_events (struct event_base *base, FILE *f);
实际上我以为这个函数可以保存的信息仍是不够多。另外,你能够将/dev/stdout
句柄打开以后传入也是能够的
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的状态。可是……用不了。
Libevent的基本处理单元是event,每一个event表明一组条件:
当一个event被设置好,而且关联到一个event_base里面时,它被称为“initialized”。此时你能够执行add
,这使得它进入pending
状态。当event被触发或超时时,它的状态称为active
,这个状况下对应的callback会被调用。若是event被配置为persist
,那么它在callback执行先后都会保持pending的状态。能够经过delete
来使得一个event从pending状态从新变成nonpending
。
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上的哪些事件。有如下几个掩码值:
signal
)int event_del (struct event *event);
前文所说的add
动做的反动做。EV_PERSIST使得一个event不会自动被event_base给delete掉,除非显式地调用这个函数。
回调的时候会传入自定义的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
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之间。
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);
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),要防止内存泄漏。
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值复制出来使用。
Libevent官方文档学习笔记(1. libevent_core部分)(本文)
Libevent官方文档学习笔记(2. bufferevent部分)
Libevent官方文档学习笔记(3. evbuffer部分)