libevent源码深度剖析六

——初见事件处理框架
张亮

前面已经对libevent的事件处理框架和event结构体做了描述,现在是时候剖析libevent对事件的详细处理流程了,本节将分析
libevent的事件处理框架event_base和libevent注册、删除事件的具体流程,可结合前一节libevent对event的管理。

1 事件处理框架-event_base

回想Reactor模式的几个基本组件,本节讲解的部分对应于Reactor框架组件。在libevent中,这就表现为event_base结构体,结构体声明如下,它位于event-internal.h文件中:

  1. struct event_base {
  2. const struct eventop *evsel;
  3. void *evbase; 
  4. int event_count;  /* counts number of total events */
  5. int event_count_active; /* counts number of active events */
  6. int event_gotterm;  /* Set to terminate loop */
  7. int event_break;  /* Set to terminate loop immediately */
  8. /* active event management */
  9. struct event_list **activequeues;
  10. int nactivequeues;
  11. /* signal handling info */
  12. struct evsignal_info sig;
  13. struct event_list eventqueue;
  14. struct timeval event_tv;
  15. struct min_heap timeheap;
  16. struct timeval tv_cache;
  17. };

下面详细解释一下结构体中各字段的含义。
1)evsel和evbase这两个字段的设置可能会让人有些迷惑,这里你可以把evsel和evbase看作是类和静态函数的关系,比如添加事件时的调
用行为:evsel->add(evbase, ev),实际执行操作的是evbase;这相当于class::add(instance,
ev),instance就是class的一个对象实例。
evsel指向了全局变量static const struct eventop *eventops[]中的一个;
前面也说过,libevent将系统提供的I/O demultiplex机制统一封装成了eventop结构;因此eventops[]包含了select、poll、kequeue和epoll等等其中的若干个全局实例对象。
evbase实际上是一个eventop实例对象;
先来看看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;
};
也就是说,在libevent中,每种I/O demultiplex机制的实现都必须提供这五个函数接口,来完成自身的初始化、销毁释放;对事件的注册、注销和分发。
比如对于epoll,libevent实现了5个对应的接口函数,并在初始化时并将eventop的5个函数指针指向这5个函数,那么程序就可以使用epoll作为I/O demultiplex机制了,这个在后面会再次提到。
2)activequeues是一个二级指针,前面讲过libevent支持事件优先级,因此你可以把它看作是数组,其中的元素activequeues[priority]是一个链表,链表的每个节点指向一个优先级为priority的就绪事件event。
3)eventqueue,链表,保存了所有的注册事件event的指针。
4)sig是由来管理信号的结构体,将在后面信号处理时专门讲解;
5)timeheap是管理定时事件的小根堆,将在后面定时事件处理时专门讲解;
6)event_tv和tv_cache是libevent用于时间管理的变量,将在后面讲到;
其它各个变量都能因名知意,就不再啰嗦了。

2 创建和初始化event_base

创建一个event_base对象也既是创建了一个新的libevent实例,程序需要通过调用event_init()(内部调用event_base_new函数执行具体操作)函数来创建,该函数同时还对新生成的libevent实例进行了初始化。
该函数首先为event_base实例申请空间,然后初始化timer mini-heap,选择并初始化合适的系统I/O 的demultiplexer机制,初始化各事件链表;
函数还检测了系统的时间设置,为后面的时间管理打下基础。

3 接口函数

前面提到Reactor框架的作用就是提供事件的注册、注销接口;根据系统提供的事件多路分发机制执行事件循环,当有事件进入“就绪”状态时,调用注册事件的回调函数来处理事件。
Libevent中对应的接口函数主要就是:

  1. int  event_add(struct event *ev, const struct timeval *timeout);
  2. int  event_del(struct event *ev);
  3. int  event_base_loop(struct event_base *base, int loops);
  4. void event_active(struct event *event, int res, short events);
  5. void event_process_active(struct event_base *base);

