libevent源码学习(6):事件处理基础——event_base的创建

目录

前言

创建默认的event_base

event_base的配置

event_config结构体

创建自定义event_base——event_base_new_with_config

禁用(避免使用)某一种IO复用模型

设置IO复用模型需要满足的特征

设置event_base的flag特性

获取event_base所满足的特征

获取当前系统所支持的IO复用模型

总结

以下源码均基于libevent-2.0.21-stable。
前言

       要实现libevent的事件处理,最关键的就是event_base,event_base就像是一棵树,而需要进行处理的事件event就像是树上的果子,因此,在分析libevent的事件处理之前,先来分析一下event_base。

       由于event_base结构体中包含了三十多个成员,直接去分析每个成员是比较麻烦并且没有太大意义的,因此个人觉得从event_base的使用中去了解event_base各个结构体成员的作用会比较好,因此这里先来看看event_base的创建。
创建默认的event_base

       在event.c中,可以看到event_base有两种创建方式。

       目前最常用的一种方式是使用event_base_new,该函数的定义如下:

    struct event_base *
    event_base_new(void)
    {
        struct event_base *base = NULL;
        struct event_config *cfg = event_config_new();  
        if (cfg) {  //cfg非空,说明event_config分配成功
            base = event_base_new_with_config(cfg);  //将cfg配置到base中去
            event_config_free(cfg);  //清空cfg链表
        }
        return base;
    }//event.c

        可以发现,该函数是创建了一个event_base并最终返回了它,并且该函数无需传入任何参数,也就是说,如果调用该函数来创建一个event_base,那么返回得到的就是一个默认的base,而event_base_new也就是默认的event_base创建函数。关于其中使用到的event_config相关的函数,下面会分析。

       除了这种创建方式外,还有另一种创建方式event_init,该函数声明如下:

    /**
      Initialize the event API.
      The event API needs to be initialized with event_init() before it can be
      used.  Sets the global current base that gets used for events that have no
      base associated with them.
      @deprecated This function is deprecated because it replaces the "current"
        event_base, and is totally unsafe for multithreaded use.  The replacement
        is event_base_new().
      @see event_base_set(), event_base_new()
     */
    struct event_base *event_init(void);//event.h

       首先关注到该函数定义的描述文字中有一个@deprecated,这说明该函数是不推荐使用并且有替代品的。并提到该函数不是线程安全的,替代函数就是event_base_new。来看看该函数定义:

    struct event_base *
    event_init(void)
    {
        struct event_base *base = event_base_new_with_config(NULL); //默认方式创建一个base 相当于调用了一个event_base_new
     
        if (base == NULL) {
            event_errx(1, "%s: Unable to construct event_base", __func__);
            return NULL;
        }
     
        current_base = base;
     
        return (base);
    }

       在event_init函数中,创建一个新的event_base是用NULL为参数调用event_base_new_with_config的,这实际上就相当于调用了默认的event_base_new。并且在最后用current_base来保存新创建的event_base,这里的current_base是一个全局变量,这也是导致该函数线程不安全的原因。查看了一下其他资料,提到event_init实际上是老版本libevent中使用到的创建函数,不过现在相当于被废弃了,只不过为了兼容低版本,还依然保存着。

       这里分析了创建一个默认的event_base,那么如果创建一个自定义的event_base呢?并且自定义的event_base中有哪些属性是可以自定义的呢?
event_base的配置

       创建一个自定义的event_base是通过函数event_base_new_with_config来实现的,该函数声明如下:

struct event_base *event_base_new_with_config(const struct event_config *);

       该函数需要传入一个event_config参数,而该参数则包含了用户自定义的配置信息,创建得到的event_base也是根据这个参数得到的。先来看看event_config结构。
