Libevent中的event结构体对应于Reactor模式中的事件处理器,其中提供了函数接口,供Reactor管理器在事件发生时调用。

1.event结构体
  1. 事件的类型:主要分为IO读写、信号、超时事件
#define EV_TIMEOUT  0x01    // 超时
#define EV_READ     0x02    // 可读
#define EV_WRITE    0x04    // 可写
#define EV_SIGNAL   0x08    // 产生信号
#define EV_PERSIST  0x10    // 标识事件不会退出事件循环(Abort loop)
  1. 表示事件的结构体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.信号事件
  1. 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;
};
  1. 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__);
    }
}
  1. 注册信号事件:通过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);
}
  1. 注销已注册的信号事件:使用evsignal_del函数

综上:由于信号事件和IO读写事件不同,不能直接让epoll模型检测就绪事件的产生。那么当有信号事件发生,如何通知事件处理框架event_base进行处理呢?

  1. 在初始化event_base_new时,使用socketpair函数创建一对套接字,记为ev_signal_pair[0]、ev_signal_pair[1]。
  2. 在注册非超时事件时,将ev_signal_pair[1]挂载到epoll实例上,由epoll模型监听该套接字的可读事件
  3. 当有信号送达,就会被Libevent捕捉到,接着信号处理器(信号的回调函数)会执行。其中向ev_signal_pair[0]套接字写入1个字节的数据,触发ev_signal_pair[1]的读事件就绪。然后epoll_wait就会返回,将捕捉到信号对应的信号事件放入激活事件队列中
  4. 在事件循环event_base_loop中执行ev_signal_pair[1]上有可读事件时设置的回调evsignal_cb
3.定时器事件
  1. 在事件主循环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);
  1. 定时器事件的超时时间如何设定的?在调用event_add函数注册定时器事件时设定
// 得到定时器触发时间点
gettime(base, &now);
// tv + &now
evutil_timeradd(&now, tv, &ev->ev_timeout);
  1. 如何判断定时事件已经触发呢?如果小根堆堆顶时间(注册定时事件的时间)小于当前时间则说明定时事件已经触发
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.激活事件的统一处理
  1. 已激活事件存储在优先级队列中,由事件处理框架根据事件的优先级在事件循环中调用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;
    }
}