本节将按介绍事件注册和删除的代码流程,libevent的事件循环框架将在下一节再具体描述。
对于定时事件,这些函数将调用timer
heap管理接口执行插入和删除操作;对于I/O和Signal事件将调用eventopadd和delete接口函数执行插入和删除操作
(eventop会对Signal事件调用Signal处理接口执行操作);这些组件将在后面的内容描述。
1)注册事件
函数原型:
int event_add(struct event *ev, const struct timeval *tv)
参数:ev:指向要注册的事件;
tv:超时时间;
函数将ev注册到ev->ev_base上,事件类型由ev->ev_events指明,如果注册成功,ev将被插入到已注册链表中;如果tv不是NULL,则会同时注册定时事件,将ev添加到timer堆上;
如果其中有一步操作失败,那么函数保证没有事件会被注册,可以讲这相当于一个原子操作。这个函数也体现了libevent细节之处的巧妙设计,且仔细看程序代码,部分有省略,注释直接附在代码中。

  1. int event_add(struct event *ev, const struct timeval *tv)
  2. {
  3. struct event_base *base = ev->ev_base; // 要注册到的event_base
  4. const struct eventop *evsel = base->evsel;
  5. void *evbase = base->evbase; // base使用的系统I/O策略
  6. // 新的timer事件,调用timer heap接口在堆上预留一个位置
  7. // 注:这样能保证该操作的原子性:
  8. // 向系统I/O机制注册可能会失败,而当在堆上预留成功后,
  9. // 定时事件的添加将肯定不会失败;
  10. // 而预留位置的可能结果是堆扩充,但是内部元素并不会改变
  11. if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
  12. if (min_heap_reserve(&base->timeheap,
  13. 1 + min_heap_size(&base->timeheap)) == -1)
  14. return (-1);  /* ENOMEM == errno */
  15. }
  16. // 如果事件ev不在已注册或者激活链表中,则调用evbase注册事件
  17. if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
  18. !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
  19. res = evsel->add(evbase, ev);
  20. if (res != -1) // 注册成功,插入event到已注册链表中
  21. event_queue_insert(base, ev, EVLIST_INSERTED);
  22. }
  23. // 准备添加定时事件
  24. if (res != -1 && tv != NULL) {
  25. struct timeval now;
  26. // EVLIST_TIMEOUT表明event已经在定时器堆中了,删除旧的
  27. if (ev->ev_flags & EVLIST_TIMEOUT)
  28. event_queue_remove(base, ev, EVLIST_TIMEOUT);
  29. // 如果事件已经是就绪状态则从激活链表中删除
  30. if ((ev->ev_flags & EVLIST_ACTIVE) &&
  31. (ev->ev_res & EV_TIMEOUT)) {
  32. // 将ev_callback调用次数设置为0
  33. if (ev->ev_ncalls && ev->ev_pncalls) {
  34. *ev->ev_pncalls = 0;
  35. }
  36. event_queue_remove(base, ev, EVLIST_ACTIVE);
  37. }
  38. // 计算时间,并插入到timer小根堆中
  39. gettime(base, &now);
  40. evutil_timeradd(&now, tv, &ev->ev_timeout);
  41. event_queue_insert(base, ev, EVLIST_TIMEOUT);
  42. }
  43. return (res);
  44. }
  45. event_queue_insert()负责将事件插入到对应的链表中,下面是程序代码;
  46. event_queue_remove()负责将事件从对应的链表中删除,这里就不再重复贴代码了;
  47. void event_queue_insert(struct event_base *base, struct event *ev, int queue)
  48. {
  49. // ev可能已经在激活列表中了,避免重复插入
  50. if (ev->ev_flags & queue) {
  51. if (queue & EVLIST_ACTIVE)
  52. return;
  53. }
  54. // ...
  55. ev->ev_flags |= queue; // 记录queue标记
  56. switch (queue) {
  57. case EVLIST_INSERTED: // I/O或Signal事件,加入已注册事件链表
  58. TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next);
  59. break;
  60. case EVLIST_ACTIVE: // 就绪事件,加入激活链表
  61. base->event_count_active++;
  62. TAILQ_INSERT_TAIL(base->activequeues[ev->ev_pri], ev, ev_active_next);
  63. break;
  64. case EVLIST_TIMEOUT: // 定时事件,加入堆
  65. min_heap_push(&base->timeheap, ev);
  66. break;
  67. }
  68. }

2)删除事件:
函数原型为:int  event_del(struct event *ev);
该函数将删除事件ev,对于I/O事件,从I/O 的demultiplexer上将事件注销;对于Signal事件,将从Signal事件链表中删除;对于定时事件,将从堆上删除;
同样删除事件的操作则不一定是原子的,比如删除时间事件之后,有可能从系统I/O机制中注销会失败。

  1. int event_del(struct event *ev)
  2. {
  3. struct event_base *base;
  4. const struct eventop *evsel;
  5. void *evbase;
  6. // ev_base为NULL,表明ev没有被注册
  7. if (ev->ev_base == NULL)
  8. return (-1);
  9. // 取得ev注册的event_base和eventop指针
  10. base = ev->ev_base;
  11. evsel = base->evsel;
  12. evbase = base->evbase;
  13. // 将ev_callback调用次数设置为
  14. if (ev->ev_ncalls && ev->ev_pncalls) {
  15. *ev->ev_pncalls = 0;
  16. }
  17. // 从对应的链表中删除
  18. if (ev->ev_flags & EVLIST_TIMEOUT)
  19. event_queue_remove(base, ev, EVLIST_TIMEOUT);
  20. if (ev->ev_flags & EVLIST_ACTIVE)
  21. event_queue_remove(base, ev, EVLIST_ACTIVE);
  22. if (ev->ev_flags & EVLIST_INSERTED) {
  23. event_queue_remove(base, ev, EVLIST_INSERTED);
  24. // EVLIST_INSERTED表明是I/O或者Signal事件,
  25. // 需要调用I/O demultiplexer注销事件
  26. return (evsel->del(evbase, ev));
  27. }
  28. return (0);
  29. }

