libevent源码学习-----event操作

libevent核心结构是event_base和event,接下来主要介绍event结构

/* event的定义的主要部分 */
struct event {

    /* ... */

    /* event监听的描述符,也可以是信号值 */
    evutil_socket_t ev_fd;

    /* 事件驱动主循环 */
    struct event_base *ev_base;

    short ev_events;
    short ev_res;       /* result passed to event callback */
    short ev_flags;
    ev_uint8_t ev_pri;  /* smaller numbers are higher priority */

    union {
        /* used for io events */
        struct {
            TAILQ_ENTRY(event) ev_io_next;
            struct timeval ev_timeout;
        } ev_io;

        /* used by signal events */
        struct {
            TAILQ_ENTRY(event) ev_signal_next;
            short ev_ncalls;
            /* Allows deletes in callback */
            short *ev_pncalls;
        } ev_signal;
    } _ev;


    struct timeval ev_timeout;

    /* allows us to adopt for different types of events */
    void (*ev_callback)(evutil_socket_t, short, void *arg);
    void *ev_arg;
};

程序中使用event最开始需要event_new创建一个event

/* 创建事件驱动 */
struct event_base* base = event_base_new(); 
/*
 *创建一个事件
 *@param base: 事件驱动
 *@param fd: event对应的文件描述符,通常是通过socket创建的套接字
 *@param EV_READ: 想要监听fd的哪些事件,EV_READ表示监听fd是否可读,也可以是EV_PERSIST代表这个event是永久事件,在调用一次回调函数后仍然继续监听,对应一次性event,调用后不再监听
 *@param cb: 当fd对应的事件发生后调用的回调函数,用户提供
 *@param arg: 传给回调函数cb的参数
 */
struct event* ev = event_new(base, fd, EV_READ|EV_PERSIST, cb, arg);

接下来一个个解释每个变量的作用
evutil_socket_t ev_fd;
event负责监听的描述符,也可以是信号值

struct event_base *ev_base;
事件驱动base

short ev_events;
对应监听fd的某些事件,上述代码中是EV_READ | EV_PERSIST,可用的events包括

  • EV_READ:fd可读
  • EV_WRITE:fd可写
  • EV_PERSIST:永久事件,激活一次后仍然继续监听,对应一次事件,激活一次后不再监听
  • EV_SIGNAL:代表这个event监听的是一个信号
  • EV_TIMEOUT:代表这个event具有超时时长

short ev_res;
当event被激活时,ev_res的值记录着是被哪些事件(上述)激活,即激活的原因

short ev_flags;
event处于的状态,其实是event都在哪几个队列中(base中有多个队列),可以是以下几种的或运算

  • EVLIST_INIT:表示event刚被初始化,不在任何队列中,通常是刚调用完event_new
  • EVLIST_INSERTED:表示event处于base的注册队列中,通常是调用event_add后
  • EVLIST_ACTIVE:表示event处于base的激活队列中,通常是event被激活,等待调用回调函数
  • EVLIST_TIMEOUT:表示event处于最小堆中,表示event具有超时时间
  • EVLIST_ALL:私有空间,不明

ev_uint8_t ev_pri;
event的优先级,base的激活队列是一个数组,每个数组元素是一个队列,数组下标越低优先级越高,在统一处理激活event时,从优先级高的event开始调用回调函数

_ev

    union {
        /* used for io events */
        struct {
            TAILQ_ENTRY(event) ev_io_next;
            struct timeval ev_timeout;
        } ev_io;

        /* used by signal events */
        struct {
            TAILQ_ENTRY(event) ev_signal_next;
            short ev_ncalls;
            /* Allows deletes in callback */
            short *ev_pncalls;
        } ev_signal;
    } _ev;

主要用于记录用户提供的相对时间,ev_timeout变量

struct timeval ev_timeout;
event超时的绝对时间

void (*ev_callback)(evutil_socket_t, short, void *arg);
用户提供的回调函数,函数指针

void *ev_arg;
传给回调函数的参数


对event的初始化操作主要集中在event_new,event_add上

event_new调用event_assign注册一个event

