libevent源码学习3—事件event
libevent 的基本操作单元是事件。每个事件代表一组条件的集合, 这些条件包括:
- 文件描述符已经就绪, 可以读取或者写入
- 文件描述符变为就绪状态,可以读取或者写入(仅对于边沿触发 IO)
- 超时事件
- 发生某信号
- 用户触发事件
所有事件具有相似的生命周期。调用libevent
函数设置事件并且关联到event_base
之后, 事件进入“已初始化(initialized)”状态。此时可以将事件添加到event_base
中,这使之进入“未决(pending)” 状态。在未决状态下, 如果触发事件的条件发生(比如说,文件描述符的状态改变, 或者超时时间到达 ),则事件进入“激活(active)”状态,(用户提供的)事件回调函数将被执行。如果配置为“持久的(persistent)”, 事件将保持为未决状态。否则, 执行完回调后, 事件不再是未决的。删除操作可以让未决事件成为非未决(已初始化)的 ; 添加操作可以让非未决事件再次成为未决的。
ps:未决: 简单来说就是一个已经产生的信号,但是还没有传递给任何进程,此时该信号的状态就称为未决状态。
1.创建事件
1.1 生成新事件
使用 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,short events, event_callback_fn cb, void *arg);
void event_free(struct event *event);
event_new()
试图分配和构造一个用于 base
的新的事件。events
参数是上述标志的集合。如果 fd
非负, 则它是将被观察其读写事件的文件。事件被激活时, libevent
将调用 cb 函数, 传递这些参数: 文件描述符 fd, 表示所有被触发事件的位字段 , 以及构造事件时的 arg 参数。发生内部错误, 或者传入无效参数时, event_new()
将返回 NULL。
所有新创建的事件都处于已初始化和非未决状态 ,调用 event_add()可以使其成为未决的。
要释放事件, 调用 event_free()
。对未决或者激活状态的事件调用 event_free()
是安全的: 在释放事件之前, 函数将会使事件成为非激活和非未决的。
event_new()实现:
struct event *event_new(struct event_base *base, evutil_socket_t fd, short events, void (*cb)(evutil_socket_t, short, void *), void *arg)
{struct event *ev;ev = mm_malloc(sizeof(struct event));if (ev == NULL)return (NULL);if (event_assign(ev, base, fd, events, cb, arg) < 0){mm_free(ev);return (NULL);}return (ev);
}
event_add()实现:
int event_add(struct event *ev, const struct timeval *tv)
{int res;if (EVUTIL_FAILURE_CHECK(!ev->ev_base)){event_warnx("%s: event has no event_base set.", __func__);return -1;}EVBASE_ACQUIRE_LOCK(ev->ev_base, th_base_lock);res = event_add_nolock_(ev, tv, 0);EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);return (res);
}
event结构体定义:
struct event {struct event_callback ev_evcallback;/* for managing timeouts */union {TAILQ_ENTRY(event) ev_next_with_common_timeout;int min_heap_idx;} ev_timeout_pos;evutil_socket_t ev_fd;struct event_base *ev_base;union {/* used for io events */struct {LIST_ENTRY (event) ev_io_next;struct timeval ev_timeout;} ev_io;/* used by signal events */struct {LIST_ENTRY (event) ev_signal_next;short ev_ncalls;/* Allows deletes in callback */short *ev_pncalls;} ev_signal;} ev_;short ev_events;short ev_res; /* result passed to event callback */struct timeval ev_timeout;
};
1.2 事件标志
- EV_TIMEOUT:这个标志表示某超时时间流逝后事件成为激活的。构造事件的时候, EV_TIMEOUT 标志是被忽略的: 可以在添加事件的时候设置超时, 也可以不设置。超时发生时,回调函数的 what 参数将带有这个标志。
- EV_READ:表示指定的文件描述符已经就绪, 可以读取的时候, 事件将成为激活的。
- EV_WRITE:表示指定的文件描述符已经就绪, 可以写入的时候, 事件将成为激活的。
- EV_SIGNAL:用于实现信号检测
- EV_PERSIST:表示事件是“持久的”
- EV_ET:表示如果底层的 event_base 后端支持边沿触发事件(默认水平),则事件应该是边沿触发的。这个标志影响 EV_READ 和 EV_WRITE 的语义。
1.3 关于事件持久性
默认情况下,每当未决事件成为激活的(fd 已经准备好读取或者写入,或者因为超时),事件将在其回调被执行前成为非未决的。如果想让事件再次成为未决的,可以在回调函数中再次对其调用 event_add()。
然而,如果设置了 EV_PERSIST 标志,事件就是持久的。这意味着即使其回调被激活,事件还是会保持为未决状态。 如果想在回调中让事件成为非未决的,可以对其调用 event_del()。
每次执行事件回调的时候,持久事件的超时值会被复位。因此,如果具有 EV_READ|EV_PERSIST 标志,以及5秒(你设置的值)的超时值,则事件将在以下情况下成为激活的:
- 套接字已经准备好被读取的时候
- 从最后一次成为激活的开始,已经过去5秒
1.4 信号事件
libevent 也可以监测 POSIX 风格的信号。要构造信号处理器
#define evsignal_new(b, x, cb, arg) \event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))
libevent 也提供了一组方便使用的宏用于处理信号事件:
#define evsignal_add(ev, tv) event_add((ev), (tv))
#define evsignal_assign(ev, b, x, cb, arg) \event_assign((ev), (b), (x), EV_SIGNAL|EV_PERSIST, cb, (arg))
#define evsignal_del(ev) event_del(ev)
#define evsignal_pending(ev, tv) event_pending((ev), EV_SIGNAL, (tv))
#define evsignal_initialized(ev) event_initialized(ev)
2.事件的未决和非未决
构造事件之后,在将其添加到 event_base
之前实际上是不能对其做任何操作的。使用event_add()
将事件添加到 event_base
。
2.1 设置未决事件
int event_add(struct event *ev, const struct timeval *tv);
在非未决的事件上调用 event_add()将使其在配置的 event_base 中成为未决的。成功时函数返回0, 失败时返回-1。
如果 tv 为 NULL, 添加的事件不会超时。否则, tv 以秒和微秒指定超时值。
如果对已经未决的事件调用 event_add()
, 事件将保持未决状态, 并在指定的超时时间被重新调度。
2.2 设置非未决事件
int event_del(struct event *ev);
对已经初始化的事件调用 event_del()
将使其成为非未决和非激活的。如果事件不是未决的或者激活的, 调用将没有效果。成功时函数返回 0, 失败时返回-1。
注意: 如果在事件激活后, 其回调被执行前删除事件, 回调将不会执行。
3.事件的优先级
int event_priority_set(struct event *ev, int pri)
{event_debug_assert_is_setup_(ev);if (ev->ev_flags & EVLIST_ACTIVE)return (-1);if (pri < 0 || pri >= ev->ev_base->nactivequeues)return (-1);ev->ev_pri = pri;return (0);
}
注意优先级的值的范围在第7行源码。相关数据结构释义:
/* Active event management. */
/** An array of nactivequeues queues for active event_callbacks (ones that have triggered, and whose callbacks need to be called). Low priority numbers are more important, and stall higher ones.
*/
struct evcallback_list *activequeues;
/** The length of the activequeues array */
int nactivequeues;
4.检查事件状态
有时候需要了解事件是否已经添加,检查事件代表什么。libevent中有很多函数来获取不同的信息。
/**Checks if a specific event is pending or scheduled.@param ev an event struct previously passed to event_add()@param events the requested event type; any of EV_TIMEOUT|EV_READ|EV_WRITE|EV_SIGNAL@param tv if this field is not NULL, and the event has a timeout, this field is set to hold the time at which the timeout will expire.@return true if the event is pending on any of the events in 'what', (that is to say, it has been added), or 0 if the event is not added.*/
int event_pending(const struct event *ev, short events, struct timeval *tv);
event_pending()
函数确定给定的事件是否是未决的或者激活的。如果是,·而且 events 参数设置了 EV_READ、EV_WRITE、EV_SIGNAL 或者 EV_TIMEOUT 等标志,则函数会返回事件当前为之未决或者激活的所有标志 。
/**Extract _all_ of arguments given to construct a given event. The event_base is copied into *base_out, the fd is copied into *fd_out, and so on.If any of the "_out" arguments is NULL, it will be ignored.*/
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_get_assignment()
复制所有为事件分配的字段到提供的指针中。任何为 NULL 的参数会被忽略。
当然还有很多获取状态的函数,详情去源码event.c
中查看。
5.一次触发事件
如果不需要多次添加一个事件,或者要在添加后立即删除事件,而事件又不需要是持久的 , 则可以使用 event_base_once()
。
int event_base_once(struct event_base *, evutil_socket_t, short, event_callback_fn, void *, const struct timeval *);
除了不支持 EV_SIGNAL 或者 EV_PERSIST 之外,这个函数的接口与 event_new()
相同。
6.手动激活事件
极少数情况下,需要在事件的条件没有触发的时候让事件成为激活的。
/* You can use this function on a pending or a non-pending event to make it active, so that its callback will be run by event_base_dispatch() or event_base_loop().One common use in multithreaded programs is to wake the thread running event_base_loop() from another thread.@param ev an event to make active.@param res a set of flags to pass to the event's callback.@param ncalls an obsolete argument: this is ignored.
*/
void event_active(struct event *ev, int res, short ncalls);
7.事件状态之间的转换
参考《libevent深入浅出》、libevent官方文档。