libevent源码详解(三)数据结构之event、event_base

  • sturct event
    • 事件定义
    • 事件类型
    • 事件状态
    • 三个链表
  • event_base
    • Reactor模式
      • Reactor模式组成
        • handleEventHandler事件源事件处理程序
        • Synchrounous Event Demultiplexer同步事件多路复用器
        • Reactor反应器
      • Reactor模式处理流程
    • event_base定义
    • libevent对Reactor反应器接口的实现

libevent中最重要的两个数据结构莫过于event 跟event_base。弄明白这两个数据结构基本上也就弄明白libevent了。

sturct event

事件定义

libevent中,不论是IO事件,timeout事件,还是signal事件,都是用struct event来描述。具体的实现代码如下。

struct event {
    TAILQ_ENTRY(event) ev_active_next;
    TAILQ_ENTRY(event) ev_next;
    /* 用来描述timeout事件 */
    union {
        TAILQ_ENTRY(event) ev_next_with_common_timeout;
        int min_heap_idx;
    } ev_timeout_pos;
    evutil_socket_t ev_fd; //对IO事件而言,为文件描述符。对信号事件而言,为绑定的信号。

    struct event_base *ev_base;//绑定的event_base

    union {
        /* 用来描述IO事件 */
        struct {
            TAILQ_ENTRY(event) ev_io_next;
            struct timeval ev_timeout;
        } ev_io;

        /* 用来描述signal事件 */
        struct {
            TAILQ_ENTRY(event) ev_signal_next;
            short ev_ncalls;

            short *ev_pncalls;
        } ev_signal;
    } _ev;

    short ev_events;//事件类型
    short ev_res;   //传递给回调函数的结果    
    short ev_flags; //事件的状态标志
    ev_uint8_t ev_pri;  //事件的优先级。值越小优先级越高
    ev_uint8_t ev_closure;
    struct timeval ev_timeout;

    /* allows us to adopt for different types of events */
    void (*ev_callback)(evutil_socket_t, short, void *arg);//事件的回调函数
    void *ev_arg;                                          //事件回调函数参数
};

我用的代码是2.0.22-stable版。这个版本中的代码比之前比较旧的版本实现得更清晰。

事件类型

事件类型由ev_events成员描述,总共有三种:IO事件、timeout事件、signal事件.EV_READ和EV_WRITE都是用来描述IO事件。

#define EV_TIMEOUT  0x01
#define EV_READ     0x02
#define EV_WRITE    0x04
#define EV_SIGNAL   0x08

事件状态

#define EVLIST_TIMEOUT 0x01 // event在time堆中  
#define EVLIST_INSERTED 0x02 // event在已注册事件链表中  
#define EVLIST_SIGNAL 0x04 // 未见使用  
#define EVLIST_ACTIVE 0x08 // event在激活链表中  
#define EVLIST_INTERNAL 0x10 // 内部使用标记  
#define EVLIST_INIT     0x80 // event已被初始化  

三个链表

我们可以看到描述事件的结构体中有两个尾队列:ev_active_next,ev_next。ev_next用来描述已经注册的事件链表。ev_active_next用来描述激活链表。

//

如果事件类型是IO事件,那么事件还会处于ev_io_next队列。如果事件类型是signal事件,那么事件还会处于ev_signal_next队列。如果是timeout事件,则有两种可能:要不处于ev_next_with_common_timeout队列,要不处于最小堆。libevent默认将timeout事件放在最小堆中。如果timeout事件数量很多,那么可以将事件放在ev_next_with_common_timeout队列,那样处理起来速度会更快。

event_base

Reactor模式

Reactor模式组成

Reactor模式大致由如下几个部分组成:handle/EventHandler事件源/事件处理接口、Synchrounous Event Demultiplexer事件多路复用器、Reactor反应器、Concrete Event handler特定事件处理接口。盗用一下别人的图。

1.handle/EventHandler事件源/事件处理程序

handle即事件源。libevent中的信号、文件描述符都是handle。

2.Synchrounous Event Demultiplexer同步事件多路复用器

同步事件多路复用器实际上是内核实现的一个函数。这个函数会阻塞等待已经注册事件集合。当有注册事件发生时,事件进入就绪状态,复用器会通知反应器。反应器收到通知后就可以调用事件的回调函数处理事件。linux系统下的select, poll,epoll都是Demultiplexer。libevent把这些复用器叫做backend,并用struct eventop来描述。

3.Reactor反应器

反应器的功能如下:

  • 运行事件循环
  • 当有事件(handle)就绪时,调用事件的回调函数
  • 注册/删除事件

Reactor模式处理流程

  • 1.注册事件/事件处理接口到Reactor中
  • 2.调用event_loop()进入循环,等待事件发生
  • 3.事件发生,Demultiplexer返回,Reactor调用回调函数进行处理

event_base定义

struct event_base {
  const struct eventop *evsel;
  void *evbase;

  struct common_timeout_list **common_timeout_queues;
  struct min_heap timeheap;//管理timeout事件的最小堆
  struct event_list *activequeues;//就绪队列。
  struct event_list eventqueue;//所有调用event_add接口的事件都会被加入到这个队列,调用后event状态为EVLIST_INSERTED.

  struct event_signal_map sigmap;
  struct event_io_map io;

  struct timeval event_tv;
  struct timeval tv_cache;
};
  • evsel :reactor模式的demultiplexer。libevent用struct eventop统一封装了demultiplexer,并强制不同系统的demultiplexer统一提供init、add、del、dispatch、dealloc接口。详见struct eventop的定义。
  • ​io/sigmap
    linux下,这两个的数据成员结构为
struct event_signal_map {
    void **entries;
    int nentries;
};

entries是一个数组。如果事件类型为信号事件,则数组成员类型为struct evmap_signal。如果事件类型为IO事件,则数组成员为struct evmap_io。这两个成员的数据结构如下:

struct evmap_io {
    struct event_list events;
    ev_uint16_t nread;
    ev_uint16_t nwrite;
};

struct evmap_signal {
    struct event_list events;
};

Q: event_base中为什么会存在这两个数据成员?
A: 因为libevent允许将同一个fd/signal映射到不同的事件,即fd/signal—->struct event*的映射。struct event_signal_map中的数组entries的索引就是fd/signal的值,对应的事件struct event则存放在evmap_io/evmap_signal中的尾队列events中。在windows中,文件描述符是一个比较大的值,不适合放到event_signal_map结构的数组中,libevent会先将fd值做一个hash然后再用链表定址存放在struct event_map_entry的尾队列中。linux下由于遵循POSIX标准的文件描述符是从0开始递增的,一般都不会太大,可以用event_signal_map中的数组来映射fd的值。windows下fd映射到struct event的具体实现分析可以参考这篇:Libevent源码分析—–event_io_map哈希表

libevent对Reactor反应器接口的实现

每个event_base就相当于一个Reactor框架。
上面我们介绍了Reactor模式中reactor反应器的三个主要功能:注册/注销事件、运行事件主循环、等待事件就绪然后调用事件处理函数。在libevent中实现这些功能的对应接口如下:

  • event_add(struct event *ev, const struct timeval *tv) 注册事件
  • event_del(struct event *ev) 注销事件
  • event_base_loop(struct event_base *) 运行事件主循环

你可能感兴趣的:(源码解析)