Libevents的基本操作单元是event,每一个event代表了一些条件的集合,这些条件包括:
文件描述符已经准备好读或写
文件描述符正在变为就绪,准备好读或写(仅限于边沿触发)
超时事件
信号发生
用户触发事件
events都有类似的生命周期。一旦调用Libevent函数创建好event,并将其关联到一个event_base之后,他就是“已初始化”状态(initialized)。这种状态下,可以进行add操作,将其状态变为base中的“挂起”状态(pending),处于“挂起”状态的event,如果触发事件的条件发生了(比如,文件描述符的状态发生变化,或者超时了),那么event的状态变为“激活”状态(active),然后它的回调函数(用户提供)开始运行。如果该event配置了“持久”属性(persistent),那么它的状态依然保持为“挂起”,否则,在回调函数运行时,它的状态就不再是“挂起”(“非挂起”状态)。可以通过delete操作,将一个“挂起”状态的event变为“非挂起”状态(non-pending),或者通过add操作,将“非挂起”的event变为“挂起”状态。
一:构建event对象
创建新的event,可以使用event_new接口:
#define EV_TIMEOUT 0x01
#define EV_READ 0x02
#define EV_WRITE 0x04
#define EV_SIGNAL 0x08
#define EV_PERSIST 0x10
#define EV_ET 0x20
typedef void (*event_callback_fn)(evutil_socket_t, short, void*);
struct event * event_new(struct event_base *base, evutil_socket_t fd,
shortwhat, event_callback_fn cb,
void* arg);
void event_free(struct event * event);
event_new函数分配并且创建一个新的event对象,并与base进行关联。what参数是上面列出标志的集合,它们的具体意义见下方。如果fd是非负的整数,则它代表了我们需要观察可读或可写事件的文件。当event变为激活时,Libevent就会调用回调函数cb,将文件描述符参数fd,所有触发事件的标志位域,以及event_new的最后一个参数:arg传递个cb。
如果发生了内部错误,或者参数非法,则event_new返回NULL。
所有新的events都是“已初始化”和“非挂起”状态,可以调用event_add函数将这样的event变为“挂起”状态。
调用event_free可以销毁event。对“挂起”或“激活”状态的event调用event_free也是安全的:在销毁它之前,会将其变为“非挂起”以及“非激活”状态。
#include <event2/event.h>
void cb_func(evutil_socket_t fd, short what, void * arg)
{
const char *data = arg;
printf("Got an event on socket %d:%s%s%s%s [%s]",
(int) fd,
(what&EV_TIMEOUT) ? " timeout" : "",
(what&EV_READ) ? " read" : "",
(what&EV_WRITE) ? " write" : "",
(what&EV_SIGNAL) ? " signal" : "",
data);
}
void main_loop(evutil_socket_t fd1, evutil_socket_t fd2)
{
struct event *ev1, *ev2;
struct timeval five_seconds = {5,0};
struct event_base * base = event_base_new();
/* The caller has already set up fd1,fd2 somehow, and make them
nonblocking. */
ev1 = event_new(base, fd1, EV_TIMEOUT|EV_READ|EV_PERSIST, cb_func,
(char*)"Reading event");
ev2 = event_new(base, fd2, EV_WRITE|EV_PERSIST, cb_func,
(char*)"Writing event");
event_add(ev1, &five_seconds);
event_add(ev2, NULL);
event_base_dispatch(base);
}
上述函数在<event2/event.h>文件中定义。
event标志:
EV_TIMEOUT:
该标志表明,超时时间过后,该event变为“激活”状态。(注意:在构建event时,EV_TIMEOUT标志是被忽略的:当add event时可以设置超时时间,也可以不设置。当超时发生时,回调函数的what参数将会设置该标志。)
EV_READ
该标志表明,当文件描述符准备好读时,event将会变为“激活”
EV_WRITE
该标志表明,当文件描述符准备好写时,event将会变为“激活”
EV_SIGNAL
用来实现信号探测,参见下面的“构造信号事件”
EV_PERSIST:
标志该event具有“持久”属性,参见下面的“事件持久性”
EV_ET:
指明如果event_base的底层方法支持“边沿触发”的话,那么该event应该是边沿触发的。这将会影响到EV_READ和EV_WRITE
自Libevent2.0.1-alpha版本以来,同一时刻,针对同一个文件描述符,可以有任意数量的event在同样的条件上“挂起”。比如,当给定的fd变为可读时,可以使两个events都变为激活状态。但是他们的回调函数的调用顺序是未定义的。
所有这些标志都在<event2/event.h>中定义。
二:事件持久性
默认情况下,当一个“挂起”的event变为“激活”时(要么是因为fd准备好读或写,要么是超时时间到),那么在它的回调函数执行之前,它就会变为“非挂起”状态。因此,如果希望再次使event变为“挂起”状态,可以在回调函数内部再次调用event_add函数。
如果event设置了EV_PERSIST标志,那么event就是“持久”的。这意味着event在回调函数激活的时候,依然保持“挂起”状态。如果希望在回调函数中将event变为“非挂起”状态,则可以调用event_del函数。
当event的回调函数运行时,“持久”event的超时时间就会被重置。因此,如果某个event标志为EV_READ|EV_PERSIST,并且将超时时间设置为5秒,则该event在下面的条件发生时,会变为“激活”:
当该socket准备好读时;
距离上次event变为激活状态后,又过了5秒钟。
三:创建一个可以将自身作为回调函数参数的的event
经常可能会希望创建这样一个event,它本身就是是回调函数的参数之一。不能仅仅传递一个指向event的指针作为event_new的参数,因为彼时它还没有创建。此时,可以通过调用event_self_cbarg函数解决这样的问题。
void*event_self_cbarg();
该函数返回一个“魔术”指针,使得event_new创建一个本身就能作为回调函数参数的event。
#include <event2/event.h>
static int n_calls = 0;
void cb_func(evutil_socket_t fd, short what, void * arg)
{
struct event *me = arg;
printf("cb_func called %d times so far. ", ++n_calls);
if (n_calls > 100)
event_del(me);
}
void run(struct event_base * base)
{
struct timeval one_sec = { 1, 0 };
struct event *ev;
/* We're going to set up a repeating timerto get called 100 times. */
ev = event_new(base, -1, EV_PERSIST, cb_func, event_self_cbarg());
event_add(ev, &one_sec);
event_base_dispatch(base);
}
该函数还可以与函数event_new,evtimer_new, evsignal_new, event_assign, evtimer_assign和evsignal_assign一起使用。然而对于非event来说,他不会作为回调函数的参数。
四:纯超时events
方便起见,Libevent提供了一系列以evtimer_开头的宏,这些宏可以代替event_*函数,来分配和操作纯超时events。使用这些宏仅能提高代码的清晰度而已。
#define evtimer_new(base, callback, arg) event_new((base), -1, 0, (callback), (arg))
#define evtimer_add(ev, tv) event_add((ev),(tv))
#define evtimer_del(ev) event_del(ev)
#define evtimer_pending(ev, tv_out) event_pending((ev), EV_TIMEOUT, (tv_out))
五:构造信号事件
Libevent也可以监控POSIX类的信号。构建一个信号处理函数,可以使用下面的接口:
#define evsignal_new(base, signum, cb, arg)
event_new(base, signum, EV_SIGNAL|EV_PERSIST, cb, arg)
除了提供一个代表信号值的整数,而不是一个文件描述符之外。它的参数与event_new是一样的。
struct event * hup_event;
struct event_base *base = event_base_new();
/*call sighup_function on a HUP signal */
hup_event= evsignal_new(base, SIGHUP, sighup_function, NULL);
注意:信号回调函数是在信号发生之后,在eventloop中调用的。所以,它们可以调用那些,对于普通POSIX信号处理函数来说不是信号安全的函数。
注意:不要在一个信号event上设置超时,不支持这样做。
对于信号event,同样有一些方便的宏可以使用:
#define evsignal_add(ev, tv) event_add((ev), (tv))
#define evsignal_del(ev) event_del(ev)
#define evsignal_pending(ev, what, tv_out) event_pending((ev), (what), (tv_out))
警告:当前版本的Libevent,对于大多数的后端方法来说,同一时间,每个进程仅能有一个event_base可以用来监听信号。如果一次向两个event_base添加event,即使是不同的信号,也仅仅会只有一个event_base可以接收到信号。对于kqueue来说,不存在这样的限制。
六:不在堆中分配event
出于性能或者其他原因的考虑,一些人喜欢将event作为一个大的结构体的一部分进行分配。对于这样的event,它节省了:
内存分配器在堆上分配小对象的开销;
event指针的解引用的时间开销;
如果event没有在缓存中,缓存不命中的时间开销。
这种方法的风险在于,与其他版本的Libevent之间不满足二进制兼容性,他们可能具有不同的event大小。
这些开销都非常小,对于大多数应用来说是无关紧要的。除非确定知道,应用程序因为使用堆分配的event而存在严重的性能损失,否则应该坚持实用event_new。如果后续版本的Libevent使用比当前Libevent更大的event结构,那么使用event_assign有可能会导致难以诊断的错误。
int event_assign(struct event * event, struct event_base * base,
evutil_socket_t fd, short what,
void(*callback)(evutil_socket_t, short, void *), void * arg);
event_assign的参数与event_new相同,除了event参数,该参数指针必须指向一个未初始化的event。该函数成功时返回0,失败时返回-1.
#include <event2/event.h>
/*Watch out! Including event_struct.h means that your code willnot
* be binary-compatible with future versions ofLibevent. */
#include <event2/event_struct.h>
#include <stdlib.h>
struct event_pair {
evutil_socket_t fd;
struct event read_event;
struct event write_event;
};
void readcb(evutil_socket_t, short, void*);
void writecb(evutil_socket_t, short, void*);
struct event_pair * event_pair_new(struct event_base * base, evutil_socket_t fd)
{
struct event_pair *p = malloc(sizeof(struct event_pair));
if (!p) return NULL;
p->fd = fd;
event_assign(&p->read_event, base, fd, EV_READ|EV_PERSIST, readcb, p);
event_assign(&p->write_event, base, fd, EV_WRITE|EV_PERSIST,writecb, p);
return p;
}
同样可以使用event_assign来初始化栈或者静态存储区中的events。
警告:
对于已经在event_base中处于“挂起”状态的event,永远不要调用event_assign。这样做会导致极为难以诊断的错误。如果event已经初始化,并且处于“挂起”状态,那么在调用event_assign之前应该先调用event_del。
对于使用event_assign分配的纯超时event或者信号event,同样有方便的宏可以使用:
#define evtimer_assign(event, base, callback, arg)
event_assign(event, base, -1, 0, callback, arg)
#define evsignal_assign(event, base, signum, callback, arg)
event_assign(event, base, signum, EV_SIGNAL|EV_PERSIST, callback, arg)
如果需要在与未来版本的LIbevent保持二进制兼容性的同时,使用event_assign,可以调用Libevent中的函数,得到运行时的event结构大小:
size_t event_get_struct_event_size(void);
该函数返回需要为event结构预留的字节数。再次提醒,只有在确定堆分配导致很明显的性能问题时,才应该使用该函数,因为它使你的代码难读又难写。
注意,将来版本的event_get_struct_event_size()的返回值可能比sizeof(structevent)小,这意味着event结构的末尾的额外字节仅仅是保留用于未来版本的Libevent的填充字节。
下面是一个使用event_get_struct_size的例子:
#include <event2/event.h>
#include <stdlib.h>
/*When we allocate an event_pair in memory, we'll actually allocate
* more space at the end of the structure. We define some macros
* to make accessing those events lesserror-prone. */
struct event_pair {
evutil_socket_t fd;
};
/*Macro: yield the struct event 'offset' bytes from the start of 'p' */
#define EVENT_AT_OFFSET(p, offset)
((struct event*) ( ((char*)(p)) + (offset) ))
/*Macro: yield the read event of an event_pair */
#define READEV_PTR(pair)
EVENT_AT_OFFSET((pair), sizeof(struct event_pair))
/*Macro: yield the write event of an event_pair */
#define WRITEEV_PTR(pair)
EVENT_AT_OFFSET((pair),
sizeof(struct event_pair)+event_get_struct_event_size())
/*Macro: yield the actual size to allocate for an event_pair */
#defineEVENT_PAIR_SIZE()
(sizeof(struct event_pair)+2*event_get_struct_event_size())
voidreadcb(evutil_socket_t, short, void *);
voidwritecb(evutil_socket_t, short, void *);
struct event_pair *event_pair_new(struct event_base *base, evutil_socket_t fd)
{
struct event_pair *p = malloc(EVENT_PAIR_SIZE());
if (!p) return NULL;
p->fd = fd;
event_assign(READEV_PTR(p), base, fd,EV_READ|EV_PERSIST, readcb, p);
event_assign(WRITEEV_PTR(p), base, fd,EV_WRITE|EV_PERSIST, writecb, p);
return p;
}
event_assign函数定义在文件<event2/event.h>中。event结构体定义在<event2/event_struct.h>文件中。
七:将events置为“挂起”或者“非挂起”
刚创建的一个event,实际上不能做任何事,直到通过调用event_add进行adding操作,将其置为“挂起”状态。
int event_add(struct event *ev, const struct timeval *tv);
在“非挂起”状态的events上执行event_add操作,则会使得该event在配置的event_base上变为“挂起”状态。该函数返回0表示成功,返回-1表示失败。如果tv为NULL,则该event没有超时时间。否则,tv以秒和毫妙表示超时时间。
如果在已经是“挂起”状态的event进行event_add操作,则会保持其“挂起”状态,并且会重置其超时时间。如果event已经是“挂起”状态,而且以NULL为超时时间对其进行re-add操作,则event_add没有任何作用。
注意:不要设置tv为希望超时事件执行的时间,比如如果置tv->tv_sec=time(NULL)+10,并且当前时间为2010/01/01,则超时时间为40年之后,而不是10秒之后。
int event_del(struct event *ev);
在已经初始化状态的event上调用event_del,则会将其状态变为“非挂起”以及“非激活”状态。如果event的当前状态不是“挂起”或“激活”状态,则该函数没有任何作用。该函数返回0表示成功,返回-1表示失败。
注意,如果在event刚变为“激活”状态,但是它的回调函数还没有执行时,调用event_del函数,则该操作使得它的回调函数不会执行。
int event_remove_timer(struct event *ev);
最后,可以在不删除event上的IO事件或信号事件的情况下,删除一个“挂起”状态的event上的超时事件。如果该event没有超时事件,则event_remove_timer没有作用。如果event没有IO事件或信号事件,只有超时事件的话,则event_remove_timer等同于event_del。该函数返回0表示成功,-1表示失败。
这些函数都是在文件<event2/event.h>中定义的。
八:事件的优先级
当多个事件在同一时间触发时,Libevent对于他们回调函数的调用顺序是没有定义的。可以通过优先级,定义某些“更重要”的events。
每一个event_base都有一个或多个优先级的值。在event初始化之后,添加到event_base之前,可以设置该event的优先级。
int event_priority_set(struct event *event, int priority);
event的优先级数必须是位于0到“event_base优先级”-1这个区间内。该函数返回0表示成功,返回-1表示失败。
当具有多种优先级的多个events同时激活的时候,低优先级的events不会运行。Libevent会只运行高优先级的events,然后重新检查events。只有当没有高优先级的events激活时,才会运行低优先级的events。
#include <event2/event.h>
void read_cb(evutil_socket_t, short, void*);
void write_cb(evutil_socket_t, short, void*);
voidmain_loop(evutil_socket_t fd)
{
struct event *important, *unimportant;
struct event_base *base;
base = event_base_new();
event_base_priority_init(base, 2);
/* Now base has priority 0, and priority 1 */
important = event_new(base, fd, EV_WRITE|EV_PERSIST, write_cb, NULL);
unimportant = event_new(base, fd, EV_READ|EV_PERSIST, read_cb, NULL);
event_priority_set(important, 0);
event_priority_set(unimportant, 1);
/*Now, whenever the fd is ready for writing, the write callback will
happen before the read callback. The read callback won't happen at
all until the write callback is no longeractive.*/
}
如果没有设置一个event的优先级,则它的默认优先级是“event_base队列长度”除以2。该函数在文件<event2/event.h>中声明。
九:检查event状态
有时可能希望知道event是否已经添加了(处于“挂起”状态),或者检查他关联到哪个event_base等。
int event_pending(const struct event *ev, short what, struct timeval *tv_out);
#define event_get_signal(ev) /* ... */
evutil_socket_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_assignment(const struct event*event,
struct event_base **base_out,
evutil_socket_t *fd_out,
short *events_out,
event_callback_fn *callback_out,
void **arg_out);
event_pending函数检查给定的event是否处于“挂起”或“激活”状态。如果确实如此,并且在what参数中设置了任何EV_READ, EV_WRITE, EV_SIGNAL或EV_TIMEOUT标志的话,则该函数返回所有该event当前正在“挂起”或“激活”的标志。
如果提供了tv_out参数,且在what参数中设置了EV_TIMEOUT参数,并且当前event确实在超时事件上“挂起”或者“激活”,则tv_out就会设置为event的超时时间。
event_get_fd和event_get_signal函数返回event上配置的文件描述符或者信号值。event_get_base()返回其配置的event_base。event_get_events() 返回event上配置的事件标志(EV_READ,EV_WRITE等)。event_get_callback函数和event_get_callback_arg函数返回event的回调函数和参数指针。event_get_priority函数返回event的当前优先级。
event_get_assignment函数在提供的参数指针中返回event的所有成分,如果参数指针为NULL,则该成分被忽略。
#include <event2/event.h>
#include <stdio.h>
/*Change the callback and callback_arg of 'ev', which must not be pending. */
int replace_callback(struct event *ev, event_callback_fn new_callback,
void* new_callback_arg)
{
struct event_base *base;
evutil_socket_t fd;
short events;
int pending;
pending = event_pending(ev, EV_READ|EV_WRITE|EV_SIGNAL|EV_TIMEOUT, NULL);
if (pending) {
/*We want to catch this here so that we do notre-assign a
* pending event. That would be very very bad.*/
fprintf(stderr, "Error! replace_callbackcalled on a pending event! ");
return -1;
}
event_get_assignment(ev, &base, &fd, &events,
NULL /* ignore oldcallback */ ,
NULL /* ignore oldcallback argument */);
event_assign(ev, base, fd, events,new_callback, new_callback_arg);
return 0;
}
这些函数在文件<event2/event.h>中定义。
十:找到当前正在运行的event
在调试程序时,可以得到当前正在运行的event的指针。
struct event * event_base_get_running_event(struct event_base * base);
注意,只有在base的loop中调用该函数,该函数才有意义。在其他线程调用时不支持的,而且会导致未定义的行为。
该函数在<event2/event.h>中声明。
十一:配置一次性的events
如果不需要对一个event进行多次添加,或者对一个非持久的event,在add之后就会delete,则可以使用event_base_once函数。
int event_base_once(struct event_base *, evutil_socket_t, short,
void (*) (evutil_socket_t, short, void*), void *, const struct timeval *);
该函数的参数与event_new一样,不同的是它不支持EV_SIGNAL或EV_PERSIST标志。得到的内部event会以默认的优先级添加到event_base中并运行。当它的回调函数执行完成之后,Libevent将会释放该内部event。该函数成功时返回0,失败是返回-1.
通过event_base_once插入的event不能被删除或者手动激活。如果希望可以取消一个event,则需要通过常规的event_new或event_assign接口创建event。
注意,直到Libevent2.0之前,如果event一直没有触发,则它的内存永远不会被释放。从Libevent2.1.2-alpha版本开始,当event_base释放时,即使events还没有被激活,它们的内存也会被释放。但是依然要注意:如果它们的回调函数的参数具有关联的内存,那么除非程序中进行释放,否则这些内存永远不会被释放。
十二:手动激活event
某些极少的情况下,你可能希望在条件未被触发的情况下就激活event;
void event_active(struct event *ev, int what, short ncalls);
该接口使得event变为“激活”状态,激活标志在what中传入(EV_READ, EV_WRITE和EV_TIMEOUT的组合)。该event之前的状态不一定非得要是“挂起”状态,而且将其激活不会使其状态变为“挂起”状态。
警告:在同一个event上递归调用event_active可能会导致资源耗尽。下面的例子就是不正确的示范:
structevent *ev;
static void cb(int sock, short which, void *arg) {
/* Whoops: Calling event_active on thesame event unconditionally
from within its callback means that no other eventsmight not get
run!*/
event_active(ev, EV_WRITE, 0);
}
int main(int argc, char**argv) {
struct event_base *base = event_base_new();
ev = event_new(base, -1, EV_PERSIST| EV_READ, cb, NULL);
event_add(ev, NULL);
event_active(ev, EV_WRITE, 0);
event_base_loop(base, 0);
return 0;
}
上面的例子描述了这样的情形:event loop仅被执行一次,而cb会被无限的递归调用中。
Example:Alternative solution to the above problem using timers
struct event *ev;
struct timeval tv;
static void cb(int sock,short which, void *arg) {
if (!evtimer_pending(ev, NULL)) {
event_del(ev);
evtimer_add(ev, &tv);
}
}
int main(int argc, char**argv) {
struct event_base *base = event_base_new();
tv.tv_sec = 0;
tv.tv_usec = 0;
ev = evtimer_new(base, cb, NULL);
evtimer_add(ev, &tv);
event_base_loop(base, 0);
return 0;
}
Example:Alternative solution to the above problem usingevent_config_set_max_dispatch_interval()
structevent *ev;
static void cb(int sock, short which, void*arg) {
event_active(ev, EV_WRITE, 0);
}
intmain(int argc, char **argv) {
struct event_config *cfg = event_config_new();
/* Run at most 16 callbacks beforechecking for other events. */
event_config_set_max_dispatch_interval(cfg, NULL, 16, 0);
struct event_base *base =event_base_new_with_config(cfg);
ev = event_new(base, -1, EV_PERSIST| EV_READ, cb, NULL);
event_add(ev, NULL);
event_active(ev, EV_WRITE, 0);
event_base_loop(base, 0);
return 0;
}
该方法在 <event2/event.h>中定义。
十三:优化一般性超时
当前版本的Libevent使用二叉堆算法来对”挂起”状态的event超时时间值进行跟踪。对于有序的添加和删除event超时时间的操作,二叉堆算法可以提供O(lg n)的性能。这对于添加随机分布的超时时间来说,性能是最优的,但是如果是大量相同时间的events来说就不是了。
比如,假设有一万个事件,每一个event的超时时间都是在他们被添加之后的5秒钟。在这种情况下,使用双向队列实现的话,可以达到O(1)的性能。
正常情况下,一般不希望使用队列管理所有的超时时间值,因为队列仅对于恒定的超时时间来说是快速的。如果一些超时时间或多或少的随机分布的话,那添加这些超时时间到队列将会花费O(n)的时间,这样的性能要比二叉堆差多了。
Libevent解决这种问题的方法是将一些超时时间值放置在队列中,其他的则放入二叉堆中。可以向Libevent请求一个“公用超时时间”的时间值,然后使用该时间值进行事件的添加。如果存在大量的event,它们的超时时间都是这种单一公用超时时间的情况,那么使用这种优化的方法可以明显提高超时事件的性能。
const struct timeval * event_base_init_common_timeout(
struct event_base *base, const struct timeval* duration);
该方法的参数有event_base,以及一个用来初始化的公用超时时间值。该函数返回一个指向特殊timeval结构体的指针,可以使用该指针表明将event添加到O(1)的队列中,而不是O(lg n)的堆中。这个特殊的timeval结构可以在代码中自由的复制和分配。该timeval只能工作在特定的event_base上(参数)。不要依赖于该timeval的实际值:Libevent仅使用它们来指明使用哪个队列。
#include <event2/event.h>
#include <string.h>
/*We're going to create a verylarge number of events on a given base,
* nearly all of which have a ten-secondtimeout. If initialize_timeout
* is called, we'll tell Libevent to add theten-second ones to an O(1)
* queue. */
struct timeval ten_seconds = { 10, 0 };
void initialize_timeout(struct event_base *base)
{
struct timeval tv_in = { 10, 0 };
const struct timeval*tv_out;
tv_out =event_base_init_common_timeout(base, &tv_in);
memcpy(&ten_seconds,tv_out, sizeof(struct timeval));
}
int my_event_add(struct event *ev, const struct timeval*tv)
{
/* Note that ev must have the sameevent_base that we passed to
initialize_timeout */
if (tv && tv->tv_sec == 10 && tv->tv_usec == 0)
return event_add(ev, &ten_seconds);
else
return event_add(ev, tv);
}
类似于其他所有的优化函数,除非确定对你有用,否则应该避免使用这种公用超时时间功能。
十四:从已清除的内存识别事件
Libevent提供了这样的函数,可以从已经清0的内存中(比如以calloc分配,或者通过memset或bzero清除)识别出已初始化的event。
int event_initialized(const struct event*ev);
#define evsignal_initialized(ev) event_initialized(ev)
#define evtimer_initialized(ev) event_initialized(ev)
警告:这些函数不能在一块未初始化的内存中识别出已经初始化了的event。除非你能确定该内存要么被清0,要么被初始化为event,否则不要使用这些函数。
一般情况下,除非你的应用程序有着极为特殊的需求,否则不要轻易使用这些函数。通过event_new返回的events永远已经初始化过的。
#include <event2/event.h>
#include <stdlib.h>
struct reader {
evutil_socket_t fd;
};
#defineREADER_ACTUAL_SIZE() (sizeof(struct reader) + event_get_struct_event_size())
#defineREADER_EVENT_PTR(r) ((struct event *) (((char*)(r))+sizeof(struct reader)))
struct reader * allocate_reader(evutil_socket_t fd)
{
struct reader *r = calloc(1, READER_ACTUAL_SIZE());
if (r)
r->fd = fd;
return r;
}
void readcb(evutil_socket_t, short, void*);
int add_reader(struct reader *r, struct event_base *b)
{
struct event *ev= READER_EVENT_PTR(r);
if (!event_initialized(ev))
event_assign(ev, b, r->fd, EV_READ, readcb, r);
return event_add(ev, NULL);
}
十五:过时的event处理函数
在Libevent2.0之前的版本中,没有event_assign或者event_new函数,而只有event_set函数,该函数返回的event与“当前”base相关联。如果有多个event_base,则还需要调用event_base_set函数指明event与哪个base相关联。
void event_set(struct event *event, evutil_socket_t fd, short what,
void(*callback)(evutil_socket_t, short, void *), void *arg);
int event_base_set(struct event_base * base, struct event *event);
event_set函数类似于event_assign,除了它使用“当前”base的概念。event_base_set函数改变event所关联的base。
如果是处理超时或者信号events,event_set也有一些便于使用的变种:evtimer_set类似于evtimer_assign,而evsignal_set类似于evsignal_assign。
Libevent2.0之前的版本中,使用以signal_为前缀的函数作为处理基于信号的event_set的变种。而不是evsignal_(也就是说是:signal_set, signal_add, signal_del, signal_pending和signal_intialized)。Libevent古老版本(0.6之前),使用timeout_,而不是evtimer_前缀。因此,如果你需要处理很老的代码的话,可能会看见timeout_add(), timeout_del(), timeout_initialized(), timeout_set(), timeout_pending()等。
较老版本的Libevent(2.0之前)使用两个宏,完成event_get_fd和event_get_signal的工作:EVENT_FD和EVENT_SIGNAL。这些宏直接监测event结构的内部,因此在各种版本之间不具有二进制兼容性。在2.0以及之后的版本中,这些宏就是event_get_fd和event_get_signal函数的别名。
在Libevent2.0之前的版本中不支持锁操作,因此,在运行base的线程之外的线程中,调用任何改变event状态的函数,都是不安全的。这些函数包括:event_add, event_del, event_active, and event_base_once。
event_base_once的古老版本是event_once,它使用“当前”base的概念。
在Libevent2.0之前,超时事件设置EV_PERSISIT是不明智的。并非在event激活时重置超时时间,EV_PERSISIT标志对于超时而言无任何作用。
Libevent2.0之前的版本不支持多个events,在同一时间添加同样的fd以及相同的READ/WRITE事件。换句话说,对于每一个fd,一次只能有一个event在其上等待读或写。
http://www.wangafu.net/~nickm/libevent-book/Ref4_event.html