Libevent中的event结构体对应于Reactor模式中的事件处理器,其中提供了函数接口,供Reactor管理器在事件发生时调用。
1.event结构体
- 事件的类型:主要分为IO读写、信号、超时事件
#define EV_TIMEOUT 0x01 // 超时
#define EV_READ 0x02 // 可读
#define EV_WRITE 0x04 // 可写
#define EV_SIGNAL 0x08 // 产生信号
#define EV_PERSIST 0x10 // 标识事件不会退出事件循环(Abort loop)
- 表示事件的结构体event如下所示,从该结构体可知,对于定时事件使用小根堆管理,信号事件和IO读写事件使用双向链表进行管理。Libevent通过event结构体对三种事件进行统一管理。
// 标志位:用于标识事件的状态
#define EVLIST_TIMEOUT 0x01 // 事件在小根堆中,这是超时事件
#define EVLIST_INSERTED 0x02 // 事件在已注册事件队列
#define EVLIST_SIGNAL 0x04 // 未使用
#define EVLIST_ACTIVE 0x08 // 事件在已激活事件队列
#define EVLIST_INTERNAL 0x10 // 内部事件(信号事件)
#define EVLIST_INIT 0x80 // 事件已被初始化
/* EVLIST_X_ Private space: 0x1000-0xf000 */
#define EVLIST_ALL (0xf000 | 0x9f)
struct event {
// 以下队列都是由双向链表实现
// 已注册事件队列
TAILQ_ENTRY (event) ev_next;
// 已激活事件队列
TAILQ_ENTRY (event) ev_active_next; //active list
// 信号队列
TAILQ_ENTRY (event) ev_signal_next; //singnal list
// 小根堆的下标
unsigned int min_heap_idx; /* for managing timeouts*/
//指向所属的事件循环event_base
struct event_base* ev_base;
//关联的文件描述符或者信号;如果是超时事件则为-1,信号事件则为信号值
int ev_fd;
//监听的事件类型
short ev_events;
//加入active队列之后要被调用的次数
short ev_ncalls;
// 指向ev_ncalls变量的指针
short* ev_pncalls; /* Allows deletes in callback */
//超时的时间,这个字段与min_heap_idx配合使用
struct timeval ev_timeout;
/* 优先级,事件触发后根据优先级放入不同active队列event_base.activequeues[ev_pri]中,ev_pri越小优先级越高*/
int ev_pri;
//指定事件发生的回调函数(事件处理程序)
void (*ev_callback)(int, short, void* arg);
// 回调函数参数
void* ev_arg;
// 发生的事件类型,存储到这个变量
int ev_res; /* result passed to event callback */
//标志位,标志该event的状态,为EVLIST_*的多种组合
int ev_flags;
};
2.信号事件
- Libevent中对信号事件的管理通过evsignal_info结构体完成,其定义如下所示:
struct evsignal_info {
/* Event watching ev_signal_pair[1] */
// 监听ev_signal_pair[1]套接字的可读、可持续事件
struct event ev_signal;
/* Socketpair used to send notifications from the signal handler */
// 与唤醒(通知)机制有关,后文介绍
int ev_signal_pair[2];
/* True iff we've added the ev_signal event yet. */
// 是否已经注册监听ev_signal_pair[1]套接字的可读事件ev_signal
int ev_signal_added;
// 表示是否捕捉到信号
volatile sig_atomic_t evsignal_caught;
// 监听不同信号的event,每种信号一个链表,多个event可以监听同一个信号
struct event_list evsigevents[NSIG];
// 不同信号捕捉到的次数,NSIG值为65
sig_atomic_t evsigcaught[NSIG];
/* Array of previous signal handler objects before Libevent started
* messing with them. Used to restore old signal handlers. */
#ifdef HAVE_SIGACTION
struct sigaction** sh_old;
#else
ev_sighandler_t** sh_old;
#endif
/* Size of sh_old. */
int sh_old_max;
};
- evsignal_info结构体的初始化:通过evsignal_init函数进行初始化,这个函数内部主要创建一对套接字,将套接字设置FD_CLOSEONEXEC和SOCK_NONBLOCK标志、初始化ev_signal事件以及一些成员。注意:此时并底层没有调用
epoll_ctl
将ev_signal事件注册,只是初始化事件处理程序,即当ev_signal_pair[1]上有可读事件时执行evsignal_cb这个回调
int
evsignal_init(struct event_base* base)
{
int i;
/*
* Our signal handler is going to write to one end of the socket
* pair to wake up our event loop. The event loop then scans for
* signals that got delivered.
*/
// 底层使用socketpair函数创建两个套接字
if (evutil_socketpair(
AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair) == -1) {
#ifdef WIN32
/* Make this nonfatal on win32, where sometimes people
have localhost firewalled. */
event_warn("%s: socketpair", __func__);
#else
event_err(1, "%s: socketpair", __func__);
#endif
return -1;
}
// 将两个套接字设置FD_CLOSEONEXEC标志
FD_CLOSEONEXEC(base->sig.ev_signal_pair[0]);
FD_CLOSEONEXEC(base->sig.ev_signal_pair[1]);
base->sig.sh_old = NULL;
base->sig.sh_old_max = 0;
base->sig.evsignal_caught = 0;
memset(&base->sig.evsigcaught, 0, sizeof(sig_atomic_t)*NSIG);
/* initialize the queues for all events */
for (i = 0; i < NSIG; ++i)
TAILQ_INIT(&base->sig.evsigevents[i]);
// 将两个套接字设置为非阻塞
evutil_make_socket_nonblocking(base->sig.ev_signal_pair[0]); // write端
evutil_make_socket_nonblocking(base->sig.ev_signal_pair[1]); // read端
// 初始化ev_signal事件,当ev_signal_pair[1]上有可读事件时执行evsignal_cb
event_set(&base->sig.ev_signal, base->sig.ev_signal_pair[1],
EV_READ | EV_PERSIST, evsignal_cb, &base->sig.ev_signal);
base->sig.ev_signal.ev_base = base;
base->sig.ev_signal.ev_flags |= EVLIST_INTERNAL;
return 0;
}
static void
evsignal_cb(int fd, short what, void* arg)
{
static char signals[1];
#ifdef WIN32
SSIZE_T n;
#else
ssize_t n;
#endif
// 将ev_signal_pair[1]套接字读缓冲区的1字节数据读走
n = recv(fd, signals, sizeof(signals), 0);
if (n == -1) {
int err = EVUTIL_SOCKET_ERROR();
// recv函数在非阻塞下读,返回-1且错误码不是EAGAIN,则表示出错
if (! error_is_eagain(err))
event_err(1, "%s: read", __func__);
}
}
- 注册信号事件:通过evsignal_add函数完成,核心操作是将ev_signal_pair[1]套接字挂载到epoll实例上,监听ev_signal_pair[1]上的可读事件。Libevent中对所有的信号都注册同一个处理函数evsignal_handler。捕捉到信号后,信号处理函数执行。在信号处理函数中,通过向ev_signal_pair[0]套接字写入一个字节的数据,ev_signal_pair[1]套接字就会触发可读事件,从而epoll_wait返回后能够对信号事件做进一步的处理
static void
evsignal_handler(int sig)
{
int save_errno = errno;
if (evsignal_base == NULL) {
event_warn(
"%s: received signal %d, but have no base configured",
__func__, sig);
return;
}
// 记录捕捉到的信号信息
evsignal_base->sig.evsigcaught[sig]++;
// 标识已捕捉到信号
evsignal_base->sig.evsignal_caught = 1;
#ifndef HAVE_SIGACTION
// 重新注册信号
signal(sig, evsignal_handler);
#endif
/* Wake up our notification mechanism */
// 向ev_signal_pair[0]套接字写入1个字节的数据,触发ev_signal_pair[1]的读事件,通知唤醒event_base有信号触发,需要处理
send(evsignal_base->sig.ev_signal_pair[0], "a", 1, 0);
errno = save_errno;
}
int
evsignal_add(struct event* ev)
{
int evsignal;
struct event_base* base = ev->ev_base;
struct evsignal_info* sig = &ev->ev_base->sig;
if (ev->ev_events & (EV_READ | EV_WRITE))
event_errx(1, "%s: EV_SIGNAL incompatible use", __func__);
// 获取信号值
evsignal = EVENT_SIGNAL(ev);
assert(evsignal >= 0 && evsignal < NSIG);
// 未注册信号事件
if (TAILQ_EMPTY(&sig->evsigevents[evsignal])) {
event_debug(("%s: %p: changing signal handler", __func__, ev));
// 设置信号处理器
if (_evsignal_set_handler(
base, evsignal, evsignal_handler) == -1)
return (-1);
/* catch signals if they happen quickly */
evsignal_base = base;
// 添加到事件循环
if (!sig->ev_signal_added) {
if (event_add(&sig->ev_signal, NULL))
return (-1);
sig->ev_signal_added = 1;
}
}
/* multiple events may listen to the same signal */
TAILQ_INSERT_TAIL(&sig->evsigevents[evsignal], ev, ev_signal_next);
return (0);
}
- 注销已注册的信号事件:使用evsignal_del函数
综上:由于信号事件和IO读写事件不同,不能直接让epoll模型检测就绪事件的产生。那么当有信号事件发生,如何通知事件处理框架event_base进行处理呢?
- 在初始化event_base_new时,使用socketpair函数创建一对套接字,记为ev_signal_pair[0]、ev_signal_pair[1]。
- 在注册非超时事件时,将ev_signal_pair[1]挂载到epoll实例上,由epoll模型监听该套接字的可读事件
- 当有信号送达,就会被Libevent捕捉到,接着信号处理器(信号的回调函数)会执行。其中向ev_signal_pair[0]套接字写入1个字节的数据,触发ev_signal_pair[1]的读事件就绪。然后epoll_wait就会返回,将捕捉到信号对应的信号事件放入激活事件队列中
- 在事件循环event_base_loop中执行ev_signal_pair[1]上有可读事件时设置的回调evsignal_cb
3.定时器事件
- 在事件主循环event_base_loop函数中,根据所有定时器事件的最小超时时间来设置IO复用的超时时间。当IO复用返回时,将已经触发的计时器事件从小根堆删除,放入到激活事件队列中。如下所示:
// 校正系统时间
timeout_correct(base, &tv);
// epoll_wait的阻塞时间
tv_p = &tv;
// 无激活事件并且未设置EVLOOP_NONBLOCK标志
if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) {
//获取到base->timeheap最小堆中最先超时的计时器,用于计算epoll_wait等待事件就绪的时长
timeout_next(base, &tv_p);
} else {
/*
* if we have active events, we just poll new events
* without waiting.
*/
evutil_timerclear(&tv); // 将阻塞超时时间清零
}
/* If we have no events, we just exit */
//事件循环中没有要监听的事件退出
if (!event_haveevents(base)) {
event_debug(("%s: no events registered.", __func__));
return (1);
}
/* update last old time */
gettime(base, &base->event_tv);
/* clear time cache */
base->tv_cache.tv_sec = 0;
// 调用epoll_wait等待事件就绪,
res = evsel->dispatch(base, evbase, tv_p);
- 定时器事件的超时时间如何设定的?在调用event_add函数注册定时器事件时设定
// 得到定时器触发时间点
gettime(base, &now);
// tv + &now
evutil_timeradd(&now, tv, &ev->ev_timeout);
- 如何判断定时事件已经触发呢?如果小根堆堆顶时间(注册定时事件的时间)小于当前时间则说明定时事件已经触发
void
timeout_process(struct event_base* base)
{
struct timeval now;
struct event* ev;
// 不存在注册事件
if (min_heap_empty(&base->timeheap))
return;
gettime(base, &now);
// 检查堆顶元素是否超时
while ((ev = min_heap_top(&base->timeheap))) {
//注册定时事件时的时间与当前时间比较,如果大于当前时间
//则说明没有超时
if (evutil_timercmp(&ev->ev_timeout, &now, > ))
break;
/* delete this event from the I/O queues */
event_del(ev);
event_debug(("timeout_process: call %p",
ev->ev_callback));
// 放入到active队列中等待回调
event_active(ev, EV_TIMEOUT, 1);
}
}
4.激活事件的统一处理
- 已激活事件存储在优先级队列中,由事件处理框架根据事件的优先级在事件循环中调用event结构体中指定的事件处理程序ev_callback。核心代码如下所示:
int
event_base_loop(struct event_base* base, int flags)
{
int done = 0
while (!done) {
// 存在激活事件
if (base->event_count_active) {
// 回调事件处理程序
event_process_active(base);
if (!base->event_count_active && (flags & EVLOOP_ONCE))
done = 1;
}
}
}
// 处理所有激活队列里的事件,对每个事件调用回调函数
static void
event_process_active(struct event_base* base)
{
struct event* ev;
struct event_list* activeq = NULL;
int i;
short ncalls;
//获取优先级最大的激活事件队列,base->activequeues[i],i越小优先级越大
for (i = 0; i < base->nactivequeues; ++i) {
if (TAILQ_FIRST(base->activequeues[i]) != NULL) {
// struct event_list *,即一个结构体数组,存储相同优先级的激活事件
activeq = base->activequeues[i];
break;
}
}
assert(activeq != NULL);
// 从优先级最大的激活事件队列取出事件进行处理
for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq)) {
// 标志为EV_PERSIST的事件是仅从激活事件队列删除,没有从event_loop从删除
if (ev->ev_events & EV_PERSIST)
event_queue_remove(base, ev, EVLIST_ACTIVE);
else
// 未设置EV_PERSIST标志的激活事件正准备回调事件处理程序
event_del(ev);
/* Allows deletes to work */
ncalls = ev->ev_ncalls;
ev->ev_pncalls = &ncalls;
while (ncalls) {
ncalls--;
ev->ev_ncalls = ncalls;
// 执行事件处理程序
(*ev->ev_callback)((int)ev->ev_fd, ev->ev_res, ev->ev_arg);
// 收到中断事件或者上层调用event_base_loopbreak
if (event_gotsig || base->event_break) {
ev->ev_pncalls = NULL;
return;
}
}
ev->ev_pncalls = NULL;
}
}