libevent源码学习-----event操作
2017-10-12 17:15
239 查看
libevent核心结构是event_base和event,接下来主要介绍event结构
程序中使用event最开始需要event_new创建一个event
接下来一个个解释每个变量的作用
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
主要用于记录用户提供的相对时间,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_assign其实就是各种初始化,没什么特别的地方
event_add调用event_add_internal函数
event_add_internal比较重要,它根据event的不同类型添加到不同的base队列中
总结
event是整个libevent的核心,通过指针保存回调函数,这样当fd被激活,就可以由libevent调用这个回调函数而无需用户关系什么时候调用。libevent内部函数指针的使用特别频繁,主要就集中在这里
对于初始化工作,其实就是程序用到什么就初始化什么,比较无脑,但是具体的细节还是值得细看的
event_add_internal这个函数尤为重要,它不仅由event_add函数调用,对于超时event仍然需要重复调用这个函数,所以在对超时event的管理上libevent的做法还是很值得学习的,不过在设计过程中要做到思路清晰真的不容易,可以细细研究大神之作
/* 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】源码分析(4)--与event相关的一些函数和操作
- Libevent源码分析-----与event相关的一些函数和操作
- Libevent源码分析-----与event相关的一些函数和操作
- Libevent源码学习---bufferevent
- 【libevent】源码学习(2)--配置event_base
- libevent源码学习-----event_base事件循环
- Libevent源码分析-----与event相关的一些函数和操作
- libevent源码分析:event_assign、event_new
- Libevent源码分析-----管理超时event
- libevent源码分析--event_set()函数
- Libevent源码分析-----evbuffer结构与基本操作
- Netty源码学习(二)NioEventLoopGroup
- [libevent源码分析] event_init
- C#源码学习之---事件驱动异步文件操作
- [libevent源码分析] event_init
- libevent学习之创建,销毁event_base
- nginx 源码学习笔记(二十三)—— event 模块(四) ——timer红黑树
- libevent学习二(Working with an event loop)
- nginx 源码学习笔记(二十三)—— event 模块(四) ——timer红黑树
- Libevent学习---bufferevent(二)