Libevent之struct event和struct event_base及其对应操作详细解释

分析一个开源库,首先应该关注其中重要的结构体,因为结构体往往是将一个事物抽象出来了。例如struct event结构体就是表示事件这一抽象个体。里面包含了事件应该有的所有操作。定义不同事件实质就是填充结构体里面对应的字段而已。结构体里面大量含有指针变量,那么可以说明这个指针变量一定指向堆中的某一段内存。存在着大量的动态分配内存操作。作者真的对内存了如指掌了,还是贯彻了一个道理,一切皆内存,全部都是通过指针取内存和存内存的效果。

1、struct event结构体

对应Roactor里面的事件注册模块。

//宏定义事件可能经过的几个状态,根据事件的不同状态就可以将其加入不同的链表中处理
#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已被初始化  
/* EVLIST_X_ Private space: 0x1000-0xf000 */
#define EVLIST_ALL  (0xf000 | 0x9f)


//宏定义事件类型
#define EV_TIMEOUT  0x01
#define EV_READ     0x02
#define EV_WRITE    0x04
#define EV_SIGNAL   0x08
#define EV_PERSIST  0x10    /* Persistant event */


struct event {
    TAILQ_ENTRY (event) ev_next;//用来链接IO时间
    TAILQ_ENTRY (event) ev_active_next;//用来链接就绪事件,实际插入对应的
    TAILQ_ENTRY (event) ev_signal_next;//用来链接信号事件
    unsigned int min_heap_idx;  /* for managing timeouts事件在堆中的下标 */

    struct event_base *ev_base;

    int ev_fd;//对于I/O事件,是绑定的文件描述符;对于signal事件,是绑定的信号
    short ev_events;//event关注的事件类型
    /*
        包含三个事件:IO事件:EV_READ和EV_WRITE
                    信号事件:EV_SIGNAL
                    定时器事件:EV_TIMEOUT    
    */
    short ev_ncalls;//事件就绪执行时,调用ev_callback的次数
    short *ev_pncalls;  /* Allows deletes in callback */

    struct timeval ev_timeout;//timout事件的超时值

    int ev_pri;     /* smaller numbers are higher priority 优先级*/

    void (*ev_callback)(int, short, void *arg);//回调函数
    void *ev_arg;//回调函数的参数

    int ev_res;     /* result passed to event callback 传递给回调函数 */
    int ev_flags;//表明事件当前处于何种状态
    /*
        依据事件处于不同状态将其加入不同的链表中。
    */
}

2、Libevent管理事件

Libevent之struct event和struct event_base及其对应操作详细解释_第1张图片

每次当有事件event转变为就绪状态时,libevent就会把它加入到对应优先级的active_event_list[priority]链表中,其中priority是event的优先级;接着libevent会根据自己的调度策略选择就绪事件,调用其cb_callback()函数执行事件处理:并根据就绪的句柄和事件类型填充cb_callback函数的参数。上图很好的说明了事件在几个队列中是怎么移动的。这是理解libevent事件管理的关键,然后再深入代码就很容易看懂了,就是根据事件的状态,将其添加到不同的队列,然后当epoll_wait返回某些事件就绪时候,那么就根据其优先级加入对应的就绪链表,然后libevent在就绪列表上根据优先级一个一个调用对应的回调函数。过程没有那么复杂,稍后我们仔细研究研究代码具体是怎么实现的,注意里面的一些实现细节。

3、struct event_base结构体

对于Reactor里面的IO复用机制模块。

TAILQ_HEAD (event_list, event);//结构体定义

struct eventop {//存储底层IO复用策略的结构体
    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;
};


struct event_base {
    const struct eventop *evsel;//底层具体I/O demultiplex操作函数集
    void *evbase;//存储IO复用返回的文件描述符相关 evbase
    int event_count;        /* counts number of total events总的事件数量 */
    int event_count_active; /* counts number of active events 就绪事件数量*/

    int event_gotterm;      /* Set to terminate loop */
    int event_break;        /* Set to terminate loop immediately */

    /* active event management */
    struct event_list **activequeues;//指针数组 ,就绪事件链表数组,每个元素防止一个链表头部
    int nactivequeues;//就绪事件队列个数,指针数组维度,也就是优先级个数

    /* signal handling info */
    struct evsignal_info sig;//用于管理信号

    struct event_list eventqueue;//注册事件队列
    struct timeval event_tv;

    struct min_heap timeheap;//管理定时器的小根堆

    struct timeval tv_cache;//记录时间缓存
}

此结构体存储了Reactor操作所需要的全部信息。

  • evsel:常struct eventop结构体指针,指向封装了底层IO复用机制的struct eventop结构体。Linux上面封装了epoll作为复用机制,struct eventop里面成员存储对应的函数指针,最后在event_base_new,初始化evsel,将其指向对应平台的复用机制。这是回调函数很好的应用,通过回调函数,实现统一的接口。
  • evbase:例如Linux上,epoll初始化后,会返回对应的epoll实例的文件描述符,在以后使用epoll函数的时候,必须使用该文件描述符,所以event_base里面一样需要存储这个信息,将这个信息的地址就赋给evbase,使得以后需要的时候,可以直接通过指针索引对应的信息。这里全部都是指针,那么可以看出,后面这些指针指向的内存,全部都是动态分配的。
  • activequeues和nactivequeues:activequeues是指针数组的首地址,nactivequeues表示指针数组的维度。因为Libevent支持就绪事件优先级操作。假如最高优先级是9,那么Libevent将创建9个就绪链表,将不同优先级的就绪事件,链接到对应的链表中,然后在调度策略里面按照优先级调用对应事件的回调函数,策略很简单。
  • timeheap:

你可能感兴趣的:(Libevent源代码分析)