libevent

  1. 句柄:I/O框架库处理的对象,即I/O事件,信号和定时事件称为统一事件源,一个事件源通常和一个句柄绑定在一起,句柄的作用是:当内核检测到就绪事件时,他通过句柄来通知应用程序这一事件,I/O事件对应的句柄是文件描述符,信号事件对应的句柄是信号值.
  2. 事件多路分发器:事件的到来是随机的,异步的,无法预测何时到来一个客户连接或收到一个信号,所以应用程序需要循环等待并处理这件事,这就是事件循环,时间循环中一般采用I/O复用.
  3. 事件处理器和具体事件处理器:事件处理器执行对应的逻辑业务.通常包含一个或多个hand_event回调函数,这些回调函数放在事件循环中被执行,I/O框架库中事件处理器通常是一个接口,用户继承它并实现,即成为具体事件处理器,因此事件处理器的回调函数一般声明为虚函数;事件处理器还包含一个get_handle它返回与事件处理器关联的句柄,当事件多路分发器检测到有事件发生时,他通过句柄来通知应用程序,必须将事件处理器和句柄绑定,才能在事件发生时获得正确的事件处理器.

 

  1. handle——事件源:Linux上是文件描述符,Windows上就是Socket或者Handle了,这里统一称为“句柄集”;程序在指定的句柄上注册关心的事件,在libevent中有三种类型的事件:定时器事件(time event)、信号事件(signal event)和I/O事件。
  2. event demultiplexer——事件多路分发机制:由操作系统提供的I/O多路复用机制,比如select和epoll。程序首先将其关心的句柄(事件源)及其事件注册到event demultiplexer上;当有事件到达时,event demultiplexer会发出通知事件处理程序“在已经注册的句柄集中,一个或多个句柄的事件已经就绪”;程序收到通知后,就可以在非阻塞的情况下对事件进行处理了。对应到libevent中,依然是select、poll、epoll等,但是libevent使用结构体eventop进行了封装,以统一的接口来支持这些I/O多路复用机制,达到了对外隐藏底层系统机制的目的
  3. Reactor——反应器:Reactor,是事件管理的接口,内部使用event demultiplexer注册、注销事件;并运行事件循环,当有事件进入“就绪”状态时,调用注册事件的回调函数处理事件。对应到libevent中,就是event_base结构体。
  4. Event Handler——事件处理程序:事件处理程序提供了一组接口,每个接口对应了一种类型的事件,供Reactor在相应的事件发生时调用,执行相应的事件处理。通常它会绑定一个有效的句柄。对应到libevent中,就是event结构体。

  libevent安装:下载地址:http://libevent.org/

1.解压文件:tar -zxvf libevent-2.1.8-stable.tar.gz

2.解压后进入目录,进行配置,把库安装到/usr目录下:./configure --prefix=/usr

3.编译安装:sudo make,sudo make install

libevnet主要逻辑

/*************************************************************************
    > File Name: le.cpp
    > Author: Chen Tianzeng
    > Mail: 971859774@qq.com 
    > Created Time: 2019年05月02日 星期四 11时30分56秒
 ************************************************************************/

#include <iostream>
#include <event.h>
#include <signal.h>
using namespace std;

void signal_cb(int fd,short event,void *arg)
{
    struct event_base *base=(struct event_base *)arg;
    struct timeval delay={2,0};
    cout<<"caught an interrupt signal;exiting in two seconds"<<endl;
    event_base_loopexit(base,&delay);
}
//超时回调函数
void time_cb(int fd,short event,void *argc)
{
    cout<<"timeout"<<endl;
}

int main()
{
    //除了调用event_init也可以调用event_base_new
    struct event_base *base=event_init();
    //创建一个信号事件
    struct event *signal_event=evsignal_new(base,SIGINT,signal_cb,base);
    //向事件循环注册
    event_add(signal_event,nullptr);
    
    struct timeval tv={1,0};
    //创建一个定时器事件
    struct event *timeout_event=evtimer_new(base,time_cb,nullptr);
    event_add(timeout_event,&tv);

    //事件分发
    event_base_dispatch(base);
    
    //释放相关资源
    event_free(timeout_event);
    event_free(signal_event);
    event_base_free(base);
    return 0;
}

 编译时加上linker :-levent

libevent简单的服务器

#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include "event2/event.h"
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#define MAXBUFLEN 1024
 
void do_accept(evutil_socket_t fd, short event, void *arg);
void do_read(evutil_socket_t fd, short event, void *arg);
void do_write(evutil_socket_t fd, short event, void *arg);
 