/*
 * 每次要添加事件都需要先调用event_new函数创建一个event,函数参数指明
 * 事件所属的驱动base
 * 事件对应的文件描述符或者信号类型fd
 * fd对应的事件events, 如EV_READ, EV_WRITE, EV_PERSIST,注意信号是EV_SIGNAL
 * 当响应事件发生时调用的回调函数cb以及传给cb的参数
 * 
 * 使用者不需要自己判断什么时候事件发生然后调用事件处理函数,而只需要将关注的这些东西
 * 传给event,唯一需要的就是自己定义一个回调函数
 * 
 * 当调用event_add后
 * event_base会统一管理它接受的所有事件,当某一个事件发生时,取得相应的event,然后调用event中存储
 * 的回调函数,同时将需要的参数传入。包括fd, 回调函数这些变量都在每一个event中存储着
 * 这就是Reactor事件驱动
 * 
 * event_new的内部调用的是event_assign函数,作用是创建一个event并初始化然后返回,
 * 用户也可以自己调用这个函数
 * 
 * 注意:这个函数并没有将event注册到base中,那是event_add的任务
 */
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_assign其实就是各种初始化,没什么特别的地方

/* 函数对struct event这个结构体的成员变量赋值 */
int
event_assign(struct event *ev, struct event_base *base, evutil_socket_t fd, short events, void (*callback)(evutil_socket_t, short, void *), void *arg)
{
    if (!base)
        base = current_base;


    /* 
     * 仅仅是将event的base成员变量进行绑定,此时仍然没有将event添加到base中
     * 如果需要添加,需要手动调用event_add()函数
     */
    ev->ev_base = base;

    ev->ev_callback = callback;
    ev->ev_arg = arg;
    ev->ev_fd = fd;
    /*
     * ev_events表示的是需要监听的事件,此外有几个值用于区分io和信号,一次和永久
     * EV_READ/EV_WRITE:读写事件
     * EV_SIGNAL:表示这个事件是一个信号
     * EV_PERSIST:表示这个事件是一个永久事件,当处理过一次之后仍然继续监听,否则处理一次后就被删除掉
     */
    ev->ev_events = events;
    /* ev_res表示event被哪个事件激活 */
    ev->ev_res = 0;
    /*
     * ev_flags表示的是这个event目前的状态,其实就是event在base的哪几个队列里/或最小堆
     * 初始状态EVLIST_INIT:表示刚刚初始化,还没有注册到base中,不在任何队列中
     * 注册状态EVLIST_INSERTED:表示已经注册到base中,在base的注册队列中
     * 活跃状态EVLIST_ACTIVE:表示监听的某个事件发生,等待着调用回调函数,在激活队列中
     * 超时状态EVLIST_TIMEOUT:表示具有超时时间的事件超时,在最小堆中
     * 内部事件EVLIST_INTERNAL:表示这个event是一个内部event,用于信号的统一
     */
    ev->ev_flags = EVLIST_INIT;
    /* 被激活的次数,对信号有用,因为一段时间内可能传来多个同样的信号 */
    ev->ev_ncalls = 0;
    ev->ev_pncalls = NULL;

    /*
     * ev_closure
     * 用于区分信号/io,永久/一次event
     * 在event_base_active_single_queue中用于判断是否是永久/信号event
     * 对于永久event且有超时时间,重新计算时间然后调用event_add
     * 对于永久信号,调用用户的信号处理函数,可能多次调用,如果同一个信号发生多次的话
     */
    if (events & EV_SIGNAL) {
        if ((events & (EV_READ|EV_WRITE)) != 0) {
            event_warnx("%s: EV_SIGNAL is not compatible with "
                "EV_READ or EV_WRITE", __func__);
            return -1;
        }
        ev->ev_closure = EV_CLOSURE_SIGNAL;
    } else {
        if (events & EV_PERSIST) {
            evutil_timerclear(&ev->ev_io_timeout);
            ev->ev_closure = EV_CLOSURE_PERSIST;
        } else {
            ev->ev_closure = EV_CLOSURE_NONE;
        }
    }

    /*
     * 如果事件具有超时时间,那么它将被添加到base的时间最小堆上
     * 而最小堆其实就是一个struct event*类型的数组,首先需要初始化每个事件
     * 在最小堆中的索引为-1,表示当前不在最小堆中
     * 下标用处不明
     */
    min_heap_elem_init(ev);

    if (base != NULL) {
        /* by default, we put new events into the middle priority */
        /*
         * base中有的激活队列是一个队列数组,数组的每一个元素是一个队列
         * 数组下标代表响应队列的优先级,下标越低,优先级越高
         * 所有如果某个事件发生了,会将响应的event放入对应优先级的队列里,
         * event优先级初始化工作在这里,默认优先级时base中激活队列数量的一半
         * 也就是优先级是中等水平,可以手动修改
         */
        ev->ev_pri = base->nactivequeues / 2;
    }

    return 0;
}

