我们知道libevent是一个Reactor模式的事件驱动的网络库。
 
到目前为止,我们已经看了核心的event和event_base结构体的源码,看了初始化这两个结构体的源码,看了注册event的源码,也将event注册到I/O多路复用监听的事件上了。现在准备工作都做好了,下面就是看运行时的主循环了,在这个主循环中,是如何检测事件、分发事件、调用事件的回调函数的。这一步就是libevent的核心框架流程了。
 
Reactor模式中的Event、Event Handler、Reactor目前都完成了,下面就剩Event Demultiplexer了。
这一步通过event_base_dispatch()完成
int
event_base_dispatch(struct event_base *event_base)
{
return (event_base_loop(event_base, )); //调用event_base_loop()
}

可以看到,该函数只是做了调用event_base_loop()这一个动作,所以工作实际是在函数event_base_loop()内完成的。

event_base_loop()

该函数完成以下工作:
1.信号标记被设置,则调用信号的回调函数
2.根据定时器最小时间,设置I/O多路复用的最大等待时间,这样即使没有I/O事件发生,也能在最小定时器超时时返回。
3.调用I/O多路复用,监听事件,将活跃事件添加到活跃事件链表中
4.检查定时事件,将就绪的定时事件从小根堆中删除,插入到活跃事件链表中
5.对event_base的活跃事件链表中的事件,调用event_process_active()函数,在该函数内调用event的回调函数,优先级高的event先处理。
 
该函数内部调用了eventop.dispatch()监听事件,event_sigcb函数指针处理信号事件,timeout_process()将超时的定时事件加入到活跃事件链表中,event_process_active()处理活跃事件链表中的事件,调用相应的回调函数。
int
event_base_loop(struct event_base *base, int flags)
{
const struct eventop *evsel = base->evsel;
void *evbase = base->evbase; //event_base的I/O多路复用
struct timeval tv;
struct timeval *tv_p;
int res, done;
/* clear time cache */
//清空时间缓存
base->tv_cache.tv_sec = ;
//处理Signal事件时,指定信号所属的event_base
if (base->sig.ev_signal_added)
evsignal_base = base;
done = ;
while (!done) { //进入事件主循环
/* Terminate the loop if we have been asked to */
//设置event_base的标记,以表明是否需要跳出循环
if (base->event_gotterm) { //event_loopexit_cb()可设置
base->event_gotterm = ;
break;
}
if (base->event_break) { //event_base_loopbreak()可设置
base->event_break = ;
break;
}
/* You cannot use this interface for multi-threaded apps */
//当event_gotsig被设置时,则event_sigcb就是信号处理的回调函数
while (event_gotsig) {
event_gotsig = ;
if (event_sigcb) {
res = (*event_sigcb)(); //调用信号处理的回调函数
if (res == -) {
errno = EINTR;
return (-);
}
}
}
timeout_correct(base, &tv); //校准时间
tv_p = &tv;
//根据定时器堆中最小超时时间计算I/O多路复用的最大等待时间tv_p
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.
*/
evutil_timerclear(&tv);
} /* If we have no events, we just exit */
//没有注册事件,则退出
if (!event_haveevents(base)) {
event_debug(("%s: no events registered.", __func__));
return ();
}
/* update last old time */
gettime(base, &base->event_tv);
/* clear time cache */
base->tv_cache.tv_sec = ;
//调用I/O多路复用,监听事件
res = evsel->dispatch(base, evbase, tv_p);
if (res == -)
return (-);
//将time cache赋值为当前系统时间
gettime(base, &base->tv_cache); //检查定时事件,将就绪的定时事件从小根堆中删除,插入到活跃事件链表中
timeout_process(base);
if (base->event_count_active) {
//处理event_base的活跃链表中的事件
//调用event的回调函数,优先级高的event先处理
event_process_active(base);
if (!base->event_count_active && (flags & EVLOOP_ONCE))
done = ;
} else if (flags & EVLOOP_NONBLOCK)
done = ;
}
/* clear time cache */
//循环结束,清空时间缓存
base->tv_cache.tv_sec = ;
event_debug(("%s: asked to terminate loop.", __func__));
return ();
}

epoll_dispatch()