typedef struct _client 
{
    char buf[MAXBUFLEN];
    //已经写到的位置
    int n_written;
    //需要写到的位置
    int n_write_to;
    //存储偏移量
    int pos;
    //读事件
    struct event * read_event;
    //写事件
    struct event * write_event;
}client;
 
client * alloc_client(struct event_base * base,int fd)
{
    client *cli = (client *)malloc(sizeof(client));
    cli->pos = cli->n_written = cli->n_write_to = 0;
    cli->read_event = event_new(base, fd, EV_READ | EV_PERSIST, do_read, cli);
    cli->write_event = event_new(base, fd, EV_WRITE | EV_PERSIST, do_write, cli);
    return cli;
}
 
void free_client(client * cli)
{
    //event_free内部将会调用event_del从Reactor中取消注册相关事件
    event_free(cli->read_event);
    event_free(cli->write_event);
    free(cli);
}
 
void do_write(evutil_socket_t fd, short events, void *arg)
{
    printf("write back to client.\n");
    ssize_t len;
    client *cli = (client *)arg;
 
    while(cli->n_written < cli->n_write_to)
    {
        len = send(fd, cli->buf + cli->n_written, cli->n_write_to - cli->n_written, 0);
        if(len < 0)
        {
            if(errno == EAGAIN)
                return;
            close(fd);
            free_client(cli);
        }
        cli->n_written += len;
    }
 
    if(cli->n_written == cli->pos)
        cli->n_write_to = cli->pos = cli->n_written = 0;
    event_del(cli->write_event);
}
void do_read(evutil_socket_t fd, short events, void *arg)
{
    char buf[MAXBUFLEN] = {0};
    ssize_t result = 0;
    int i = 0;
    client * cli = (client *)arg;
    while(1)
    {
        result = recv(fd, buf, MAXBUFLEN, 0);
        if(result <= 0)
            break;
        printf("get something\n");
        for(i = 0; i < result; i++)
        {
            cli->buf[cli->pos++] = buf[i];
            if(buf[i] == '\n')
            {
                cli->n_write_to = cli->pos;
                event_add(cli->write_event, NULL);
            }
        }
    }
 
    if(result == 0) //对端已关闭连接
    {
        printf("连接已关闭\n");
        close(fd);
        free_client(cli);
    }
    else if(result <0)
    {
        if(errno == EAGAIN)
            return;
        perror("recv");
        close(fd);
        free_client(cli);
    }
}
void do_accept(evutil_socket_t fd, short events, void *arg)
{
    printf("get a new connection.\n");
    struct event_base* base = (struct event_base *)arg;
 
    struct sockaddr_storage client_addr;
    socklen_t len = 0;
    int cli_fd = 0;
 
    if((cli_fd = accept(fd, (struct sockaddr *)&client_addr, &len)) < 0)
    {
        perror("accept");
        return;
    }
 
    evutil_make_socket_nonblocking(cli_fd);
 
    //建立新连接成功,将该句柄加入事件循环
    client * cli = alloc_client(base, cli_fd);
    event_add(cli->read_event, NULL);
}
 
