您的位置:首页 > 其它

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

2017-10-12 17:15 239 查看
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 源码