在上面我们看到,event_base_loop()中通过I/O多路复用的dispatch()函数完成监听事件功能。在之前的event_init()中我们看到,通过遍历eventops数组,从中选择一个I/O多路复用机制,所以不同的I/O多路复用机制有不同的eventop结构体,相应的也就有不同的dispatch()函数。下面,再次看下eventop结构体(event-internal.h)
struct eventop {
const char *name;
void *(*init)(struct event_base *); //初始化
int (*add)(void *, struct event *); //注册事件
int (*del)(void *, struct event *); //删除事件
int (*dispatch)(struct event_base *, void *, struct timeval *); //事件分发
void (*dealloc)(struct event_base *, void *); //注销,释放资源
/* set if we need to reinitialize the event base */
int need_reinit;
};
在event_add()中通过add()成员函数注册event到监听事件中,现在在event_base_loop()中通过dispatch()成员函数监听事件。
libevent支持多种I/O多路复用机制,下面先看下epoll的eventop结构体(epoll.c)
const struct eventop epollops = {
"epoll",
epoll_init,
epoll_add,
epoll_del,
epoll_dispatch,
epoll_dealloc,
/* need reinit */
};
然后看下epoll的dispatch()函数(epoll.c)
从下面源码可见,epoll_dispatch()的工作主要有:
1.调用epoll_wait()监听事件
2.如果有信号发生,调用evsignal_process()处理信号
3.将活跃的event根据其活跃的类型注册到活跃事件链表上
4.如果events数组大小不够,则重新分配为原来2倍大小
static int
epoll_dispatch(struct event_base *base, void *arg, struct timeval *tv)
{
struct epollop *epollop = arg;
struct epoll_event *events = epollop->events;
struct evepoll *evep;
int i, res, timeout = -;
if (tv != NULL)
timeout = tv->tv_sec * + (tv->tv_usec + ) / ; //转换为微米
if (timeout > MAX_EPOLL_TIMEOUT_MSEC) { //设置最大超时时间
/* Linux kernels can wait forever if the timeout is too big;
* see comment on MAX_EPOLL_TIMEOUT_MSEC. */
timeout = MAX_EPOLL_TIMEOUT_MSEC;
}
res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout); //监听事件发生
if (res == -) {
if (errno != EINTR) {
event_warn("epoll_wait");
return (-);
}
evsignal_process(base); //由于Signal事件发生中断,处理Signal事件
return ();
} else if (base->sig.evsignal_caught) {
evsignal_process(base); //有Signal事件发生,处理Signal事件
}
event_debug(("%s: epoll_wait reports %d", __func__, res));
for (i = ; i < res; i++) { //处理活跃事件
int what = events[i].events; //活跃类型
struct event *evread = NULL, *evwrite = NULL;
int fd = events[i].data.fd; //event的文件描述符
if (fd < || fd >= epollop->nfds)
continue;
evep = &epollop->fds[fd];
if (what & (EPOLLHUP|EPOLLERR)) { //判断epoll的events类型,并找到注册的event
evread = evep->evread;
evwrite = evep->evwrite;
} else {
if (what & EPOLLIN) {
evread = evep->evread;
}
if (what & EPOLLOUT) {
evwrite = evep->evwrite;
}
}
if (!(evread||evwrite))
continue; //添加event到活跃事件链表中
if (evread != NULL)
event_active(evread, EV_READ, );
if (evwrite != NULL)
event_active(evwrite, EV_WRITE, );
}
//如果注册的事件全部变为活跃,则增大events数组为原来两倍
if (res == epollop->nevents && epollop->nevents < MAX_NEVENTS) {
/* We used all of the event space this time. We should
be ready for more events next time. */
int new_nevents = epollop->nevents * ;
struct epoll_event *new_events;
new_events = realloc(epollop->events,
new_nevents * sizeof(struct epoll_event));
if (new_events) {
epollop->events = new_events;
epollop->nevents = new_nevents;
}
}
return ();
}

event_process_active()

好了,现在活跃的I/O事件、定时器事件已经全部添加到活跃事件链表中了。下面就开始调用这些event的回调函数进行处理了,这步是在event_base_loop()中调用event_process_active()来完成的。
该函数从event_base的activequeueus链表数组上取出一个链表;对该链表上的event调用回调函数;优先调用优先级值最小的event
/*
* Active events are stored in priority queues. Lower priorities are always
* process before higher priorities. Low priority events can starve high
* priority ones.
*/
static void
event_process_active(struct event_base *base)
{
struct event *ev;
struct event_list *activeq = NULL;
int i;
short ncalls;
for (i = ; i < base->nactivequeues; ++i) { //取出第一个活跃链表
if (TAILQ_FIRST(base->activequeues[i]) != NULL) {
activeq = base->activequeues[i];
break;
}
}
assert(activeq != NULL); //优先处理优先级值最小的event
for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq)) {
if (ev->ev_events & EV_PERSIST)
event_queue_remove(base, ev, EVLIST_ACTIVE); //是持久事件,则从活跃链表移除
else
event_del(ev); //不是持久事件,则直接删除该事件 /* Allows deletes to work */
ncalls = ev->ev_ncalls;
ev->ev_pncalls = &ncalls;
while (ncalls) {
ncalls--;
ev->ev_ncalls = ncalls;
//调用该event的回调函数,event.ev_res保存返回值
(*ev->ev_callback)((int)ev->ev_fd, ev->ev_res, ev->ev_arg);
if (event_gotsig || base->event_break) {
ev->ev_pncalls = NULL;
return;
}
}
ev->ev_pncalls = NULL;
}
}

