前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >libevent源码深度剖析(六) 初见事件处理框架

libevent源码深度剖析(六) 初见事件处理框架

作者头像
范蠡
发布2018-07-25 16:06:40
1.1K0
发布2018-07-25 16:06:40
举报

系列目录

(1)libevent源码深度剖析一 序 (2)libevent源码深度剖析二 Reactor模式 (3)libevent源码深度剖析三 libevent基本使用场景和事件流程 (4)libevent源码深度剖析四 libevent源代码文件组织 (5)libevent源码深度剖析五 libevent的核心:事件event (6)libevent源码深度剖析六 初见事件处理框架 (7)libevent源码深度剖析七 事件主循环 (8)libevent源码深度剖析八 集成信号处理 (9)libevent源码深度剖析九 集成定时器事件 (10)libevent源码深度剖析十 支持I/O多路复用技术 (11)libevent源码深度剖析十一 时间管理 (12)libevent源码深度剖析十二 让libevent支持多线程 (13)libevent源码深度剖析十三 libevent信号处理注意点

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

1.事件处理框架-event_base

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

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

下面详细解释一下结构体中各字段的含义。

  1. evsel和evbase这两个字段的设置可能会让人有些迷惑,这里你可以把evsel和evbase看作是类和静态函数的关系,比如添加事件时的调用行为:evsel->add(evbase, ev),实际执行操作的是evbase;这相当于class::add(instance, ev),instance就是class的一个对象实例。
  2. evsel指向了全局变量static const struct eventop *eventops[]中的一个; 前面也说过,libevent将系统提供的I/O demultiplex机制统一封装成了eventop结构;因此eventops[]包含了select、poll、kequeue和epoll等等其中的若干个全局实例对象。
  3. evbase实际上是一个eventop实例对象; 先来看看eventop结构体,它的成员是一系列的函数指针, 在event-internal.h文件中: 1struct eventop { 2 const char *name; 3 // 初始化 4 void *(*init)(struct event_base *); 5 // 注册事件 6 int (*add)(void *, struct event *); 7 // 删除事件 8 int (*del)(void *, struct event *); 9 // 事件分发 10 int (*dispatch)(struct event_base *, void *, struct timeval *); 11 // 注销,释放资源 12 void (*dealloc)(struct event_base *, void *); 13 /* set if we need to reinitialize the event base */ 14 int need_reinit; 15}; 也就是说,在libevent中,每种I/O demultiplex机制的实现都必须提供这五个函数接口,来完成自身的初始化、销毁释放;对事件的注册、注销和分发。 比如对于epoll,libevent实现了5个对应的接口函数,并在初始化时并将eventop的5个函数指针指向这5个函数,那么程序就可以使用epoll作为I/O demultiplex机制了,这个在后面会再次提到。
  4. activequeues是一个二级指针,前面讲过libevent支持事件优先级,因此你可以把它看作是数组,其中的元素activequeues[priority]是一个链表,链表的每个节点指向一个优先级为priority的就绪事件event。
  5. eventqueue,链表,保存了所有的注册事件event的指针。
  6. sig是由来管理信号的结构体,将在后面信号处理时专门讲解;
  7. timeheap是管理定时事件的小根堆,将在后面定时事件处理时专门讲解;
  8. 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中对应的接口函数主要就是:

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

本节将按介绍事件注册和删除的代码流程,libevent的事件循环框架将在下一节再具体描述。

  • 对于定时事件,这些函数将调用timer heap管理接口执行插入和删除操作;
  • 对于I/O和Signal事件将调用eventopadd和delete接口函数执行插入和删除操作(eventop会对Signal事件调用Signal处理接口执行操作);

这些组件将在后面的内容描述。

1)注册事件 函数原型:

1int event_add(struct event *ev, const struct timeval *tv)

参数:ev:指向要注册的事件; tv:超时时间;

e函数将ev注册到ev->ev_base上,事件类型由ev->ev_events指明,

  • 如果注册成功,v将被插入到已注册链表中;
  • 如果tv不是NULL,则会同时注册定时事件,将ev添加到timer堆上;

如果其中有一步操作失败,那么函数保证没有事件会被注册,可以讲这相当于一个原子操作。这个函数也体现了libevent细节之处的巧妙设计,且仔细看程序代码,部分有省略,注释直接附在代码中。

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

2)删除事件: 函数原型为:

1int event_del(struct event *ev);

该函数将删除事件ev

  • 对于I/O事件,从I/O 的demultiplexer上将事件注销;
  • 对于Signal事件,将从Signal事件链表中删除;
  • 对于定时事件,将从堆上删除;

同样删除事件的操作则不一定是原子的,比如删除时间事件之后,有可能从系统I/O机制中注销会失败。

 1int 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         // 从对应的链表中删除
19         if (ev->ev_flags & EVLIST_TIMEOUT)
20              event_queue_remove(base, ev, EVLIST_TIMEOUT);
21         if (ev->ev_flags & EVLIST_ACTIVE)
22              event_queue_remove(base, ev, EVLIST_ACTIVE);
23         if (ev->ev_flags & EVLIST_INSERTED) {
24              event_queue_remove(base, ev, EVLIST_INSERTED);
25          // EVLIST_INSERTED表明是I/O或者Signal事件,
26          // 需要调用I/O demultiplexer注销事件
27              return (evsel->del(evbase, ev));
28     }
29     return (0);
30}

4.小结

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-05-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 高性能服务器开发 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.事件处理框架-event_base
  • 2.创建和初始化event_base
  • 3.接口函数
  • 4.小结
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档