event_config结构体

       event_config结构体定义如下:

    struct event_config {  //关于头结点的结构体,描述整个双向链表,event_config用于配置event_base
        TAILQ_HEAD(event_configq, event_config_entry) entries; //每个结点包含一个avoid_method
        //定义一个头结点结构体,结构体名为event_configq,结点类型为event_config_entry,头结点变量名为entries
        int n_cpus_hint;
        enum event_method_feature require_features;  //要求满足的特征:ET、O1、FDS
        enum event_base_config_flag flags; //其他配置要求:无锁、不检查环境变量、...
    };

       event_config中有4个结构体成员。其中第一个结构体成员entries是TAILQ中的头结点类型,它指向一个event_config_entry类型的结点,event_config_entry类型的定义如下:

    struct event_config_entry {   //结点类型
        TAILQ_ENTRY(event_config_entry) next;
        //定义一个带前驱后驱指针的结构体变量名为next,指向的结点类型为event_config_entry
        const char *avoid_method;
    };

        可以看到,event_config_entry实际上就是TAILQ中的链表结点,每一个结点都包含一个前驱和后驱结点指针,除此之外每个结点还有一个常量字符串类型的avoid_method,从变量名上来看,每个结点都定义了一个“避免使用的方法”,这个“方法”其实指的就是某种多路IO复用模型。event_config结构体的第一个成员entries作为TAILQ的头结点,连接的就是所有需要避免使用的多路IO复用模型。

        event_config的第二个成员n_cpus_hint,该成员仅在启用IOCP时使用到,这里暂时不做说明;

        event_config的第三个成员require_features,是枚举类型event_method_feature,该枚举类型定义如下:

    enum event_method_feature {
        /** Require an event method that allows edge-triggered events with EV_ET. */
        EV_FEATURE_ET = 0x01,
        /** Require an event method where having one event triggered among
         * many is [approximately] an O(1) operation. This excludes (for
         * example) select and poll, which are approximately O(N) for N
         * equal to the total number of possible events. */
        EV_FEATURE_O1 = 0x02,
        /** Require an event method that allows file descriptors as well as
         * sockets. */
        EV_FEATURE_FDS = 0x04
    };

        event_method_feature中定义了3种特征,分别表示“是否支持边沿触发模式”、“是否支持事件的添加、删除、激活等操作的时间复杂度为O(1)”和“是否除了socket之外也支持其他的文件描述符”。因此,event_method_feature描述的应当是IO复用方法所需满足的特征。

       event_config的第四个成员flags,是枚举类型event_base_config_flag,该枚举类型定义如下:

    enum event_base_config_flag {
        /** Do not allocate a lock for the event base, even if we have
            locking set up. */
        EVENT_BASE_FLAG_NOLOCK = 0x01,   //
        /** Do not check the EVENT_* environment variables when configuring
            an event_base  */
        EVENT_BASE_FLAG_IGNORE_ENV = 0x02,
        /** Windows only: enable the IOCP dispatcher at startup
            If this flag is set then bufferevent_socket_new() and
            evconn_listener_new() will use IOCP-backed implementations
            instead of the usual select-based one on Windows.
         */
        EVENT_BASE_FLAG_STARTUP_IOCP = 0x04,
        /** Instead of checking the current time every time the event loop is
            ready to run timeout callbacks, check after each timeout callback.
         */
        EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,
     
        /** If we are using the epoll backend, this flag says that it is
            safe to use Libevent's internal change-list code to batch up
            adds and deletes in order to try to do as few syscalls as
            possible.  Setting this flag can make your code run faster, but
            it may trigger a Linux bug: it is not safe to use this flag
            if you have any fds cloned by dup() or its variants.  Doing so
            will produce strange and hard-to-diagnose bugs.
            This flag can also be activated by settnig the
            EVENT_EPOLL_USE_CHANGELIST environment variable.
            This flag has no effect if you wind up using a backend other than
            epoll.
         */
        EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10
    };

         event_base_config_flag与event_method_feature都是枚举类型,不同的是event_method_feature描述的是方法所需满足的特征,而event_base_config_flag描述的则是event_base的特性,从event_base_config_flag的定义来看,这里定义了5种event_base可供选择的特性,各自的含义如下:(图片来源于《libevent参考手册》)

       通过以上对event_config的结构体成员分析,可以知道event_config结构体中指定了需要避免使用的IO模型(entries)、所使用的IO复用模型需要满足的特征(event_method_feature)以及创建的event_base需要满足的要求(event_base_config_flag),这样,将event_config类型的参数cfg传入到event_base_new_with_config中,即可根据cfg中的配置来创建符合条件的event_base。

       接下来就分析event_base_new_with_config是如何通过参数cfg来创建一个自定义的event_base。