int main(int argc, char **argv)
{
    struct sockaddr_in serv;
    serv.sin_family = AF_INET;
    serv.sin_addr.s_addr = INADDR_ANY;
    serv.sin_port = htons(9211);
 
    int fd = socket(PF_INET, SOCK_STREAM, 0);
    if(fd < 0)
    {
        perror("socket");
        return -1;
    }
 
    int res;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &res, sizeof(res));
    //将监听socket设置为非阻塞
    evutil_make_socket_nonblocking(fd);
 
    if(bind(fd, (struct sockaddr *)&serv, sizeof(serv)) < 0)
    {
        perror("bind");
        return -1;
    }
 
    if(listen(fd, 5) < 0)
    {
        perror("listen");
        return -1;
    }
 
    //创建一个Reactor
    struct event_base *  base = event_base_new();
    //为监听句柄创建一个持久化的可读事件, 回调函数为do_accept,当fd可读时,将会调用do_accept
    struct event * listen_event = event_new(base, fd, EV_READ | EV_PERSIST, do_accept, (void *)base);
    //将事件处理器注册到事件循环
    event_add(listen_event, NULL);
    //开始事件循环
    event_base_dispatch(base);
    //释放Reactor
    event_base_free(base);
}
View Code
  1. 调用event_base创建一个event_base对象,一个event_base相当于一个Reactor实例
    struct event_base *event_init(void);//注:有些早期版本返回值为void *
    struct event_base *event_base_new(void);
  2. 创建具体事件处理器,并设置他们从属的Reactor实例,evsignal_new和evtimer_new创建信号事件处理器和定时事件处理器
    /*
     调用event_new创建具体的事件处理器event并设置从属的event_base实例,同时设置关心的事件和事件触发后的回调函数,若为信号事件,可调用evsignal_new创建;若为定时器事件,可调用evtimer_new创建。evsignal_new和evtimer_new实际上是两个宏定义
    实际依旧是调用了event_new函数
    */ #define evtimer_new(b, cb, arg) event_new((b), -1, 0, (cb), (arg)) #define evsignal_new(b, x, cb, arg) \ event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg)) struct event * event_new(struct event_base *base, evutil_socket_t fd, short events, void (*cb)(evutil_socket_t, short, void *), void *arg); /* 第一个参数base表示新建事件从属的event_base实例;第二个参数表示fd表示关心的句柄,可以是文件句柄也可以是信号值;第三个参数表示关心的事件,包括EV_READ(IO读)、EV_WRITE(IO写)、EV_SIGNAL(信号)等事件。
    第四个参数表示事件触发后,执行哪个回调函数;第五个参数表示执行回调函数cb时的参数
    */
  3. 调用event_add,将事件处理器添加到注册队列中,并将事件处理其对应的事件添加到事件多路分发器中,event_add相当于Reactor中的register_add,在注册事件时,主要做两件事:将事件注册到内核事件注册表中,将事件添加到对应的队列或者堆中;需要注意的是,I/O事件和信号事件会被添加到注册事件链表中,如果它们是就绪事件,则直接添加到已注册事件链表中;定时事件则被添加到管理定时事件的小顶堆中
    int event_add(struct event *ev, const struct timeval *tv); 
    //ev是需要注册的事件,若tv不为空,则表示该事件是个定时器事件,tv为超时时间
    
        /*这个函数也是库中对外接口的很重要的一个,将event添加到监听队列,(这里的监听是监听 
        这个套接字上的事件)*/  
        int event_add(struct event *ev, const struct timeval *tv)  
        {  
            struct event_base *base = ev->ev_base;//要注册到的event_base  
            const struct eventop *evsel = base->evsel;  
            void *evbase = base->evbase;  
            int res = 0;  
    
            event_debug((  
                 "event_add: event: %p, %s%s%scall %p",  
                 ev,  
                 ev->ev_events & EV_READ ? "EV_READ " : " ",  
                 ev->ev_events & EV_WRITE ? "EV_WRITE " : " ",  
                 tv ? "EV_TIMEOUT " : " ",  
                 ev->ev_callback));  
    
            assert(!(ev->ev_flags & ~EVLIST_ALL));  
             /*发现这个event是定时事件,那么就将此事件添加到小顶堆中,这里并没有直接添加,而是 
             为添加做准备,在小顶堆上申请一个空间*/  
            if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {//<span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;">EVLIST_TIMEOUT表示ev已经在定时器事件堆中</span>  
                if (min_heap_reserve(&base->timeheap,  
                    1 + min_heap_size(&base->timeheap)) == -1)  
                    return (-1);  /* ENOMEM == errno */  
            }  
    
            /*如果这个事件还没有被添加到事件队列中,并且这个事件是I/O或者信号事件,都 
            会在这里被添加到*/  
            if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&  
                !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {  
                /*这个函数是I/O复用机制中的回调函数,回调函数中的内容就是将event添加到event_base 
                中.........*/  
                /*这个函数就是将event和epoll_event集合中的某个联系起来*/  
                res = evsel->add(evbase, ev);  
                /*同时将这个时间添加到event_base中的eventqueues队列中,这个队列存放着和这个 
                event_base相关的所有事件*/  
                if (res != -1)  
                    event_queue_insert(base, ev, EVLIST_INSERTED);  
            }  
    
            /*  
             * we should change the timout state only if the previous event 
             * addition succeeded. 
             */  
            if (res != -1 && tv != NULL) {  
                struct timeval now;  
    
                /*  
                 * we already reserved memory above for the case where we 
                 * are not replacing an exisiting timeout. 
                 */  
                 /*检查这个事件是否存在在队列中,如果存在,那么就删除这个时间*/  
                if (ev->ev_flags & EVLIST_TIMEOUT)  
                    event_queue_remove(base, ev, EVLIST_TIMEOUT);  
    
                /* Check if it is active due to a timeout.  Rescheduling 
                 * this timeout before the callback can be executed 
                 * removes it from the active list. */  
                if ((ev->ev_flags & EVLIST_ACTIVE) &&  
                    (ev->ev_res & EV_TIMEOUT)) {  
                    /* See if we are just active executing this 
                     * event in a loop 
                     */  
                    if (ev->ev_ncalls && ev->ev_pncalls) {  
                        /* Abort loop */  
                        *ev->ev_pncalls = 0;  
                    }  
    
                    event_queue_remove(base, ev, EVLIST_ACTIVE);  
                }  
                /*通过上面一些列的检测,在这里可以将这个超时事件添加到已经申请好的空间中的位置*/  
                gettime(base, &now);  
                evutil_timeradd(&now, tv, &ev->ev_timeout);  
    
                event_debug((  
                     "event_add: timeout in %ld seconds, call %p",  
                     tv->tv_sec, ev->ev_callback));  
    
                event_queue_insert(base, ev, EVLIST_TIMEOUT);  
            }  
    
            return (res);  
        }  
    View Code
  4. 调用event_base_dispatch函数来执行循环事件
    int event_base_dispatch(struct event_base *);
    int event_base_loopexit(struct event_base *, const struct timeval *);
    /*参数是当前事件循环所属的event_base, 执行事件循环实际上是执行了一个while循环,循环里面对定时器超时时间进行检测,并调用select、epoll等事件多路分发函数对注册的句柄进行监控
    直到没有需要再监控的句柄或调用了event_base_loopexit或event_base_loopbreak()退出循环函数
    */
  5. 事件循环结束后,使用*_free系列函数释放系统资源

