Libevent源码分析-----配置event_base

时间:2022-10-25 00:18:19
 出处:  http://blog.csdn.net/luotuo44/article/details/38443569



        前面的博文都是讲一些Libevent的一些辅助结构,现在来讲一下关键结构体:event_base。

 

        这里作一个提醒,在阅读Libevent源码时,会经常看到backend这个单词。其直译是“后端”。实际上其指的是Libevent内部使用的多路IO复用函数,多路IO复用函数就是select、poll、epoll这类函数。本系列博文中,为了叙述方便,“多路IO复用函数”与“后端”这两种说法都会采用。


配置结构体:

        通常我们获取event_base都是通过event_base_new()这个无参函数。使用这个无参函数,只能得到一个默认配置的event_base结构体。本文主要是讲一些怎么获取一个非默认配置的event_base以及可以对event_base进行哪些配置。

        还是先看一下event_base_new函数吧。

//event.c文件
struct event_base *
event_base_new(void)
{
struct event_base *base = NULL;
struct event_config *cfg = event_config_new();
if (cfg) {
base = event_base_new_with_config(cfg);
event_config_free(cfg);
}
return base;
}

        可以看到,其先创建了一个event_config结构体,并用cfg指针指向之,然后再用这个变量作为参数调用event_base_new_with_config。因为并没有对cfg进行任何的设置,所以得到的是默认配置的event_base。

        从这里也可以知道,如果要对event_base进行配置,那么对cfg变量进行配置即可。现在我们的目光从event_base结构体转到event_config结构体。

        先来看看event_config结构体的定义。

struct event_config {
TAILQ_HEAD(event_configq, event_config_entry) entries;

int n_cpus_hint;
enum event_method_feature require_features;
enum event_base_config_flag flags;
};

struct event_config_entry {
TAILQ_ENTRY(event_config_entry) next;

const char *avoid_method;
};

        我们要做的就是对event_config结构体的那四个成员变量进行配置。



具体的配置内容:

拒绝使用某个后端:

        第一个成员entries,其结构就不展开了,关于TAILQ_HEAD,可以参考《TAILQ_QUEUE队列》。这里知道它是表示一个队列即可,队列元素的类型就是event_config_entry,可以用来存储一个字符串指针。它对应的设置函数为event_config_avoid_method。

        Libevent是跨平台的Reactor,对于事件监听,其内部是使用多路IO复用函数。比较通用的多路IO复用函数是select和poll。而很多平台都提出了自己的高效多路IO复用函数,比如:epoll、devpoll、kqueue。Libevent对于这些多路IO复用函数都进行包装,供自己使用。event_config_avoid_method函数就是指出,避免使用指定的多路IO复用函数。其是通过字符串的方式指定的,即参数method。这个字符串将由队列元素event_config_entry的avoid_method成员变量存储(由于是指针,所以更准确来说是指向)。

        查看Libevent源码包里的文件,可以发现有诸如epoll.c、select.c、poll.c、devpoll.c、kqueue.c这些文件。打开这些文件就可以发现在文件内容的前面都会定义一个struct eventop类型变量。该结构体的第一个成员必然是一个字符串。这个字符串就描述了对应的多路IO复用函数的名称。所以是可以通过名称来禁用某种多路IO复用函数的。

        下面是event_config_avoid_method函数的实现。其作用是把method指明的各个名称记录到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) {
mm_free(entry);
return (-1);
}

//插入到队列中
TAILQ_INSERT_TAIL(&cfg->entries, entry, next);

return (0);
}


上面的代码是设置拒绝使用某一个多路IO复用函数,在创建一个event_base时怎么进行选择的可以参考这一个链接


智能调整CPU个数:

        第二个成员变量n_cpus_hint从名字来看是指明CPU的数量。是通过函数event_config_set_num_cpus_hint来设置的。其作用是告诉event_config,系统中有多少个CPU,以便作一些对线程池作一些调整来获取更高的效率。目前,仅仅Window系统的IOCP(Windows的IOCP能够根据CPU的个数智能调整),该函数的设置才有用。在以后,Libevent可能会将之应用于其他系统。

        正如其名字中的hint,这仅仅是一个提示。就如同C++中的inline。event_base实际使用的CPU个数不一定等于提示的个数。

 

规定所选后端需满足的特征:

        第三个成员变量require_features。从其名称来看是要求的特征。不错,这个变量指定 多路IO复用函数应该满足哪些特征。所有的特征定义在一个枚举类型中。

//event.h文件
enum event_method_feature {
//支持边沿触发
EV_FEATURE_ET = 0x01,
//添加、删除、或者确定哪个事件激活这些动作的时间复杂度都为O(1)
//select、poll是不能满足这个特征的.epoll则满足
EV_FEATURE_O1 = 0x02,
//支持任意的文件描述符,而不能仅仅支持套接字
EV_FEATURE_FDS = 0x04
};

        这个成员变量是通过event_config_require_features函数设置的。该函数的内部还是挺简单的。

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

        从函数的实现可以看到,如果要设置多个特征,不能调用该函数多次,而应该使用位操作。比如: EV_FEATURE_O1 | EV_FEATURE_FDS作为参数。

        值得注意的是,对于某些系统,可能其提供的多路IO复用函数不能满足event_config_require_features函数要求的特征,此时event_base_new_with_config函数将返回NULL,即得不到一个满足条件的event_base。所以在设置这个特征时,那么就要检查event_base_new_with_config的返回值是否为NULL,像下面代码那样。