创建自定义event_base——event_base_new_with_config

      event_base_new_with_config函数定义如下:

    struct event_base *
    event_base_new_with_config(const struct event_config *cfg)
    {
        int i;
        struct event_base *base;   
        int should_check_environment;
     
    #ifndef _EVENT_DISABLE_DEBUG_MODE
        event_debug_mode_too_late = 1;
    #endif
        //分配一个event_base,其内部全部置0
        if ((base = mm_calloc(1, sizeof(struct event_base))) == NULL) {
            event_warn("%s: calloc", __func__);  //分配base失败
            return NULL;
        }
     
        //一系列初始化
        detect_monotonic();
        gettime(base, &base->event_tv);
     
        min_heap_ctor(&base->timeheap);
        TAILQ_INIT(&base->eventqueue);
        base->sig.ev_signal_pair[0] = -1;
        base->sig.ev_signal_pair[1] = -1;
        base->th_notify_fd[0] = -1;
        base->th_notify_fd[1] = -1;
     
        event_deferred_cb_queue_init(&base->defer_queue);
        base->defer_queue.notify_fn = notify_base_cbq_callback;
        base->defer_queue.notify_arg = base;
        if (cfg)
            base->flags = cfg->flags;  //将配置结构体cfg中的flag赋值给base
     
        evmap_io_initmap(&base->io);
        evmap_signal_initmap(&base->sigmap);
        event_changelist_init(&base->changelist);
     
        base->evbase = NULL;
        //如果cfg不为空并且cfg设置了忽略环境变量,则should_check_environment为0,其他情况为1(包括cfg为空的情况)
        should_check_environment =
            !(cfg && (cfg->flags & EVENT_BASE_FLAG_IGNORE_ENV));  //是否需要检查环境变量
        //eventops中存放所有可供使用的backups
        for (i = 0; eventops[i] && !base->evbase; i++) { //每一次循环必须base->evbase为NULL,这样就保证了event_base只会接受第一个符合条件的eventop
            if (cfg != NULL) {
                /* determine if this backend should be avoided */
                if (event_config_is_avoided_method(cfg,
                    eventops[i]->name)) //检查eventops中的backup是否在cfg中被avoid
                    continue;
                if ((eventops[i]->features & cfg->require_features) //检查eventops中的backup对应的特征是否符合cfg中所要求的特征
                    != cfg->require_features)
                    continue;
            }
            //执行到这里说明当前的backup是符合cfg中的feature和avoid要求的
            /* also obey the environment variables */
            if (should_check_environment && //如果还需要检查环境变量,并且由backup名称构成的环境变量存在的话
                event_is_method_disabled(eventops[i]->name))
                continue;
            //执行到这里说明当前的backup是符合feature、avoid以及环境变量的
            base->evsel = eventops[i]; //将该backup作为base使用的backup
     
            base->evbase = base->evsel->init(base); // 用选定的backup的初始化函数来初始化base中的evbase
        }
     
        if (base->evbase == NULL) { //如果evbase依然为NULL,说明没有合适的backup
            event_warnx("%s: no event mechanism available",
                __func__);
            base->evsel = NULL;
            event_base_free(base);
            return NULL;
        }
     
        if (evutil_getenv("EVENT_SHOW_METHOD"))
            event_msgx("libevent using: %s", base->evsel->name);
     
        /* allocate a single active event queue */
        if (event_base_priority_init(base, 1) < 0) {
            event_base_free(base);
            return NULL;
        }
     
        /* prepare for threading */
     
    #ifndef _EVENT_DISABLE_THREAD_SUPPORT   //如果支持多线程,就给base分配锁和条件变量
        if (EVTHREAD_LOCKING_ENABLED() &&  //如果启用了线程锁,并且没有设置cfg或者flags中没有设置无锁
            (!cfg || !(cfg->flags & EVENT_BASE_FLAG_NOLOCK))) {
            int r;
            EVTHREAD_ALLOC_LOCK(base->th_base_lock,
                EVTHREAD_LOCKTYPE_RECURSIVE);  //分配一个递归锁
            base->defer_queue.lock = base->th_base_lock;
            EVTHREAD_ALLOC_COND(base->current_event_cond);  //分配一个条件变量
            r = evthread_make_base_notifiable(base);
            if (r<0) {
                event_warnx("%s: Unable to make base notifiable.", __func__);
                event_base_free(base);
                return NULL;
            }
        }
    #endif
     
    #ifdef WIN32
        if (cfg && (cfg->flags & EVENT_BASE_FLAG_STARTUP_IOCP)) //如果在windows下并且设置了IOCP可用falg,就开启iocp
            event_base_start_iocp(base, cfg->n_cpus_hint);
    #endif
     
        return (base);
    }

       在该函数中,所做的最主要的事情,就是将满足cfg的IO复用模型分配给创建的event_base上,cfg只是提供了该模型需要满足的条件,event_base_new_with_config会根据cfg从libevent提供的几种IO模型中去找到满足条件的IO模型,并将其作为最终event_base所使用的IO模型。event_base_new_with_config中这一部分的实现代码如下:

    ......
    base->evbase = NULL;
     
    for (i = 0; eventops[i] && !base->evbase; i++) { //每一次循环必须base->evbase为NULL,这样就保证了event_base只会接受第一个符合条件的eventop
            if (cfg != NULL) {
                /* determine if this backend should be avoided */
                if (event_config_is_avoided_method(cfg,
                    eventops[i]->name)) //检查eventops中的backup是否在cfg中被avoid
                    continue;
                if ((eventops[i]->features & cfg->require_features) //检查eventops中的backup对应的特征是否符合cfg中所要求的特征
                    != cfg->require_features)
                    continue;
            }
            //执行到这里说明当前的backup是符合cfg中的feature和avoid要求的
            
            ......
     
            base->evsel = eventops[i]; //将该backup作为base使用的backup
     
            base->evbase = base->evsel->init(base); // 用选定的backup的初始化函数来初始化base中的evbase
        }
     
    ......

      在for循环中,会循环判断eventops中的每个元素,eventops是一个全局的指针数组,它里面包含了libevent中提供的IO复用模型,eventops中的实际元素是由操作系统决定的,比如说在windows下eventops中就只有一个&win32ops元素了。

    static const struct eventop *eventops[] = { //包含了各种可使用的backups的函数结构体
    #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
    };

       而对于eventops中的每个元素,都是指向eventop类型的指针,eventop的定义如下:

    struct eventop { //每一种backend对应一个eventop
        /** The name of this backend. */
        const char *name;//backend的名称
        /** 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. */
         //初始化函数,在该函数中需要创建一个新的结构体,并在其中包含有所有该backend执行所需的信息
         //并且返回一个指向该结构体的指针。这个结构体指针会被event_base.evbase所接收
        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.
         */
         //使能指定的文件描述符fd的感兴趣事件events,感兴趣事件包括读事件EV_READ、
         //写事件EV_WRITE、信号事件EV_SIGNAL以及边沿事件EV_ET。old为该描述符已经被设置的事件;fdinfo描述符信息;
        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.    
         *///实现事件循环event_loop的核心函数。它必须查看哪些已注册的事件已经准备好了,
        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;//当fork后,是否需要重新初始化 标识
        /** Bit-array of supported event_method_features that this backend can
         * provide. */
        enum event_method_feature features;  //描述该backend所支持的特征(支持边沿触发、事件操作时间复杂度为O(1)、支持文件描述符包括套接字)
        /** 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;//文件描述符的其他信息
    };

       这里的backend译为“后端”,实际上就是指一种IO复用模型,像常见的IO复用模型epoll、select、iocp等都属于这样一种“后端”。在结构体eventop中,需要指定模型的名称(name)、模型对应的初始化函数、事件注册函数、事件删除函数、事件分发函数以及事件销毁函数、fork后是否需要重新初始化(need_reinit)以及该模型的特征(features)。

       比如以eventops中的&epollops为例,根据名字来看,能够知道它对应了epoll模型,查看epollops的定义,如下所示:

    const struct eventop epollops = {
        "epoll",       //backend名称
        epoll_init,    //初始化函数
        epoll_nochangelist_add,    //事件添加函数
        epoll_nochangelist_del,    //事件删除函数
        epoll_dispatch,    //事件分发函数
        epoll_dealloc,     //事件销毁函数
        1, /* need reinit */    //fork后需要重新初始化
        EV_FEATURE_ET|EV_FEATURE_O1,   //epoll的特征为“支持边沿触发”以及“支持事件操作时间复杂度为O1”
        0
    };  //epoll.c

       除了&epollops、eventops中每一个元素对对应一种IO模型,而每一种IO模型都有各自的名称、事件操作函数、IO模型的特征(如epoll模型是满足“边沿触发”和“事件操作时间复杂度为O1”两个特征的,多个特征之间是'|'连接的)等信息。

       再回到event_base_new_with_config中的相应代码部分。遍历eventops,对于eventops中的每个元素,会先用event_config_is_avoided_method来判断该元素对应的IO模型是否是cfg中设置“需要避免”的,传入参数为cfg以及当前的eventops元素的名称,event_config_is_avoided_method函数的定义如下:

    static int  //判断一个method是不是cfg所设定的avoid_method,是则1,否则0
    event_config_is_avoided_method(const struct event_config *cfg,
        const char *method)  //如果cfg中某个结点设置的avoid_method与传入的method相同则返回1否则为0
    {    
        struct event_config_entry *entry;  //用于保存遍历的结点
     
        TAILQ_FOREACH(entry, &cfg->entries, next) {  //遍历cfg中TAILQ的所有结点
            if (entry->avoid_method != NULL &&
                strcmp(entry->avoid_method, method) == 0)  //如果结点中设置的avoid_method和传入的method相同则返回1否则返回0
                return (1);
        }
     
        return (0);
    }

        该函数的实现逻辑还是很简单的。由于cfg中的entries是TAILQ的头结点,它所连接的每一个结点都指定了一种“需要避免的方法”,因此这里直接遍历entries下的每一个结点,如果entries下某一个结点的avoid_method与遍历到的eventops元素对应的IO模型名称相同,那么就说明该IO模型是不允许使用的,就继续遍历eventops下一个元素;

       除了判断当前eventops元素对应的IO模型是否“需要被避免",还需要判断当前eventops元素对应的IO模型是否满足cfg所设置的特征。因此就还需要将当前eventops元素的features与cfg设置的require_features进行比较,如果不同的话,说明当前eventops元素对应的IO模型不满足cfg设置的条件,就继续遍历eventops下一个元素。

       如果上面两个条件都满足了,那么就说明当前的eventops元素对应的IO模型是满足cfg条件的,此时将该eventops元素存到新建的event_base的evsel中,并且用eventops元素对应的IO模型中的初始化函数对新建的event_base进行初始化,并将初始化函数的返回值存储在event_base的evbase中。

       这里需要注意的是调用IO模型的init函数的返回值,各种IO模型对应的返回值是不同的,。这些类型的定义如下所示:

        需要注意的是,对于每一种eventops中涉及到的IO复用模型,其对应的初始化函数(如select_init、epoll_init等)返回的都是相应的IO复用结构体,如对于epoll_init函数,其返回值实际上是struct epollop *类型,在poll模型的poll_init函数中,则返回的是struct poll *类型,select_init返回struct select *类型等等。其各自对应的复用结构体定义如下:

    //select复用结构体
    struct selectop {  
        int event_fds;        /* Highest fd in fd set */
        int event_fdsz;
        int resize_out_sets;
        fd_set *event_readset_in;
        fd_set *event_writeset_in;
        fd_set *event_readset_out;
        fd_set *event_writeset_out;
    };
    //poll复用结构体
    struct pollop {    
        int event_count;        /* Highest number alloc */
        int nfds;            /* Highest number used */
        int realloc_copy;        /* True iff we must realloc
                         * event_set_copy */
        struct pollfd *event_set;
        struct pollfd *event_set_copy;
    };
    //epoll复用结构体
    struct epollop {   
        struct epoll_event *events;
        int nevents;
        int epfd;
    };
     
    ......

       可见,在IO复用结构体中,保存了对应IO复用模型的相关信息,由于各种类型不同,因此定义一个void *类型的base->evbase来进行保存。然后,通过base->evbase = base->evsel->init(base);就把所选用的IO复用模型对应的复用结构体赋值给了新建的event_base的evbase成员。如果eventops的某一个元素对应的IO模型符合cfg设置的条件,那么base->evbase也就不再为NULL,就不会再执行下一次循环了,也就是说,event_base_new_with_config只会选用eventops中第一个满足条件的元素所对应的IO模型。

       从event_base_new_with_config的流程我们也可以发现,通过IO模型的特征来定制我们需要的cfg,并将其作为参数,event_base_new_with_config会自动选用符合条件的IO模型。并且最终选用的IO模型相关的信息都是封装在了新建的event_base的evsel和evbase成员中,我们无需知道新建的event_base使用的是哪一种IO模型,就可以通过base->add、base->del......来调用所使用的IO模型的事件操作函数。

       现在再回到默认的event_base_new函数中,该函数是调用event_base_new_with_config(NULL)来创建一个新的event_base的,不难知道,event_base_new_with_config的参数如果传入的是NULL,那么就相当于没有设定任何的features、avoid_method和flags,那么就会直接使用eventops中的第一个元素对应的IO复用模型作为默认的IO复用模型。

        接下来再看看如何去设定自己需要的cfg。
