一旦有了一个已经注册了某些事件的event_base,就需要让libevent等待事件并且通知事件的发生。
int event_base_loop(struct event_base *, int);
相关宏定义见: Macro definition 相关宏函数见: Macro function EVBASE_ACQUIRE_LOCK N_ACTIVE_CALLBACKS
int
event_base_loop(struct event_base *base, int flags)
{
const struct eventop *evsel = base->evsel; // 获取事件操作接口
struct timeval tv; // 用于存储超时时间
struct timeval *tv_p; // 超时时间指针
int res, done, retval = 0; // 返回值、循环控制标志和结果状态
/* 获取锁,以确保线程安全。将在 evsel.dispatch 内部释放锁 */
EVBASE_ACQUIRE_LOCK(base, th_base_lock);
if (base->running_loop) { // 检查是否已有循环在运行
event_warnx("%s: reentrant invocation. Only one event_base_loop"
" can run on each event_base at once.", __func__);
EVBASE_RELEASE_LOCK(base, th_base_lock); // 释放锁
return -1; // 返回错误
}
base->running_loop = 1; // 标记循环正在运行
clear_time_cache(base); // 清理时间缓存
if (base->sig.ev_signal_added && base->sig.ev_n_signals_added) // 如果有信号事件,设置信号基础
evsig_set_base_(base);
done = 0; // 初始化循环终止标志
#ifndef EVENT__DISABLE_THREAD_SUPPORT
base->th_owner_id = EVTHREAD_GET_ID(); // 设置线程所有者ID
#endif
base->event_gotterm = base->event_break = 0; // 初始化事件终止和中断标志
while (!done) { // 循环直到 done 为真
base->event_continue = 0; // 初始化继续标志
base->n_deferreds_queued = 0; // 初始化延迟队列计数
/* 检查是否需要终止循环 */
if (base->event_gotterm) {
break;
}
if (base->event_break) {
break;
}
tv_p = &tv; // 设置超时指针
if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) { // 如果没有活动回调且非非阻塞标志
timeout_next(base, &tv_p); // 获取下一个超时时间
} else {
/* 如果有活动事件,直接轮询而不等待 */
evutil_timerclear(&tv); // 清除时间
}
/* 如果没有事件,并且不允许在空状态下退出,则退出 */
if (0==(flags&EVLOOP_NO_EXIT_ON_EMPTY) &&
!event_haveevents(base) && !N_ACTIVE_CALLBACKS(base)) {
event_debug(("%s: no events registered.", __func__));
retval = 1; // 设置结果状态为1
goto done; // 跳转到清理部分
}
event_queue_make_later_events_active(base); // 激活稍后的事件
clear_time_cache(base); // 再次清理时间缓存
res = evsel->dispatch(base, tv_p); // 调用事件调度函数
if (res == -1) { // 检查调度函数是否成功
event_debug(("%s: dispatch returned unsuccessfully.",
__func__));
retval = -1; // 设置结果状态为-1
goto done; // 跳转到清理部分
}
update_time_cache(base); // 更新时间缓存
timeout_process(base); // 处理超时事件
if (N_ACTIVE_CALLBACKS(base)) { // 如果有活动回调
int n = event_process_active(base); // 处理活动回调
if ((flags & EVLOOP_ONCE) // 如果是一次性循环
&& N_ACTIVE_CALLBACKS(base) == 0
&& n != 0)
done = 1; // 设置循环结束标志
} else if (flags & EVLOOP_NONBLOCK)
done = 1; // 如果是非阻塞标志,结束循环
}
event_debug(("%s: asked to terminate loop.", __func__));
done:
clear_time_cache(base); // 清理时间缓存
base->running_loop = 0; // 标记循环已结束
EVBASE_RELEASE_LOCK(base, th_base_lock); // 释放锁
return (retval); // 返回最终结果
}
/** @name Loop flags
These flags control the behavior of event_base_loop().
*/
/**@{*/
/** Block until we have an active event, then exit once all active events
* have had their callbacks run. */
#define EVLOOP_ONCE 0x01
/** Do not block: see which events are ready now, run the callbacks
* of the highest-priority ones, then exit. */
#define EVLOOP_NONBLOCK 0x02
/** Do not exit the loop because we have no pending events. Instead, keep
* running until event_base_loopexit() or event_base_loopbreak() makes us
* stop.
*/
#define EVLOOP_NO_EXIT_ON_EMPTY 0x04
/**@}*/
默认情况下,event_base_loop()函数运行event_base直到其中没有已经注册的事件为止。执行循环的时候,函数重复地检查是否有任何已经注册的事件被触发(比如说,读事件的文件描述符已经就绪,可以读取了;或者超时事件的超时时间即将到达)。如果有事件被触发,函数标记被触发的事件为“激活的”,并且执行这些事件。
在flags参数中设置一个或者多个标志就可以改变event_base_loop()的行为。如果设置了EVLOOP_ONCE,循环将等待某些事件成为激活的,执行激活的事件直到没有更多的事件可以执行,然会返回。如果设置了EVLOOP_NONBLOCK,循环不会等待事件被触发:循环将仅仅检测是否有事件已经就绪,可以立即触发,如果有,则执行事件的回调。
完成工作后,如果正常退出,event_base_loop()返回0;如果因为后端中的某些未处理错误而退出,则返回-1。
int
event_base_dispatch(struct event_base *event_base)
{
return (event_base_loop(event_base, 0));
}
event_base_dispatch()等同于没有设置标志的event_base_loop()。所以,event_base_dispatch()将一直运行,直到没有已经注册的事件了,或者调用了event_base_loopbreak()或者event_base_loopexit()为止。
如果想在移除所有已注册的事件之前停止活动的事件循环,可以调用两个稍有不同的函数。
注意event_base_loopexit(base,NULL)和event_base_loopbreak(base)在事件循环没有运行时的行为不同:前者安排下一次事件循环在下一轮回调完成后立即停止(就好像带EVLOOP_ONCE标志调用一样);后者却仅仅停止当前正在运行的循环,如果事件循环没有运行,则没有任何效果。
这两个函数都在成功时返回0,失败时返回-1。
event_base_loopbreak()让event_base立即退出循环。它与event_base_loopexit(base,NULL)的不同在于,如果event_base当前正在执行激活事件的回调,它将在执行完当前正在处理的事件后立即退出。
int event_base_loopbreak(struct event_base *event_base)
int event_base_loopbreak(struct event_base *event_base)
{
int r = 0;
if (event_base == NULL)
return (-1);
//获取锁
EVBASE_ACQUIRE_LOCK(event_base, th_base_lock);
event_base->event_break = 1;
if (EVBASE_NEED_NOTIFY(event_base)) {
r = evthread_notify_base(event_base);
} else {
r = (0);
}
EVBASE_RELEASE_LOCK(event_base, th_base_lock);
return r;
}
注意event_base_loopexit(base,NULL)和event_base_loopbreak(base)在事件循环没有运行时的行为不同:前者安排下一次事件循环在下一轮回调完成后立即停止(就好像带EVLOOP_ONCE标志调用一样);后者却仅仅停止当前正在运行的循环,如果事件循环没有运行,则没有任何效果。 event_base_loopexit()让event_base在给定时间之后停止循环。如果tv参数为NULL,event_base会立即停止循环,没有延时。如果event_base当前正在执行任何激活事件的回调,则回调会继续运行,直到运行完所有激活事件的回调之才退出。
int event_base_loopexit(struct event_base *event_base, const struct timeval *tv)
static void event_loopexit_cb(evutil_socket_t fd, short what, void *arg)
{
struct event_base *base = arg;
base->event_gotterm = 1;
}
int event_base_loopexit(struct event_base *event_base, const struct timeval *tv)
{
return (event_base_once(event_base, -1, EV_TIMEOUT, event_loopexit_cb,
event_base, tv));
}
event_base
: 事件基础。-1
: 事件标识符,-1
表示这是一个一次性事件,没有持久化标识。EV_TIMEOUT
: 事件类型,这里是超时事件。表示这个事件是基于超时的。event_loopexit_cb
: 事件回调函数,当事件触发时会调用这个函数。event_base
: 作为回调函数的用户数据传递,这里传递了event_base
结构的指针。tv
: 超时时间。如果为NULL
,超时时间可能会被视为立即触发
event_base_loopexit()让event_base在给定时间之后停止循环。如果tv参数为NULL,event_base会立即停止循环,没有延时。如果event_base当前正在执行任何激活事件的回调,则回调会继续运行,直到运行完所有激活事件的回调之才退出。
event_base_loopbreak()让event_base立即退出循环。它与event_base_loopexit(base,NULL)的不同在于,如果event_base当前正在执行激活事件的回调,它将在执行完当前正在处理的事件后立即退出。
/* Schedules an event once */
int
event_base_once(struct event_base *base, evutil_socket_t fd, short events,
void (*callback)(evutil_socket_t, short, void *),
void *arg, const struct timeval *tv)
{
struct event_once *eonce;
int res = 0;
int activate = 0;
if (!base)
return (-1);
/* We cannot support signals that just fire once, or persistent
* events. */
if (events & (EV_SIGNAL|EV_PERSIST))
return (-1);
if ((eonce = mm_calloc(1, sizeof(struct event_once))) == NULL)
return (-1);
eonce->cb = callback;
eonce->arg = arg;
if ((events & (EV_TIMEOUT|EV_SIGNAL|EV_READ|EV_WRITE|EV_CLOSED)) == EV_TIMEOUT) {
evtimer_assign(&eonce->ev, base, event_once_cb, eonce);
if (tv == NULL || ! evutil_timerisset(tv)) {
/* If the event is going to become active immediately,
* don't put it on the timeout queue. This is one
* idiom for scheduling a callback, so let's make
* it fast (and order-preserving). */
activate = 1;
}
} else if (events & (EV_READ|EV_WRITE|EV_CLOSED)) {
events &= EV_READ|EV_WRITE|EV_CLOSED;
event_assign(&eonce->ev, base, fd, events, event_once_cb, eonce);
} else {
/* Bad event combination */
mm_free(eonce);
return (-1);
}
if (res == 0) {
EVBASE_ACQUIRE_LOCK(base, th_base_lock);
if (activate)
event_active_nolock_(&eonce->ev, EV_TIMEOUT, 1);
else
res = event_add_nolock_(&eonce->ev, tv, 0);
if (res != 0) {
mm_free(eonce);
return (res);
} else {
LIST_INSERT_HEAD(&base->once_events, eonce, next_once);
}
EVBASE_RELEASE_LOCK(base, th_base_lock);
}
return (0);
}
-
参数说明:
base
: 指向event_base
结构的指针,用于指定事件基础。fd
: 文件描述符。用于 I/O 事件时指定哪个文件描述符。events
: 事件类型标志,包括超时 (EV_TIMEOUT
)、读 (EV_READ
)、写 (EV_WRITE
) 和关闭 (EV_CLOSED
)。callback
: 回调函数指针,在事件发生时调用。arg
: 传递给回调函数的用户数据。tv
: 超时时间的指针。用于设置超时事件的超时时间。
-
主要逻辑:
- 基础检查: 检查
base
是否为NULL
,如果是,则返回-1
。 - 事件类型检查: 只允许
EV_TIMEOUT
、EV_READ
、EV_WRITE
和EV_CLOSED
事件类型。不能处理EV_SIGNAL
和EV_PERSIST
类型。 - 内存分配: 分配
struct event_once
结构的内存,并检查分配是否成功。 - 事件类型处理:
- 超时事件 (
EV_TIMEOUT
): 如果tv
为NULL
或无效,标记事件为立即激活。 - I/O 事件 (
EV_READ
、EV_WRITE
、EV_CLOSED
): 处理与文件描述符相关的事件。
- 超时事件 (
- 锁操作:
- 获取
base
的锁。 - 如果事件需要立即激活,调用
event_active_nolock_
使事件立即激活。 - 否则,将事件添加到事件队列中,调用
event_add_nolock_
。 - 在锁内将事件插入到一次性事件列表
once_events
中。 - 释放锁。
- 获取
- 返回值: 成功时返回
0
,否则返回错误代码。
- 基础检查: 检查
有时候需要在事件回调中获取当前时间的近似视图,但不想调用gettimeofday()(可能是因为OS将gettimeofday()作为系统调用实现,而你试图避免系统调用的开销)。
在回调中,可以请求libevent开始本轮回调时的当前时间视图。
int event_base_gettimeofday_cached(struct event_base *base, struct timeval *tv)
int event_base_gettimeofday_cached(struct event_base *base, struct timeval *tv)
{
int r;
if (!base) {
base = current_base;
if (!current_base)
return evutil_gettimeofday(tv, NULL);
}
EVBASE_ACQUIRE_LOCK(base, th_base_lock);
if (base->tv_cache.tv_sec == 0) {
r = evutil_gettimeofday(tv, NULL);
} else {
evutil_timeradd(&base->tv_cache, &base->tv_clock_diff, tv);
r = 0;
}
EVBASE_RELEASE_LOCK(base, th_base_lock);
return r;
}
如果当前正在执行回调,event_base_gettimeofday_cached()函数设置tv_out参数的值为缓存的时间。否则,函数调用evutil_gettimeofday()获取真正的当前时间。成功时函数返回0,失败时返回负数。
注意,因为libevent在开始执行回调的时候缓存时间值,所以这个值至少是有一点不精确的。如果回调执行很长时间,这个值将非常不精确。
这个函数是libevent 2.0.4-alpha新引入的
void event_base_dump_events(struct event_base *base, FILE *output)
void event_base_dump_events(struct event_base *base, FILE *output)
{
EVBASE_ACQUIRE_LOCK(base, th_base_lock);
fprintf(output, "Inserted events:\n");
event_base_foreach_event_nolock_(base, dump_inserted_event_fn, output);
fprintf(output, "Active events:\n");
event_base_foreach_event_nolock_(base, dump_active_event_fn, output);
EVBASE_RELEASE_LOCK(base, th_base_lock);
}
为帮助调试程序(或者调试libevent),有时候可能需要加入到event_base的事件及其状态的完整列表。调用event_base_dump_events()可以将这个列表输出到指定的文件中。 这个函数在libevent 2.0.1-alpha版本中引入。
前面已经讨论过,老版本的libevent 具有“当前”event_base的概念。
本文讨论的某些事件循环函数具有操作当前event_base的变体。除了没有base参数外,这些函数跟当前新版本函数的行为相同。
Current function | Obsolete current-base version |
---|---|
event_base_dispatch() | event_dispatch() |
event_base_loop() | event_loop() |
event_base_loopexit() | event_loopexit() |
event_base_loopbreak() | event_loopbreak() |
2.0版本之前的event_base是不支持锁的,所以这些函数并不是完全线程安全的:不允许在执行事件循环的线程之外的其他线程中调用_loopbreak()或者_loopexit()函数。