#include<event.h>
#include<stdio.h>

int main()
{
event_config *cfg = event_config_new();
event_config_require_features(cfg, EV_FEATURE_O1 | EV_FEATURE_FDS);

event_base *base = event_base_new_with_config(cfg);
if( base == NULL )
{
printf("don't support this features\n");
base = event_base_new(); //使用默认的。
}

…..
return 0;
}

        上面代码中,如果是在Linux运行,也是返回NULL。即epoll都不能同时满足那个两个特征。

        那么怎么知道多路IO复用函数支持哪些特征呢?前面说到的一个机构体struct eventop中有一个成员正是enum event_method_feature features。在Libevent-2.0.21-stable中是倒数第二个成员。打开epoll.c、select.c、poll.c、devpoll.c、kqueue.c这些文件,查看里面定义的struct eventop类型变量,就可以看到各个多路IO复用函数都支持哪些特征。在epoll.c文件可以看到,epoll支持EV_FEATURE_ET|EV_FEATURE_O1。所以前面的代码中,返回NULL。



其他一些设置:

        第四个变量flags是通过函数event_config_set_flag设置的。函数的实现很简单。注意,函数的内部是进行 |= 运算的。

//event.c文件
int
event_config_set_flag(struct event_config *cfg, int flag)
{
if (!cfg)
return -1;
cfg->flags |= flag;
return 0;
}

        现在来看一下参数flag可以取哪些值。

  • EVENT_BASE_FLAG_NOLOCK:不要为event_base分配锁。设置这个选项可以为event_base节省一点加锁和解锁的时间,但是当多个线程访问event_base会变得不安全
  • EVENT_BASE_FLAG_IGNORE_ENV:选择多路IO复用函数时,不检测EVENT_*环境变量。使用这个标志要考虑清楚:因为这会使得用户更难调试程序与Libevent之间的交互
  • EVENT_BASE_FLAG_STARTUP_IOCP:仅用于Windows。这使得Libevent在启动时就启用任何必需的IOCP分发逻辑,而不是按需启用。如果设置了这个宏,那么evconn_listener_new和bufferevent_socket_new函数的内部将使用IOCP
  • EVENT_BASE_FLAG_NO_CACHE_TIME:在执行event_base_loop的时候没有cache时间。该函数的while循环会经常取系统时间,如果cache时间,那么就取cache的。如果没有的话,就只能通过系统提供的函数来获取系统时间。这将更耗时
  • EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST:告知Libevent,如果决定使用epoll这个多路IO复用函数,可以安全地使用更快的基于changelist 的多路IO复用函数:epoll-changelist多路IO复用可以在多路IO复用函数调用之间,同样的fd 多次修改其状态的情况下,避免不必要的系统调用。但是如果传递任何使用dup()或者其变体克隆的fd给Libevent,epoll-changelist多路IO复用函数会触发一个内核bug,导致不正确的结果。在不使用epoll这个多路IO复用函数的情况下,这个标志是没有效果的。也可以通过设置EVENT_EPOLL_USE_CHANGELIST 环境变量来打开epoll-changelist选项


        综观上面4个变量的设置,特征设置event_config_require_features和CPU数目设置event_config_set_num_cpus_hint两者的函数调用会覆盖之前的设置。如果要同时设置多个,那么需要在参数中使用位运算中的 | 。而另外两个变量的设置可以通过多次调用函数的方式同时设置多个值。



获取当前配置:

        前面的介绍的都是设置,现在来讲一下获取。主要有下面几个。

const char **event_get_supported_methods(void);
const char *event_base_get_method(const struct event_base *);
int event_base_get_features(const struct event_base *base);
static int event_config_is_avoided_method(const struct event_config *cfg, const char *method)

        第一个函数是获取当前系统所支持的多路IO复用函数有哪些。第二个函数需要一个event_base结构体作为参数,说明是在new到一个event_base之后才能调用的。该函数返回值是对应event_base* 当前所采用的多路IO复用函数是哪个。第三个函数则是获取参数event_base当前所采用的特征是什么。第四个函数则说明参数method指明的多路IO复用函数是不是被参数cfg所禁用了。如果是禁用了,返回非0值。不禁用就返回0。

#include<event2/event.h>
#include<stdio.h>

#ifdef WIN32
#include<WinSock2.h>
#endif

int main()
{
#ifdef WIN32
WSADATA wsa_data;
WSAStartup(0x0201, &wsa_data);
#endif

const char** all_methods = event_get_supported_methods();

while( all_methods && *all_methods )
{
printf("%s\t", *all_methods++);
}

printf("\n");

event_base *base = event_base_new();
if( base )
printf("current method:\t %s\n", event_base_get_method(base) );
else
printf("base == NULL\n");

#ifdef WIN32
WSACleanup();
#endif

return 0;
}

        上面代码在我的Ubuntu10.04上运行,其结果为:

        epoll        poll        select

        currentmethod:        epoll

        在Win7 + VS2010的运行结果为

        win32

        currentmethod:        win32



参考:

        http://www.wangafu.net/~nickm/libevent-book/Ref2_eventbase.html