禁用(避免使用)某一种IO复用模型

       可以使用event_config_avoid_method来添加禁用(需要被避免使用)的IO复用模型。该函数逻辑很简单,传入配置结构体cfg以及待禁用的IO复用模型名称,然后创建一个event_config_entry结点,将禁用的IO名称存入该结点,最后将该结点添加到cfg的TAILQ头结点entries中即可。

    int
    event_config_avoid_method(struct event_config *cfg, const char *method)
    {
        struct event_config_entry *entry = mm_malloc(sizeof(*entry)); //新建一个结点
        if (entry == NULL)
            return (-1);
     
        if ((entry->avoid_method = mm_strdup(method)) == NULL) { //将新建的结点下的avoid_method赋值为method
            mm_free(entry);
            return (-1);
        }
     
        TAILQ_INSERT_TAIL(&cfg->entries, entry, next); //该创建好的结点添加到cfg中
     
        return (0);
    }

设置IO复用模型需要满足的特征

        使用event_config_require_features函数来设置cfg中的require_features,需要注意的是,这里传入的参数features是直接被赋值给cfg->require_features的,而eventops中各个IO复用模型的features是多个feature的“或”结果。因此,如果需要设定IO复用模型满足多个特征的话,那么传入的features就必须是这多个特征的“或”结果,如EV_FEATURE_ET|EV_FEATURE_O1,不能先用EV_FEATURE_ET调用一次再用EV_FEATURE_O1调用一次,这样得到的cfg中只有最后调用的EV_FEATURE_O1。

    int
    event_config_require_features(struct event_config *cfg,
        int features)   //设置cfg的feature
    {
        if (!cfg)
            return (-1);
        cfg->require_features = features;
        return (0);
    }