event

  libevent中的事件处理器是event结构类型。event结构体封装了句柄、事件类型、回调函数、以及其他必要的标志和数据.

struct event {
    TAILQ_ENTRY(event) ev_active_next;
    TAILQ_ENTRY(event) ev_next;
    /* for managing timeouts */
    union {
        TAILQ_ENTRY(event) ev_next_with_common_timeout;
        int min_heap_idx;
    } ev_timeout_pos;
    evutil_socket_t ev_fd;

    struct event_base *ev_base;

    union {                // I/O事件和信号事件不能同时设置
        /* used for io events */
        struct {
            TAILQ_ENTRY(event) ev_io_next;
            struct timeval ev_timeout;
        } ev_io;

        /* used by signal events */
        struct {
            TAILQ_ENTRY(event) ev_signal_next;
            short ev_ncalls;
            /* Allows deletes in callback */
            short *ev_pncalls;
        } ev_signal;
    } _ev;

    short ev_events;
    short ev_res;        /* result passed to event callback */
    short ev_flags;
    ev_uint8_t ev_pri;    /* smaller numbers are higher priority */
    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;
};
View Code
  1. ev_events:所支持的事件类型,可以用所示的标志按位与(互斥的标志不行,如读写事件和信号不能同时设置)
    #define EV_TIMEOUT 0x01//定时事件
    #define EV_READ 0x02//可读事件
    #define EV_WRITE 0x04//可写事件
    #define EV_SIGNAL 0x08//信号事件
    #define EV_PERSIST 0x10//永久事件,事件被触发后自动重新对这个event调用event_add
  2. ev_next:所有已注册过得事件处理器(包括I/O事件处理器和信号事件处理器)通过该成员串成一个尾队列,称之注册事件队列,宏TAILQ_ENTIR是尾队列中的结点类型
    #define TAILQ_ENTIRY(type)\
    struct\
    {
        struct type *tqe_next;\ /*下一个元素*/
        struct type **tqe_prev;\ /*前一个元素的地址*/
    }
    View Code
  3. ev_active_next:所有被激活的事件处理器通过该成员串成一个尾队列成为活动事件队列,活动队列不止一个,不同优先级插入到不同活动队列中,再循环事件中,Reactor按优先级从高到低遍历所有活动事件队列,依次处理其中的事件处理器
  4. ev_times_pos:仅用于定时事件处理器(定时器),这些定时器不存储在时间堆中而是在尾队列中,称为通用定时器队列,通用定时器ev_timeout_pos联合体的ev_next_with_common_timeout成员指出了该定时器在通用定时器队列中的位置,对于其他定时器而言ev_timeout_pos的min_heap_index成员指定了定时器在堆中的位置,一个定时器是否是通用定时器取决于其超时值大小.
  5. _ev:所有相同描述符值的I/O事件处理器通过ev.ev_io.ev_io_next成员串成一个尾队列,称为I/O事件队列,所有相同信号值得事件处理器通过ev.ev_signal.ev_signal_next成员串成一个尾队列(信号事件队列),ev_ev_signal.ev_ncalls成员指定信号发生时,Reactor需要执行多少次该事该事件对应的件处理器中的回调函数,ev_ev_signal.ev_pncalls要么是NULL要么是指向ev_ev_signal.ev_ncalls.
  6. 在程序中可能针对一个socket上的可读可写事件创建多个事件处理器(他们拥有不同的回调函数),当事件发生时,所有的事件处理器都被执行,所以事件队列将具有相同文件描述符值得事件处理器你放在一起,当一个文件描述符上的事件发生时,多路事件分发器能很快地把相关的事件处理器添加到活动事件队列中(信号事件队列同因).ev_fd:对于I/O是文件描述符,对于信号是信号; ev_base:该事件处理器从属的ev_base; ev_res:当前活动事件的类型; ev_flags:一些事件可选的标志; ev_pri:事件处理器优先级,值越小优先级越高; ev_closure:指定ev_base指定事件处理器的回调函数时行为; ev_timeout:仅对定时器有效,指定定时器超时值; ev_callback:事件处理器的回调函数,由event_base回调,当会调函数执行时,他的三个参数分别传入事件处理器的如下三个成员:ev_fd,ev_res,ev_flags; ev_arg:回调函数的参数.
    //ev_flags
    #define EVLIST_TIMEOUT 0x01 /*事件处理器从属通用定时器队列或时间堆*/
    #define EVLIST_INSERTED 0x02 /*事件处理器从属于注册事件队列*/
    #define EVLIST_SIGNAL 0x04 /*没有使用*/
    #define EVLIST_ACTIVE 0x08 /*事件处理器从属于活动事件队列*/
    #define EVLIST_INTERNAL 0x10 /*内部使用*/
    #define EVLIST_INIT 0x80 /*事件处理器已经被初始化*/
    #define EVLIST_ALL (0xf000|0x9f) 定义所有标志
    
    //ev_closure
    #define EV_CLOSURE_NONE 0 //默认行为
    #define EV_CLOSURE_SIGNAL 1//执行信号事件处理器的回调函数,调用ev.ev_signal.ev_ncalls次回调函数
    #define EV_CLOSURE_PERSIST 2//执行完回调函数后,再次将事件处理器加入注册时间队列