event_add调用event_add_internal函数

/*
 * 这个函数用来将event添加到base中,也就是添加到base的注册队列中
 * 函数内部通过调用event_add_internal()来完成工作,不过再次之前需要为base加锁,
 * 因为需要更改base中的队列,而其他线程此时可能正在访问队列,这就造成了race condition
 * 所以需要锁来保护
 */
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_internal(ev, tv, 0);

    EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);

    return (res);
}

event_add_internal比较重要,它根据event的不同类型添加到不同的base队列中

/*
 * event_add调用的内部函数,用于将event添加到base的注册队列中
 * 同时添加到相应的map中
 * 
 * 注意:这个函数不仅仅只由event_add调用,还有event_persist_closure调用
 * 由这个函数调用是因为当具有超时时间的event被激活后,需要先从base中的所有队列中删除
 * 然后重新计算超时时间,再重新添加到base中,所以又重新调用了这个函数
 * 
 * 注意:event不仅代表文件描述符,还有可能是信号的event,当是信号时,会递归
 * 调用两遍这个函数,第一遍调用时判断是信号则调用evsig_map_add函数,在这个函数中
 * 进行两步
 *   将信号event添加到base的信号map中
 *   调用evsigops的add函数,即调用evsig_add,这个函数中绑定内部信号处理函数,同时将socketpair的event
 *   添加到base中,使用event_add,也就是调用event_add_internal
 * 不过只会执行两遍,因为在evsig_add中会进行判断,只有第一次添加socketpair的event时才会执行第二次调用
 * 
 * 见evsig_add
 */