设置event_base的flag特性

       使用event_config_set_flag来设置cfg中的flags成员,这里的设置和event_config_require_features是有所不同的:event_config_require_features是一次性设置,无法实现特征的“添加”;而event_config_set_flag是通过cfg->flags |= flag;来对cfg的flags进行赋值的,也就是说,可以分开设置flags,如果要同时满足多个flags,可以分别多次调用event_config_set_flag函数。举个例子,假设创建的event_base必须同时满足EVENT_BASE_FLAG_NOLOCK和EVENT_BASE_FLAG_IGNORE_ENV两个特性,除了一次性调用event_config_set_flag(cfg,EVENT_BASE_FLAG_NOLOCK | EVENT_BASE_FLAG_IGNORE_ENV),还可以分两次调用event_config_set_flag(cfg,EVENT_BASE_FLAG_NOLOCK);event_config_set_flag(cfg,EVENT_BASE_FLAG_IGNORE_ENV);得到的结果都是一样的,而在event_config_require_features中则只能使用前者来设置多个特征。

    int
    event_config_set_flag(struct event_config *cfg, int flag) //为cfg在原来的基础上添加flag
    {
        if (!cfg)
            return -1;
        cfg->flags |= flag;
        return 0;
    }

       通过以上三个函数,就可以定制自己所需要的cfg,使用这个cfg作为参数去调用event_base_new_with_config函数就可以创建一个满足cfg条件的event_base了。