源文档解释

/**
 * @struct event
 *
 * Structure to represent a single event.
 *
 * An event can have some underlying condition it represents: a socket
 * becoming readable or writeable (or both), or a signal becoming raised.
 * (An event that represents no underlying condition is still useful: you
 * can use one to implement a timer, or to communicate between threads.)
 *
 * Generally, you can create events with event_new(), then make them
 * pending with event_add().  As your event_base runs, it will run the
 * callbacks of an events whose conditions are triggered.  When you
 * longer want the event, free it with event_free().
 *
 * In more depth:
 *
 * An event may be "pending" (one whose condition we are watching),
 * "active" (one whose condition has triggered and whose callback is about
 * to run), neither, or both.  Events come into existence via
 * event_assign() or event_new(), and are then neither active nor pending.
 *
 * To make an event pending, pass it to event_add().  When doing so, you
 * can also set a timeout for the event.
 *
 * Events become active during an event_base_loop() call when either their
 * condition has triggered, or when their timeout has elapsed.  You can
 * also activate an event manually using event_active().  The even_base
 * loop will run the callbacks of active events; after it has done so, it
 * marks them as no longer active.
 *
 * You can make an event non-pending by passing it to event_del().  This
 * also makes the event non-active.
 *
 * Events can be "persistent" or "non-persistent".  A non-persistent event
 * becomes non-pending as soon as it is triggered: thus, it only runs at
 * most once per call to event_add().  A persistent event remains pending
 * even when it becomes active: you'll need to event_del() it manually in
 * order to make it non-pending.  When a persistent event with a timeout
 * becomes active, its timeout is reset: this means you can use persistent
 * events to implement periodic timeouts.
 *
 * This should be treated as an opaque structure; you should never read or
 * write any of its fields directly.  For backward compatibility with old
 * code, it is defined in the event2/event_struct.h header; including this
 * header may make your code incompatible with other versions of Libevent.
 *
 * @see event_new(), event_free(), event_assign(), event_get_assignment(),
 *    event_add(), event_del(), event_active(), event_pending(),
 *    event_get_fd(), event_get_base(), event_get_events(),
 *    event_get_callback(), event_get_callback_arg(),
 *    event_priority_set()
 */