Libevent源码分析—event_base_dispatch()的更多相关文章

  1. Libevent源码分析 (1) hello-world

    Libevent源码分析 (1) hello-world ⑨月份接触了久闻大名的libevent,当时想读读源码,可是由于事情比较多一直没有时间,现在手头的东西基本告一段落了,我准备读读libeven ...

  2. 【转】libevent源码分析

    libevent源码分析 转自:http://www.cnblogs.com/hustcat/archive/2010/08/31/1814022.html 这两天没事,看了一下Memcached和l ...

  3. Libevent源码分析系列【转】

    转自:https://www.cnblogs.com/zxiner/p/6919021.html 1.使用libevent库     源码那么多,该怎么分析从哪分析呢?一个好的方法就是先用起来,会用了 ...

  4. Libevent源码分析系列

    1.使用libevent库     源码那么多,该怎么分析从哪分析呢?一个好的方法就是先用起来,会用了,然后去看底层相应的源码,这样比较有条理,自上向下掌握.下面用libevent库写个程序,每隔1秒 ...

  5. libevent源码分析

    这两天没事,看了一下Memcached和libevent的源码,做个小总结. 1.入门 1.1.概述Libevent是一个用于开发可扩展性网络服务器的基于事件驱动(event-driven)模型的网络 ...

  6. libevent源码分析二--timeout事件响应

    libevent不仅支持io事件,同时还支持timeout事件与signal事件,这篇文件将分析libevent是如何组织timeout事件以及如何响应timeout事件. 1.  min_heap ...

  7. libevent源码分析一--io事件响应

    这篇文章将分析libevent如何组织io事件,如何捕捉事件的发生并进行相应的响应.这里不会详细分析event与event_base的细节,仅描述io事件如何存储与如何响应. 1.  select l ...

  8. libevent源码分析(一)

    分析libevent的源代码,我的想法的是先分析各种结构体,struct event_base.struct event,然后是event_base_new函数.event_new函数.event_a ...

  9. Libevent源码分析—event_init()

    下面开始看初始化event_base结构的相关函数.相关源码位于event.c event_init() 首先调用event_init()初始化event_base结构体 struct event_b ...

随机推荐

  1. 导出SharePoint2013用户及权限

    cls Add-PSSnapin Microsoft.SharePoint.PowerShell -ea 0 $site = New-Object Microsoft.SharePoint.SPSit ...

  2. 对STM32库函数中 assert 函数的认知

    > 本段代码取自 <stm32f4xx_gpio.c> > 可以看出进入函数第一件事就是做 assert 输入参数检查,参数合法后,根据参数做相应操作 /** * @brief ...

  3. 洛谷P3952 时间复杂度

    大毒瘤...... 时隔快半年我终于花了两个小时堪堪A掉这一题...果然我还没有准备好. 想法:用DFS模拟递归. 时间复杂度的处理:每层循环取max,然后相加. 最大难点:各种繁杂而令人发指的特判. ...

  4. [luogu2657][windy数]

    luogu2657 思路 数位dp,记录下上个位置的数,如果当前的数字与上个数字的差值小于2,就不再转移.还是要注意排除前导0.在记忆化的时候,全都是前导0的情况不能记忆化. 代码 #include& ...

  5. c# Bitmap byte[] Stream 文件相互转换

    //byte[] 转图片 publicstatic Bitmap BytesToBitmap(byte[] Bytes) { MemoryStream stream = null; try { str ...

  6. ADO.NET入门教程(五) 细说数据库连接池

    摘要 今天我要讲的是数据库连接池.说实话,我表示鸭梨很大.因为相比其他章节来说,连接池相对来说难理解一点.我要用最通俗的语句给大家讲明白,讲透彻却也不是一件很容易的事.但是,连接池又是非常重要的知识点 ...

  7. 类变量、实例变量--python

    1.类变量.实例变量概念 类变量: 类变量就是定义在类中,但是在函数体之外的变量.通常不使用self.变量名赋值的变量.类变量通常不作为类的实例变量的,类变量对于所有实例化的对象中是公用的. 实例变量 ...

  8. 重新打开Eclipse出现“An internal error has occurred. java.lang.NullPointerException”

    如果出现了上述的错误按照如下的3个步骤解决:1.首先关闭MyEclipse工作空间.2.然后删除工作空间下的. “/.metadata/.plugins/org.eclipse.core.runtim ...

  9. 浅谈js的数字格式

    除了正常我们常用的十进制(如5,8,12.123等),js还可以直接表示2.8.16进制 1.二进制 二进制是以0b开头 0b10; 2.八进制 八进制是以0开头 010: 3.十六进制 十六进制是以 ...

  10. P5002 专心OI - 找祖先

    P5002 专心OI - 找祖先 给定一棵有根树(\(n \leq 10000\)),\(M \leq 50000\) 次询问, 求以 \(x\) 为 \(LCA\) 的点对个数 错误日志: 看下面 ...