获取event_base所满足的特征

        使用event_base_get_features来获取已创建的event_base所满足的特征。前面说了,新建的event_base所使用的IO复用模型的相关信息都放在evsel和evbase成员中,因此可以直接从evsel->features来获取event_base所满足的特征。

    int
    event_base_get_features(const struct event_base *base)
    {
        return base->evsel->features;
    }

获取当前系统所支持的IO复用模型

        使用event_get_supported_methods可以获取当前系统所支持的IO复用模型。前面说了,不同系统下直接影响的就是全局指针数组eventops的内容,因此event_get_supported_methods函数实际上就是对eventops中存在的元素进行遍历,记录下这些元素对应的IO复用模型的名称,保存在const char二级指针methods中,它实际就相当于是一个const char数组,可以通过methods[i]来进行元素的访问,如果没有支持的IO复用模型,那么methods的内容就为NULL。

    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);
    }

总结

       event_base的创建方式有两种,一种是用event_base_new创建默认的event_base,该函数无需参数,在默认的情况下,创建的event_base所使用的IO复用模型是eventops中的第一个元素对应的IO复用模型;另一种创建方式则是使用event_base_new_with_config进行创建,该函数需要传入一个const struct event_config *型的cfg结构体指针变量,该结构体中会指定创建的event_base使用的IO模型所需满足的条件,根据cfg设置的条件从eventops中找到符合条件的IO复用模型,最终作为创建的event_base所使用的IO复用模型。

       libevent中封装了多种多平台下的IO复用模型,用户无需知道最终使用的是哪一种模型,可以指定IO模型所需满足的条件。在event_base成功创建后,直接通过base->evsel->xxx即可调用相应的事件添加、删除等函数。
————————————————
版权声明:本文为CSDN博主「HerofH_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_28114615/article/details/92847048

原文地址:https://www.cnblogs.com/cnhk19/p/14465634.html