static inline int
event_add_internal(struct event *ev, const struct timeval *tv,
    int tv_is_absolute)
{
    struct event_base *base = ev->ev_base;
    int res = 0;
    int notify = 0;


    /*
     * 这一步主要是用来让最小堆增加一个位置,并没有实际添加到最小堆上
     * 判断条件是这是一个具有超时时间的event,同时在最小堆中没有这个event
     * 这样就需要在最小堆上留出一个位置来存放这个event
     * 因为用户可以对同一个event调用event_add多次,这就可能两次event_add除了超时时间不同
     * 其他的都相同,这样就不需要在留出一个位置,直接替换以前的就可以
     * 
     * 如果已经在最小堆中,ev_flags将是EVLIST_TIMEOUT
     */
    if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
        if (min_heap_reserve(&base->timeheap,
            1 + min_heap_size(&base->timeheap)) == -1)
            return (-1);  /* ENOMEM == errno */
    }


    /*
     * 这一步就是根据io还是signal添加到base的不同map中,然后加入到base的队列中,
     * 注意event_queue_insert的最后一个参数
     * 这个函数可以根据参数的不同选择添加不同的队列中,同时为这个event的flags添加
     * 不同的状态,此时为EVLIST_INSERTED会另event->flags |= EVLIST_INSERTED,同时添加到注册队列上
     * 表示这个event处于base的注册队列中
     *
     * 此时仍然没有考虑具有超时时间的event,所以这种event也同样会进入这个语句
     * 添加到不同的map中,然后添加到base的注册队列中
     * 在下面还会单独为具有超时时间的event调用一次,那时为EVLIST_TIMEOUT
     */
    if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
        !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
        /*
         * 注意只有文件描述符的event会添加到io函数中,信号event不添加,它不需要监听
         * 有信号发生会由内核通知进程
         */
        if (ev->ev_events & (EV_READ|EV_WRITE))
            res = evmap_io_add(base, ev->ev_fd, ev);
        else if (ev->ev_events & EV_SIGNAL)
            res = evmap_signal_add(base, (int)ev->ev_fd, ev);
        /*
         * 注意如果是信号,在两层递归调用时会将
         * 信号event,socketpair读端event都添加到signal map和base注册队列中
         */
        if (res != -1)
            event_queue_insert(base, ev, EVLIST_INSERTED);
        if (res == 1) {
            /* evmap says we need to notify the main thread. */
            notify = 1;
            res = 0;
        }
    }

    /*
     * we should change the timeout state only if the previous event
     * addition succeeded.
     */
    /* 这一步开始处理具有超时时间的event */
    if (res != -1 && tv != NULL) {
        struct timeval now;
        int common_timeout;

        /*
         * for persistent timeout events, we remember the
         * timeout value and re-add the event.
         *
         * If tv_is_absolute, this was already set.
         */
        /*
         * event分为永久的和一次的,是用户在调用event_new时传入的参数
         * 对于永久的event,在被激活一次之后还需要继续监听,
         * 而对于有超时时间的event,需要对event的超时时间进行更新
         * 
         * 为什么:因为base在进行超时判断时是通过绝对时间进行判断的,也就是说在添加event的时候
         * 将当前时间+时间间隔获得的绝对时间作为判断超时的依据
         * 这样做的原因是不需要在判断超时时比较时间差,只需要比较当前时间和超时时间即可
         * 
         * 所以,如果event是永久的,那么再处理过一次之后需要更新超时绝对时间,方法就是保存用户
         * 传入的时间间隔,再下一次添加时使用
         *
         * tv_is_absolute是传入的参数,event_add传入时设为0,表示传入的时间是时间间隔,不是绝对时间
         */
        if (ev->ev_closure == EV_CLOSURE_PERSIST && !tv_is_absolute)
            ev->ev_io_timeout = *tv;

        /*
         * we already reserved memory above for the case where we
         * are not replacing an existing timeout.
         */

        /*
         * 对于用户对同一个event调用event_add多次的情况,先将以前的从最小堆
         * 中删除,再添加更新的这个
         */
        if (ev->ev_flags & EVLIST_TIMEOUT) {
            /* XXX I believe this is needless. */
            if (min_heap_elt_is_top(ev))
                notify = 1;
            event_queue_remove(base, ev, EVLIST_TIMEOUT);
        }

        /* Check if it is active due to a timeout.  Rescheduling
         * this timeout before the callback can be executed
         * removes it from the active list. */
        /* 
         * 如果此时event正处于激活队列中,从激活队列删了 
         * 如果是信号,将其发生次数设为0,就不会调用信号处理函数了
         */
        if ((ev->ev_flags & EVLIST_ACTIVE) &&
            (ev->ev_res & EV_TIMEOUT)) {
            if (ev->ev_events & EV_SIGNAL) {
                /* See if we are just active executing
                 * this event in a loop
                 */
                if (ev->ev_ncalls && ev->ev_pncalls) {
                    /* Abort loop */
                    *ev->ev_pncalls = 0;
                }
            }

            event_queue_remove(base, ev, EVLIST_ACTIVE);
        }

        /* 计算超时绝对事件 */
        gettime(base, &now);

        common_timeout = is_common_timeout(tv, base);
        if (tv_is_absolute) {
            ev->ev_timeout = *tv;
        } else if (common_timeout) {
            struct timeval tmp = *tv;
            tmp.tv_usec &= MICROSECONDS_MASK;
            evutil_timeradd(&now, &tmp, &ev->ev_timeout);
            ev->ev_timeout.tv_usec |=
                (tv->tv_usec & ~MICROSECONDS_MASK);
        } else {
            evutil_timeradd(&now, tv, &ev->ev_timeout);
        }



        /* 调用event_queue_insert()将具有超时时间的event添加到base最小堆中 */
        event_queue_insert(base, ev, EVLIST_TIMEOUT);

        if (min_heap_elt_is_top(ev))
            notify = 1;
    }

    return (res);
}

总结
event是整个libevent的核心,通过指针保存回调函数,这样当fd被激活,就可以由libevent调用这个回调函数而无需用户关系什么时候调用。libevent内部函数指针的使用特别频繁,主要就集中在这里
对于初始化工作,其实就是程序用到什么就初始化什么,比较无脑,但是具体的细节还是值得细看的
event_add_internal这个函数尤为重要,它不仅由event_add函数调用,对于超时event仍然需要重复调用这个函数,所以在对超时event的管理上libevent的做法还是很值得学习的,不过在设计过程中要做到思路清晰真的不容易,可以细细研究大神之作

你可能感兴趣的:(libevent,libevent,源码)