4 小节
分析了event_base这一重要结构体,初步看到了libevent对系统的I/O demultiplex机制的封装event_op结构,并结合源代码分析了事件的注册和删除处理,下面将会接着分析事件管理框架中的主事件循环部分。

libevent源码深度剖析六的更多相关文章

  1. libevent 源码深度剖析十三

    libevent 源码深度剖析十三 —— libevent 信号处理注意点 前面讲到了 libevent 实现多线程的方法,然而在多线程的环境中注册信号事件,还是有一些情况需要小心处理,那就是不能在多 ...

  2. libevent源码深度剖析十二

    libevent源码深度剖析十二 ——让libevent支持多线程 张亮 Libevent本身不是多线程安全的,在多核的时代,如何能充分利用CPU的能力呢,这一节来说说如何在多线程环境中使用libev ...

  3. libevent源码深度剖析十一

    libevent源码深度剖析十一 ——时间管理 张亮 为了支持定时器,Libevent必须和系统时间打交道,这一部分的内容也比较简单,主要涉及到时间的加减辅助函数.时间缓存.时间校正和定时器堆的时间值 ...

  4. libevent源码深度剖析十

    libevent源码深度剖析十 ——支持I/O多路复用技术 张亮 Libevent的核心是事件驱动.同步非阻塞,为了达到这一目标,必须采用系统提供的I/O多路复用技术,而这些在Windows.Linu ...

  5. libevent源码深度剖析九

    libevent源码深度剖析九 ——集成定时器事件 张亮 现在再来详细分析libevent中I/O事件和Timer事件的集成,与Signal相比,Timer事件的集成会直观和简单很多.Libevent ...

  6. libevent源码深度剖析八

    libevent源码深度剖析八 ——集成信号处理 张亮 现在我们已经了解了libevent的基本框架:事件管理框架和事件主循环.上节提到了libevent中I/O事件和Signal以及Timer事件的 ...

  7. libevent源码深度剖析七

    libevent源码深度剖析七 ——事件主循环 张亮 现在我们已经初步了解了libevent的Reactor组件——event_base和事件管理框架,接下来就是libevent事件处理的中心部分 — ...

  8. libevent源码深度剖析五

    libevent源码深度剖析五 ——libevent的核心:事件event 张亮 对事件处理流程有了高层的认识后,本节将详细介绍libevent的核心结构event,以及libevent对event的 ...

  9. libevent源码深度剖析四

    libevent源码深度剖析四 ——libevent源代码文件组织 1 前言 详细分析源代码之前,如果能对其代码文件的基本结构有个大概的认识和分类,对于代码的分析将是大有裨益的.本节内容不多,我想并不 ...

随机推荐

  1. android知识点大总结

    1.掌握Android编程的基本概念与要点,Android SDK及其开发环境搭建.Android项目结构分析.2.Android 应用设计模式.文件系统.3.文件形式的数据存储与访问.SDCard卡 ...

  2. 前端之jQuery03 插件

    jQuery.fn.extend(object) 扩展 jQuery 元素集来提供新的方法(通常用来制作插件) 增加两个插件方法: // jQuery 扩展机制 // 自己扩展两个方法 // 把我这个 ...

  3. myeclipse三个地方的java版本统一

    1 java build path 2 java compiler 3 Myeclipse -> project facets

  4. UltraEdit工具安装和注册机破解

    1.关闭网络连接(或者直接拔掉网线). 2.打开UltraEdit软件,稍等片刻会出现提示你你使用的是试用版本的窗口.如下图,点击“注册”. 3.填写许可证id和密码.许可证id可任意填写,不过根据经 ...

  5. 1138. Postorder Traversal (25)

    Suppose that all the keys in a binary tree are distinct positive integers. Given the preorder and in ...

  6. 剑指offer-第三章高质量代码(反转链表)

    题目:定义一个函数,输入一个链表的头节点,反转该链表并输出反转链表的头节点. 思路:对一个链表反转需要三个指针操作来保证链表在反转的过程中保证不断链,给链表一个行动指针pNode,对pNode指向的节 ...

  7. ul li 水平居中

    li的float:left方法显然有一个问题,就是无法居中(水平),只能使用padding-left或margin-right的方法方法来固定其居中.但这样可能在宽屏与窄屏的显示不一致.使用这种方法主 ...

  8. POJ3764,BZOJ1954 The xor-longest Path

    题意 In an edge-weighted tree, the xor-length of a path p is defined as the xor sum of the weights of ...

  9. merge into报错ORA-00926、ORA-38014

    今天用ibatis写个插入操作,为了兼容修改想使用 merge into语句,以便重复插入时直接 update,具体语句如下: <insert id="wlf"> ME ...

  10. 使用XV-11激光雷达做hector_slam

    大家在学习ROS中不可避免需要使用激光雷达,高精地图.实时定位以及障碍物检测等多项技术,而这些技术都离不开光学雷达的支持,但是呢雷达这真是太贵了,大部分人是负担不起(实验室.研究所土豪可以略过),但是 ...