版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Function_Dou/article/details/87891373
搞清楚前面已经分析的事件的注册和注销两方面, 相信你对libevent已经有了一定的认识了, 而本节将来分析调用过程中很重要的功能, 他叫做主循环.
主循环主要执行的工作就是将就绪队列中的事件全部激活执行调度. 感觉是不是重心啊? 激活事件耶, 就是执行回调呀. 但是我们的event
和event_base
中可以包含的事件可以是 IO, 信号, 定时那么这些具体怎么柔和到一起进行处理呢? 接下来我们就来看一下吧.
event_base_loop 函数
代码量有点大, 但是没关系, 只要仔细分析每一步实现的功能在大致从头分析一下应该都能够理清楚思路.
// 主循环
// 1. 设置退出标志位, 则退出主循环
// 2. 主循环被阻塞直到有就绪事件或者非阻塞
// 3. 将超时事件加入就绪队列中
// 4. 如果就绪队列有事件并且执行完或者设置是非阻塞, 则退出主循环
int
event_base_loop(struct event_base *base, int flags)
{
const struct eventop *evsel = base->evsel; // base 的复用功能的函数
void *evbase = base->evbase; // 复用功能函数指定的参数
struct timeval tv;
struct timeval *tv_p;
int res, done;
/* clear time cache */
base->tv_cache.tv_sec = 0; // 清除 base 中的时间
// 如果设置有信号事件, 就将 base 交给专门处理信号的结构处理
// 将信号单独作处理
if (base->sig.ev_signal_added)
evsignal_base = base;
done = 0;
while (!done) {
/* Terminate the loop if we have been asked to */
// 如果设置了退出标志位, 就会退出主循环
if (base->event_gotterm) {
base->event_gotterm = 0;
break;
}
if (base->event_break) {
base->event_break = 0;
break;
}
/* You cannot use this interface for multi-threaded apps */
// 这个我们不分析. 就是处理信号
while (event_gotsig) {
event_gotsig = 0;
if (event_sigcb) {
res = (*event_sigcb)();
if (res == -1) {
errno = EINTR;
return (-1);
}
}
}
// 每次都会矫正时间
timeout_correct(base, &tv);
tv_p = &tv;
// 如果没有就绪事件并且设置的阻塞模式, 则调用 timeout_next 计算最近的计时器
// 什么时候超时, 如果没有定时器则 tv_p == NULL
// 如果设置了为非阻塞模式, 则不会计算最近计时器触发的时间. 一般非阻塞模式都
// 是在并发量大的情况下, 否则纯粹是浪费 CPU
if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) {
timeout_next(base, &tv_p);
} else {
/*
* if we have active events, we just poll new events
* without waiting.
*/
// tv->tv_sev = tv->tv_usec = 0;
// 有事件或者非阻塞则立即返回
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;
// 调用的是某种具体的多路IO机制的等待函数, 如 : epoll的epoll_wait函数
res = evsel->dispatch(base, evbase, tv_p);
if (res == -1)
return (-1);
// 缓存当前时间
gettime(base, &base->tv_cache);
// 如果有(超时)被激活的事件, 就将事件从最小堆中移除并加入到就绪队列中
timeout_process(base);
// 就绪队列中有事件, 则将事件进行处理.
// 如果就绪队列非空 或者 当前状态是非阻塞则退出主循环
if (base->event_count_active) {
event_process_active(base);
if (!base->event_count_active && (flags & EVLOOP_ONCE))
done = 1;
} else if (flags & EVLOOP_NONBLOCK)
done = 1;
}
/* clear time cache */
base->tv_cache.tv_sec = 0;
event_debug(("%s: asked to terminate loop.", __func__));
return (0);
}
不明白的地方可能会有一点的多, 但是有些我们就没必须区深究,如 : 信号处理, 时间获取. 现在把大概的过程理一下
- 如果到来的是信号, 则由libevent的全局变量
evsignal_base
指向. - 如果设置了
event_base
中的退出主循环标志, 则退出主循环. (在event_base结构提过, 待会具体分析). - 矫正时间并将时间保存起来.
- 如果就绪队列没有事件, 并且设置的是阻塞. 那么调用
timeout_next
计算最近的计时器什么时候超时(也就是找出快要超时的或者已经超时的事件). 如果就绪队列中有事件或者是非阻塞的则将tv
清零. - 调用
dispath
函数, 如果没有就绪事件则退出, 否则调用 IO 多路函数等待事件(这里将dispath
是将select
和epoll
等调用函数封装了, 如 ;epoll就是调用epoll_wait
函数等待). - 最后, 如果就绪队列有事件则处理事件处理完退出主循环, 或者没有就绪事件设置非阻塞则直接退出主循环.
啊啊啊, 终于理出来了. 确实写出来还是有一点多哦. 其实可以看出来主循环就是将超时事件加入就绪队列接着将就绪队列的事件全部处理掉再退出主循环. 不管是IO 还是时间都是加入到就绪队列一并处理了. (信号暂时不管)
接着我们来分析退出主循环的几个方式.
- 如果没有就绪事件或者设置为非阻塞都会正常退出主循环.
- 如果设置了
event_break
和event_gotterm
标志, 就会直接退出循环, 非正常的
下节我们来分析怎样设置非正常退出的标志
调用主循环
可能你看到调用主循环有点懵逼, 上面event_base_loop
不就是主循环啊, 直接调用就行了啊. 但是不要忘了直接调用的话需要我们传入event_base结构啊, 但是用户万一传错了怎么办, 所以libevent对主循环有一个简单的调用方法.
int
event_dispatch(void)
{
return (event_loop(0));
}
int
event_loop(int flags)
{
return event_base_loop(current_base, flags); // 调用libevent的全局 event_base变量
}
直接调用event_dispatch
即可, 也可以调用event_base_dispatch
int
event_base_dispatch(struct event_base *base)
{
return event_base_loop(base, 0);
}
总结
理解清楚主循环是为了处理就绪事件, 没有就绪事件或者设置退出标志就会退出主循环.