View Code

往注册事件队列中添加事件处理器

  创建一个event对象的函数是event_new,创建好之后应用程序调用event_add函数将其添加到注册事件队列中,并将对应的事件注册到事件多路分发器上,event_add函数主要是调用一个内部函数event_add_internal来实现的,event_add_internal内部主要调用以下几个函数:

  1. evmap_io_add:将I/O事件添加到事件多路分发器,并将对应的事件处理器添加到I/O事件队列中,同时建立I/O事件和I/O事件处理器之间的映射
  2. evmap_signal_add:该函数将信号时间添加到事件多路分发器,并将对应的事件处理器添加到信号事件队列中,同时建立信号事件和信号事件处理器之间的映射关系
  3. event_queue_insert:将事件处理器添加到各种事事件队列中,将I/O事件处理器和信号时事件处理器插入注册事件队列,将定时器插入到通用定时器队列或事件堆,将被激活的事件处理器添加到活动事件队列

往事件多路分发器中注册事件

  event_queue_insert仅仅将一个事件处理器加入到event_base某个事件队列,对于新加进的I/O事件处理器和信号事件处理器,需要让事件多路分发器监听对应的事件,同时建立文件描述符,信号值,与事件处理器之间的映射关系,通过evmap_io_add和evmap_signal_add完成,这两个函数相当于事件多路分发器中的register_event.

eventop结构体

  eventop结构体封装了i/o复用机制必要的一些操作,比如注册事件、等待事件等,它为event_base支持的所有后端i/o复用机制提供了一个统一的接口,libevent默认选择的后端i/o复用技术是epoll

// <event_internal.h>
/** Structure to define the backend of a given event_base. */
struct eventop {
    /** The name of this backend. */
    const char *name;
    /** Function to set up an event_base to use this backend.  It should
     * create a new structure holding whatever information is needed to
     * run the backend, and return it.  The returned pointer will get
     * stored by event_init into the event_base.evbase field.  On failure,
     * this function should return NULL. */
    void *(*init)(struct event_base *);
    /** Enable reading/writing on a given fd or signal.  'events' will be
     * the events that we're trying to enable: one or more of EV_READ,
     * EV_WRITE, EV_SIGNAL, and EV_ET.  'old' will be those events that
     * were enabled on this fd previously.  'fdinfo' will be a structure
     * associated with the fd by the evmap; its size is defined by the
     * fdinfo field below.  It will be set to 0 the first time the fd is
     * added.  The function should return 0 on success and -1 on error.
     */
    int (*add)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
    /** As "add", except 'events' contains the events we mean to disable. */
    int (*del)(struct event_base *, evutil_socket_t fd, short old, short events, void *fdinfo);
    /** Function to implement the core of an event loop.  It must see which
        added events are ready, and cause event_active to be called for each
        active event (usually via event_io_active or such).  It should
        return 0 on success and -1 on error.
     */
    int (*dispatch)(struct event_base *, struct timeval *);
    /** Function to clean up and free our data from the event_base. */
    void (*dealloc)(struct event_base *);
    /** Flag: set if we need to reinitialize the event base after we fork.
     */
    int need_reinit;
    /** Bit-array of supported event_method_features that this backend can
     * provide. */
    enum event_method_feature features;
    /** Length of the extra information we should record for each fd that
        has one or more active events.  This information is recorded
        as part of the evmap entry for each fd, and passed as an argument
        to the add and del functions above.
     */
    size_t fdinfo_len;
};
View Code

可以看到,该结构体已经声明(非定义)了多路分发机制常备的Reactor初始化、事件添加、事件移除、事件分发及Reactor清理函数,而且均以函数指针的方式定义,便于复用。如epoll对该结构体的一个复用:

static void *epoll_init(struct event_base *);
static int epoll_dispatch(struct event_base *, struct timeval *);
static void epoll_dealloc(struct event_base *);

static const struct eventop epollops_changelist = {
    "epoll (with changelist)",
    epoll_init,
    event_changelist_add,
    event_changelist_del,
    epoll_dispatch,
    epoll_dealloc,
    1, /* need reinit */
    EV_FEATURE_ET|EV_FEATURE_O1,
    EVENT_CHANGELIST_FDINFO_SIZE
};
View Code

  libevent用了一个数组来存储其所支持的多路复用机制:

