版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Function_Dou/article/details/87891357
本节我们依旧来分析event
的操作, 接着上节没有讲完的关于event的注册和注销两个功能的实现.
event 注册
依旧来看一个event 注册函数的原型
int event_add(struct event *ev, const struct timeval *tv);
相比event_set
函数event_add
真的很干净.
参数
- ev : 将要注册的event事件. 其实实际注册的是
ev->ev_base
. - tv : 设置超时时间. 如果
tv != NULL
则会同时注册定时事件, 将ev
添加到timer堆上.
函数源码event.c
函数真的很长, 不慌, 我们慢慢的分析(基本在注释中).
// 设置事件的定时. 实质 : 注册事件
// 1. 如果设置定时, 则先在小根堆上申请一个注册的空间
// 2. 如果事件已经处于定时队列, 则将其从定时队列中移除
// 3. 如果事件已经在就绪队列中, 则将其从就绪队列中移除
// 4. 重新设置定时时间, 并将其插入到定时队列中
int
event_add(struct event *ev, const struct timeval *tv)
{
struct event_base *base = ev->ev_base; // 指向要注册的 ev_base
const struct eventop *evsel = base->evsel;
void *evbase = base->evbase; // base 所使用的IO成员
int res = 0;
event_debug((
"event_add: event: %p, %s%s%scall %p",
ev,
ev->ev_events & EV_READ ? "EV_READ " : " ",
ev->ev_events & EV_WRITE ? "EV_WRITE " : " ",
tv ? "EV_TIMEOUT " : " ",
ev->ev_callback));
assert(!(ev->ev_flags & ~EVLIST_ALL));
/*
* prepare for timeout insertion further below, if we get a
* failure on any step, we should not change any state.
*/
// 如果设置了定时, 则现在先在小根堆上分配一个空位留给定时事件, 保证注册定时事件时不会失败
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 */
}
// 该事件是处于读/写/信号并且没有处于就绪队列中
if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
!(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
// 添加处理函数. 信号也是在这里被添加的, 都是统一的添加接口
res = evsel->add(evbase, ev);
if (res != -1)
// 将设置的事件插入到注册链表中
event_queue_insert(base, ev, EVLIST_INSERTED);
}
/*
* we should change the timout state only if the previous event
* addition succeeded.
*/
// 设置了超时时间
if (res != -1 && tv != NULL) {
struct timeval now;
/*
* we already reserved memory above for the case where we
* are not replacing an exisiting timeout.
*/
// 如果已经在定时队列中, 就先将其移除再重新进行插入
if (ev->ev_flags & EVLIST_TIMEOUT)
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. */
// 检查当前事件是已经处于激活状态而且是设置了定时事件的话,
// 就将其从就绪队列中删除
// 注意 : 上面是从定时队列中移除, 这里是从就绪队列中移除
if ((ev->ev_flags & EVLIST_ACTIVE) &&
(ev->ev_res & EV_TIMEOUT)) {
/* 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);
evutil_timeradd(&now, tv, &ev->ev_timeout);
event_debug((
"event_add: timeout in %ld seconds, call %p",
tv->tv_sec, ev->ev_callback));
// 重新插入定时队列中
event_queue_insert(base, ev, EVLIST_TIMEOUT);
}
return (res);
}
从29行可以看出来libevent的精妙之处 : 如果申请到空间, 则下面的操作就能够正常执行, 如果不能就直接退出, 这里保证了定时的注册是一个原子操作.
现在来分析event_add
的执行过程 :
- 如果设置了定时, 则在小根堆上分配一个空间, 以便之后的注册.
- 如果事件已经设置了I/O或者信号标志, 并且事件并没有被初始化或者没有在就绪队列中, 则将事件插入到就绪队列中(就是有可能该事件并没有被初始化也可以调用注册函数哦).
- 如果设置了定时, 现在判断它是否已经在定时队列或者在就绪队列中了,如果是, 则从队列中删除在重新设置定时并且插入到定时队列中.(就是可能事件之前已经设置了定时时间, 但是可能不满意可以反悔重新设置定时时间的)
相信整个函数的流程清楚了, 但是其中一些出现过几次的函数(event_queue_insert
和event_queue_remove
)知道功能但还想看一下实现吧. 不急不急, 我们一个个来分析.
event_queue_insert函数
将处理事件插入到指定队列中.
// 将处理事件插入到指定队列中
void
event_queue_insert(struct event_base *base, struct event *ev, int queue)
{
// 如果事件已经在该队列中, 则什么也不做
if (ev->ev_flags & queue) {
/* Double insertion is possible for active events */
if (queue & EVLIST_ACTIVE)
return;
event_errx(1, "%s: %p(fd %d) already on queue %x", __func__,
ev, ev->ev_fd, queue);
}
// 注册次数 + 1
if (~ev->ev_flags & EVLIST_INTERNAL)
base->event_count++;
// 设置该标志位, 表示该事件已经被插入到该队列中
ev->ev_flags |= queue;
switch (queue) {
case EVLIST_INSERTED:
TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next);
break;
case EVLIST_ACTIVE:
base->event_count_active++; // 队列的就绪个数 + 1
TAILQ_INSERT_TAIL(base->activequeues[ev->ev_pri],
ev,ev_active_next);
break;
case EVLIST_TIMEOUT: {
min_heap_push(&base->timeheap, ev);
break;
}
default:
event_errx(1, "%s: unknown queue %x", __func__, queue);
}
}
其实理解还是很容易的.
- 如果事件已经被注册了则什么也不做的返回
- 如果事件并没有被初始化, 则将
base
的注册数 +1 - 将事件加入到指定队列中. (TAILQ_INSERT_TAIL 之后分析源码.)
event_queue_remove函数
将事件从指定队列中移除.
// 将事件从指定队列中移除
void
event_queue_remove(struct event_base *base, struct event *ev, int queue)
{
if (!(ev->ev_flags & queue))
event_errx(1, "%s: %p(fd %d) not on queue %x", __func__,
ev, ev->ev_fd, queue);
// 注册次数 - 1
if (~ev->ev_flags & EVLIST_INTERNAL)
base->event_count--;
// 重设标志位, 表示事件已经从该队列中被移除
ev->ev_flags &= ~queue;
switch (queue) {
case EVLIST_INSERTED:
TAILQ_REMOVE(&base->eventqueue, ev, ev_next);
break;
case EVLIST_ACTIVE:
base->event_count_active--; // 队列的就绪个数 - 1
TAILQ_REMOVE(base->activequeues[ev->ev_pri],
ev, ev_active_next);
break;
case EVLIST_TIMEOUT:
min_heap_erase(&base->timeheap, ev);
break;
default:
event_errx(1, "%s: unknown queue %x", __func__, queue);
}
}
过程与上面一样.
- 如果事件并没有在指定队列上则报错
base
上的注册次数 -1- 重新设置
ev
事件的flags
标志. 并从指定队列中删除注册事件.
event 的注销
event 注销其实就很简单了. 从所有队列中删除指定事件.
// 从所有队列中删除指定事件
int
event_del(struct event *ev)
{
struct event_base *base;
const struct eventop *evsel;
void *evbase;
event_debug(("event_del: %p, callback %p",
ev, ev->ev_callback));
/* An event without a base has not been added */
if (ev->ev_base == NULL)
return (-1);
// ev注册的event_base和eventop指针
base = ev->ev_base;
evsel = base->evsel;
evbase = base->evbase;
assert(!(ev->ev_flags & ~EVLIST_ALL));
/* See if we are just active executing this event in a loop */
// 将事件的回调次数设为0
if (ev->ev_ncalls && ev->ev_pncalls) {
/* Abort loop */
*ev->ev_pncalls = 0;
}
// 事件从每一个队列中进行删除
if (ev->ev_flags & EVLIST_TIMEOUT)
event_queue_remove(base, ev, EVLIST_TIMEOUT);
if (ev->ev_flags & EVLIST_ACTIVE)
event_queue_remove(base, ev, EVLIST_ACTIVE);
if (ev->ev_flags & EVLIST_INSERTED) {
event_queue_remove(base, ev, EVLIST_INSERTED);
return (evsel->del(evbase, ev)); // 同时取消监听
}
return (0);
}
- 如果事件已经就绪, 则将就绪调用回调函数的次数设置为 0
- 从事件所在的链表中删除, 一个事件以可能同时处在多个事件队列上.
- 如果事件处于
EVLIST_INSERTED
状态的话, 最后还应该将它的监听也删除. 如: 从epoll的监听中删除, 从绑定的信号中删除.
总结
篇幅可能有点长, 但是大多都是代码, 所以慢慢分析的话难度也不大. 其实最重要的就是搞清楚event
和event_base
每个成员对应的哪个功能, 其他都是判断事件状态, 加入某个队列或从某个队列中删除. 接下来我们分析event
重要的主循环.