// <event.c>
/* Array of backends in order of preference. */
static const struct eventop *eventops[] = {
#ifdef _EVENT_HAVE_EVENT_PORTS
    &evportops,
#endif
#ifdef _EVENT_HAVE_WORKING_KQUEUE
    &kqops,
#endif
#ifdef _EVENT_HAVE_EPOLL
    &epollops,
#endif
#ifdef _EVENT_HAVE_DEVPOLL
    &devpollops,
#endif
#ifdef _EVENT_HAVE_POLL
    &pollops,
#endif
#ifdef _EVENT_HAVE_SELECT
    &selectops,
#endif
#ifdef WIN32
    &win32ops,
#endif
    NULL
};
View Code

  通过这个程序,我们可以知道libevent是通过宏定义来确定当前操作系统是否支持某中多路复用机制,并且按顺序选择系统支持的机制。另外,我们如果想知道程序当前所支持的多路复用机制,我们可以调用函数event_get_supported_methods:

// <event.c>
const char **
event_get_supported_methods(void)
{
    static const char **methods = NULL;
    const struct eventop **method;
    const char **tmp;
    int i = 0, k;

    /* count all methods */
    for (method = &eventops[0]; *method != NULL; ++method) {
        ++i;
    }

    /* allocate one more than we need for the NULL pointer */
    tmp = mm_calloc((i + 1), sizeof(char *));
    if (tmp == NULL)
        return (NULL);

    /* populate the array with the supported methods */
    for (k = 0, i = 0; eventops[k] != NULL; ++k) {
        tmp[i++] = eventops[k]->name;
    }
    tmp[i] = NULL;

    if (methods != NULL)
        mm_free((char**)methods);

    methods = tmp;

    return (methods);
}
View Code

  而要想知道程序实际调用的是哪一种多路复用机制,我们可以调用函数event_get_method得到:

 // <event.c>
 const char *
 event_get_method(void)
{
     return (current_base->evsel->name);
}
View Code

event_base

  1. libevent实际上就是对底层select/poll/epoll等进行了封装,每个event_base都有一种“方法”,该“方法”是select、poll、epoll、kqueue、devpoll、evport、win32
  2. 使用 libevent 函数之前需要分配一个或者多个 event_base 结构体。每个 event_base 结构体持有一个事件集合,可以检测以确定哪个事件是激活的
  3. event_base 相当于是一个底座,只要向底座上插入事件,然后不断的监控事件,等待事件发生调用回调函数即可

  结构体event_base是libevent的reactor.首先为 event_base 实例申请空间,然后初始化 timer mini-heap,初始化系统io,初始化事件链表,检测系统的时间设置,每一个event_base持有和管理多个event,并且判断哪些event是被激活了的。但是这发生在一个单一的线程内。如果你想哟啊在多个线程内并行处理event,则需要在每个线程中各创建一个event_base。

struct event_base {
    /** Function pointers and other data to describe this event_base's
     * backend. */
    const struct eventop *evsel;
    /** Pointer to backend-specific data. */
    void *evbase;
 
    /** List of changes to tell backend about at next dispatch.  Only used
     * by the O(1) backends. */
    struct event_changelist changelist;
 
    /** Function pointers used to describe the backend that this event_base
     * uses for signals */
    const struct eventop *evsigsel;
    /** Data to implement the common signal handelr code. */
    struct evsig_info sig;
 
    /** Number of virtual events */
    int virtual_event_count;
    /** Number of total events added to this event_base */
    int event_count;
    /** Number of total events active in this event_base */
    int event_count_active;
 
    /** Set if we should terminate the loop once we're done processing
     * events. */
    int event_gotterm;
    /** Set if we should terminate the loop immediately */
    int event_break;
    /** Set if we should start a new instance of the loop immediately. */
    int event_continue;
 
    /** The currently running priority of events */
    int event_running_priority;
 
    /** Set if we're running the event_base_loop function, to prevent
     * reentrant invocation. */
    int running_loop;
 
    /* Active event management. */
    /** An array of nactivequeues queues for active events (ones that
     * have triggered, and whose callbacks need to be called).  Low
     * priority numbers are more important, and stall higher ones.
     */
    struct event_list *activequeues;
    /** The length of the activequeues array */
    int nactivequeues;
 
    /* common timeout logic */
 
    /** An array of common_timeout_list* for all of the common timeout
     * values we know. */
    struct common_timeout_list **common_timeout_queues;
    /** The number of entries used in common_timeout_queues */
    int n_common_timeouts;
    /** The total size of common_timeout_queues. */
    int n_common_timeouts_allocated;
 
    /** List of defered_cb that are active.  We run these after the active
     * events. */
    struct deferred_cb_queue defer_queue;
 
    /** Mapping from file descriptors to enabled (added) events */
    struct event_io_map io;
 
    /** Mapping from signal numbers to enabled (added) events. */
    struct event_signal_map sigmap;
 
    /** All events that have been enabled (added) in this event_base */
    struct event_list eventqueue;
 
    /** Stored timeval; used to detect when time is running backwards. */
    struct timeval event_tv;
 
    /** Priority queue of events with timeouts. */
    struct min_heap timeheap;
 
    /** Stored timeval: used to avoid calling gettimeofday/clock_gettime
     * too often. */
    struct timeval tv_cache;
 
#if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
    /** Difference between internal time (maybe from clock_gettime) and
     * gettimeofday. */
    struct timeval tv_clock_diff;
    /** Second in which we last updated tv_clock_diff, in monotonic time. */
    time_t last_updated_clock_diff;
#endif
 
#ifndef _EVENT_DISABLE_THREAD_SUPPORT
    /* threading support */
    /** The thread currently running the event_loop for this base */
    unsigned long th_owner_id;
    /** A lock to prevent conflicting accesses to this event_base */
    void *th_base_lock;
    /** The event whose callback is executing right now */
    struct event *current_event;
    /** A condition that gets signalled when we're done processing an
     * event with waiters on it. */
    void *current_event_cond;
    /** Number of threads blocking on current_event_cond. */
    int current_event_waiters;
#endif
 
#ifdef WIN32
    /** IOCP support structure, if IOCP is enabled. */
    struct event_iocp_port *iocp;
#endif
 
    /** Flags that this base was configured with */
    enum event_base_config_flag flags;
 
    /* Notify main thread to wake up break, etc. */
    /** True if the base already has a pending notify, and we don't need
     * to add any more. */
    int is_notify_pending;
    /** A socketpair used by some th_notify functions to wake up the main
     * thread. */
    evutil_socket_t th_notify_fd[2];
    /** An event used by some th_notify functions to wake up the main
     * thread. */
    struct event th_notify;
    /** A function used to wake up the main thread from another thread. */
    int (*th_notify_fn)(struct event_base *base);
};
View Code

  event_base在libevent是一个核心数据结构,其对应一个反应堆模型实例。如果把event_base看成一个类的话,const struct eventop *evsel;中的回调函数就是这个类中的方法

  在 libevent 中, 每种 I/O demultiplex 机制的实现都必须提供eventop这五个函数接口,来完成自身的初始化、销毁释放;对事件的注册、注销和分发。

事件循环---event_base_loop

  libevent中实现事件循环的函数是event_base_loop,该函数首先调用i/o事件多路分发器的事件监听函数,以等待事件,当有事件发生时,就依次处理之。

  一旦一些eventsevent_base注册之后(下一节会讨论如何创建和注册events),就可以使Libevent等待events,并且在events准备好时能够通知你。 
   
  默认情况下,event_base_loop()会在event_base上一直运行,直到其上已经没有注册的events了。运行loop时,它会重复检查那些已经注册的events是否触发了(比如,一个读event的文件描述符变得可读,或者后一个超时event已经超时)。一旦触发,该函数会将这些触发的events标记为active,并且开始运行回调函数。

#define EVLOOP_ONCE             0x01
#define EVLOOP_NONBLOCK         0x02
#define EVLOOP_NO_EXIT_ON_EMPTY 0x04

int event_base_loop(struct event_base *base, int flags);

  可以通过设置一个或多个flag参数,来改变event_base_loop()函数的行为。如果设置了EVLOOP_ONCE,那么loop将会一直等待,直到一些events变为active,然后运行这些激活的events的回调函数,直到运行完所有激活的events为止,最后函数返回。如果设置了EVLOOP_NONBLOCK标志,则该函数不会等待events变为触发,它仅仅检查是否有事件准备好了,然后运行他们的回调函数,最后函数返回。 
  event_base_loop函数返回0表示正常退出,返回-1表示后端方法发生了错误。返回1表示已经没有pendingactive状态的events了。 

 libevent学习手册:https://segmentfault.com/a/1190000005594871

 libevent官方文档:https://www.monkey.org/~provos/libevent/doxygen-2.0.1/include_2event2_2event_8h.html

https://www.bookstack.cn/read/libevent/0696851dcbb5e81c.md

转载于:https://www.cnblogs.com/tianzeng/p/10799362.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值