加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

Libevent源码分析-----跨平台Reactor接口的实现

发布时间:2020-12-15 06:25:58 所属栏目:百科 来源:网络整理
导读:?? 转载请注明出处: http://www.jb51.cc/article/p-kofkkjra-gp.html 之前的博文讲了怎么实现线程、锁、内存分配、日志等功能的跨平台。Libevent最重要的跨平台功能还是实现了多路IO接口的跨平台(即Reactor模式)。这使得用户可以在不同的平台使用统一的接口
??
转载请注明出处:http://www.52php.cn/article/p-kofkkjra-gp.html



之前的博文讲了怎么实现线程、锁、内存分配、日志等功能的跨平台。Libevent最重要的跨平台功能还是实现了多路IO接口的跨平台(即Reactor模式)。这使得用户可以在不同的平台使用统一的接口。这篇博文就是来讲解Libevent是怎么实现这一点的。

Libevent在实现线程、内存分配、日志时,都是使用了函数指针和全局变量。在实现多路IO接口上时,Libevent也采用了这种方式,不过还是有点差别的。


相关结构体:

现在来看一下event_base结构体,下面代码只列出了本文要讲的内容:

[cpp] view plain copy print ?
  1. //event-internal.h文件
  2. structevent_base{
  3. conststructeventop*evsel;
  4. void*evbase;
  5. };
  6. structeventop{
  7. constchar*name;//多路IO复用函数的名字
  8. void*(*init)(structevent_base*);
  9. int(*add)(structevent_base*,evutil_socket_tfd,shortold,shortevents,void*fdinfo);
  10. int(*del)(structevent_base*,void*fdinfo);
  11. int(*dispatch)(structevent_base*,structtimeval*);
  12. void(*dealloc)(structevent_base*);
  13. intneed_reinit;//是否要重新初始化
  14. //多路IO复用的特征。参考http://blog.csdn.net/luotuo44/article/details/38443569
  15. enumevent_method_featurefeatures;
  16. size_tfdinfo_len;//额外信息的长度。有些多路IO复用函数需要额外的信息
  17. };
//event-internal.h文件
struct event_base {
	const struct eventop *evsel;
	void *evbase;

	…
};
	struct eventop {
	const char *name; //多路IO复用函数的名字

	void *(*init)(struct event_base *);

	int (*add)(struct event_base *,evutil_socket_t fd,short old,short events,void *fdinfo);
	int (*del)(struct event_base *,void *fdinfo);
	int (*dispatch)(struct event_base *,struct timeval *);
	void (*dealloc)(struct event_base *);

	int need_reinit; //是否要重新初始化
	//多路IO复用的特征。参考http://blog.csdn.net/luotuo44/article/details/38443569
	enum event_method_feature features;
	size_t fdinfo_len; //额外信息的长度。有些多路IO复用函数需要额外的信息
};

可以看到event_base结构体中有一个struct eventop类型指针。而这个struct eventop结构体的成员就是一些函数指针。名称也像一个多路IO复用函数应该有的操作:add可以添加fd,del可以删除一个fd,dispatch可以进入监听。明显只要给event_base的evsel成员赋值就能使用对应的多路IO复用函数了。

选择后端:


可供选择的后端:

现在来看一下有哪些可以用的多路IO复用函数。其实在Libevent的源码目录中,已经为每一个多路IO复用函数专门创建了一个文件,如select.c、poll.c、epoll.c、kqueue.c等。

打开这些文件就可以发现在文件的前面都会声明一些多路IO复用的操作函数,而且还会定义一个struct eventop类型的全局变量。如下面代码所示:

[cpp] view plain copy print ?
  1. //select.c文件
  2. staticvoid*select_init(structevent_base*);
  3. staticintselect_add(structevent_base*,int,void*);
  4. staticintselect_del(structevent_base*,void*);
  5. staticintselect_dispatch(structevent_base*,structtimeval*);
  6. staticvoidselect_dealloc(structevent_base*);
  7. conststructeventopselectops={
  8. "select",
  9. select_init,
  10. select_add,
  11. select_del,
  12. select_dispatch,
  13. select_dealloc,
  14. 0,/*doesn'tneedreinit.*/
  15. EV_FEATURE_FDS,
  16. };
  17. //poll.c文件
  18. staticvoid*poll_init(structevent_base*);
  19. staticintpoll_add(structevent_base*,void*_idx);
  20. staticintpoll_del(structevent_base*,void*_idx);
  21. staticintpoll_dispatch(structevent_base*,structtimeval*);
  22. staticvoidpoll_dealloc(structevent_base*);
  23. conststructeventoppollops={
  24. "poll",
  25. poll_init,
  26. poll_add,
  27. poll_del,
  28. poll_dispatch,
  29. poll_dealloc,
  30. 0,/*doesn'tneed_reinit*/
  31. EV_FEATURE_FDS,
  32. sizeof(structpollidx),
  33. };
//select.c文件
static void *select_init(struct event_base *);
static int select_add(struct event_base *,int,void*);
static int select_del(struct event_base *,void*);
static int select_dispatch(struct event_base *,struct timeval *);
static void select_dealloc(struct event_base *);

const struct eventop selectops = {
	"select",select_init,select_add,select_del,select_dispatch,select_dealloc,/* doesn't need reinit. */
	EV_FEATURE_FDS,};

//poll.c文件
static void *poll_init(struct event_base *);
static int poll_add(struct event_base *,void *_idx);
static int poll_del(struct event_base *,void *_idx);
static int poll_dispatch(struct event_base *,struct timeval *);
static void poll_dealloc(struct event_base *);

const struct eventop pollops = {
	"poll",poll_init,poll_add,poll_del,poll_dispatch,poll_dealloc,/* doesn't need_reinit */
	EV_FEATURE_FDS,sizeof(struct pollidx),};

如何选定后端:

看到这里,读者想必已经知道,只需将对应平台的多路IO复用函数的全局变量赋值给event_base的evsel变量即可。可是怎么让Libevent根据不同的平台选择不同的多路IO复用函数呢?另外像大部分OS都会实现select、poll和一个自己的高效多路IO复用函数。怎么从多个中选择一个呢?下面看一下Libevent的解决方案吧:

[cpp] view plain copy print ?
  1. //event.c文件
  2. #ifdef_EVENT_HAVE_EVENT_PORTS
  3. externconststructeventopevportops;
  4. #endif
  5. #ifdef_EVENT_HAVE_SELECT
  6. externconststructeventopselectops;
  7. #endif
  8. #ifdef_EVENT_HAVE_POLL
  9. externconststructeventoppollops;
  10. #endif
  11. #ifdef_EVENT_HAVE_EPOLL
  12. externconststructeventopepollops;
  13. #endif
  14. #ifdef_EVENT_HAVE_WORKING_KQUEUE
  15. externconststructeventopkqops;
  16. #endif
  17. #ifdef_EVENT_HAVE_DEVPOLL
  18. externconststructeventopdevpollops;
  19. #endif
  20. #ifdefWIN32
  21. externconststructeventopwin32ops;
  22. #endif
  23. /*Arrayofbackendsinorderofpreference.*/
  24. staticconststructeventop*eventops[]={
  25. #ifdef_EVENT_HAVE_EVENT_PORTS
  26. &evportops,
  27. #endif
  28. #ifdef_EVENT_HAVE_WORKING_KQUEUE
  29. &kqops,
  30. #endif
  31. #ifdef_EVENT_HAVE_EPOLL
  32. &epollops,
  33. #endif
  34. #ifdef_EVENT_HAVE_DEVPOLL
  35. &devpollops,
  36. #endif
  37. #ifdef_EVENT_HAVE_POLL
  38. &pollops,
  39. #endif
  40. #ifdef_EVENT_HAVE_SELECT
  41. &selectops,
  42. #endif
  43. #ifdefWIN32
  44. &win32ops,
  45. #endif
  46. NULL
  47. };
//event.c文件
#ifdef _EVENT_HAVE_EVENT_PORTS
extern const struct eventop evportops;
#endif
#ifdef _EVENT_HAVE_SELECT
extern const struct eventop selectops;
#endif
#ifdef _EVENT_HAVE_POLL
extern const struct eventop pollops;
#endif
#ifdef _EVENT_HAVE_EPOLL
extern const struct eventop epollops;
#endif
#ifdef _EVENT_HAVE_WORKING_KQUEUE
extern const struct eventop kqops;
#endif
#ifdef _EVENT_HAVE_DEVPOLL
extern const struct eventop devpollops;
#endif
#ifdef WIN32
extern const struct eventop win32ops;
#endif

/* 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
};

它根据宏定义判断当前的OS环境是否有某个多路IO复用函数。如果有,那么就把与之对应的struct eventop结构体指针放到一个全局数组中。有了这个数组,现在只需将数组的某个元素赋值给evsel变量即可。因为是条件宏,在编译器编译代码之前完成宏的替换,所以是可以这样定义一个数组的。关于这些检测当前OS环境的宏,可以参考《event-config.h指明所在系统的环境》。

从数组的元素可以看到,低下标存的是高效多路IO复用函数。如果从低到高下标选取一个多路IO复用函数,那么将优先选择高效的。


具体实现:

现在看一下Libevent是怎么选取一个多路IO复用函数的:
[cpp] view plain copy print ?
  1. //event.c文件
  2. structevent_base*
  3. event_base_new_with_config(conststructevent_config*cfg)
  4. {
  5. inti;
  6. structevent_base*base;
  7. intshould_check_environment;
  8. //分配并清零event_base内存.event_base里的所有成员都会为0
  9. if((base=mm_calloc(1,sizeof(structevent_base)))==NULL){
  10. event_warn("%s:calloc",__func__);
  11. returnNULL;
  12. }
  13. ...
  14. should_check_environment=
  15. !(cfg&&(cfg->flags&EVENT_BASE_FLAG_IGNORE_ENV));
  16. //遍历数组的元素
  17. for(i=0;eventops[i]&&!base->evbase;i++){
  18. if(cfg!=NULL){
  19. /*determineifthisbackendshouldbeavoided*/
  20. if(event_config_is_avoided_method(cfg,
  21. eventops[i]->name))
  22. continue;
  23. if((eventops[i]->features&cfg->require_features)
  24. !=cfg->require_features)
  25. continue;
  26. }
  27. /*alsoobeytheenvironmentvariables*/
  28. if(should_check_environment&&
  29. event_is_method_disabled(eventops[i]->name))
  30. continue;
  31. //找到了一个满足条件的多路IO复用函数
  32. base->evsel=eventops[i];
  33. //初始化evbase,后面会说到
  34. base->evbase=base->evsel->init(base);
  35. }
  36. if(base->evbase==NULL){
  37. event_warnx("%s:noeventmechanismavailable",
  38. __func__);
  39. base->evsel=NULL;
  40. event_base_free(base);
  41. returnNULL;
  42. }
  43. ....
  44. return(base);
  45. }
//event.c文件
struct event_base *
event_base_new_with_config(const struct event_config *cfg)
{
	int i;
	struct event_base *base;
	int should_check_environment;

	//分配并清零event_base内存. event_base里的所有成员都会为0
	if ((base = mm_calloc(1,sizeof(struct event_base))) == NULL) {
		event_warn("%s: calloc",__func__);
		return NULL;
	}	

	...
	should_check_environment =
	    !(cfg && (cfg->flags & EVENT_BASE_FLAG_IGNORE_ENV));	
        //遍历数组的元素
	for (i = 0; eventops[i] && !base->evbase; i++) {
		if (cfg != NULL) {
			/* determine if this backend should be avoided */
			if (event_config_is_avoided_method(cfg,eventops[i]->name))
				continue;
			if ((eventops[i]->features & cfg->require_features)
			    != cfg->require_features)
				continue;
		}

		/* also obey the environment variables */
		if (should_check_environment &&
		    event_is_method_disabled(eventops[i]->name))
			continue;

		//找到了一个满足条件的多路IO复用函数
		base->evsel = eventops[i];

		//初始化evbase,后面会说到
		base->evbase = base->evsel->init(base);
	}

	if (base->evbase == NULL) {
		event_warnx("%s: no event mechanism available",__func__);
		base->evsel = NULL;
		event_base_free(base);
		return NULL;
	}

	....

	return (base);
}

可以看到,首先从eventops数组中选出一个元素。如果设置了event_config,那么就对这个元素(即多路IO复用函数)特征进行检测,看其是否满足event_config所描述的特征。关于event_config,可以查看《多路IO复用函数的选择配置》。


后端数据存储结构体:

在本文最前面列出的event_base结构体中,除了evsel变量外,还有一个evbase变量。这也是一个很重要的变量,而且也是用于跨平台的。

像select、poll、epoll之类多路IO复用函数在调用时要传入一些数据,比如监听的文件描述符fd,监听的事件有哪些。在Libevent中,这些数据都不是保存在event_base这个结构体中的,而是存放在evbase这个指针指向的一个结构体中。


IO复用结构体:

由于不同的多路IO复用函数需要使用不同格式的数据,所以Libevent为每一个多路IO复用函数都定义了专门的结构体(即结构体是不同的),本文姑且称之为IO复用结构体。evbase指向的就是这些结构体。由于这些结构体是不同的,所以要用一个void类型指针。

在select.c、poll.c这类文件中都定义了属于自己的IO复用结构体,如下面代码所示:
[cpp] view plain copy print ?
  1. //select.c文件
  2. structselectop{
  3. intevent_fds;/*Highestfdinfdset*/
  4. intevent_fdsz;
  5. intresize_out_sets;
  6. fd_set*event_readset_in;
  7. fd_set*event_writeset_in;
  8. fd_set*event_readset_out;
  9. fd_set*event_writeset_out;
  10. };
  11. //poll.c文件
  12. structpollop{
  13. intevent_count;/*Highestnumberalloc*/
  14. intnfds;/*Highestnumberused*/
  15. intrealloc_copy;/*Trueiffwemustrealloc
  16. *event_set_copy*/
  17. structpollfd*event_set;
  18. structpollfd*event_set_copy;
  19. };
//select.c文件
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.c文件
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;
};

前面event_base_new_with_config的代码中,有下面一行代码:
[cpp] view plain copy print ?
  1. base->evbase=base->evsel->init(base);
base->evbase = base->evsel->init(base);
明显这行代码就是用来赋值evbase的。下面是poll对应的init函数:
[cpp] view plain copy print ?
  1. //poll.c文件
  2. staticvoid*
  3. poll_init(structevent_base*base)
  4. {
  5. structpollop*pollop;
  6. if(!(pollop=mm_calloc(1,sizeof(structpollop))))
  7. return(NULL);
  8. evsig_init(base);//其他的一些初始化
  9. return(pollop);
  10. }
//poll.c文件
static void *
poll_init(struct event_base *base)
{
	struct pollop *pollop;

	if (!(pollop = mm_calloc(1,sizeof(struct pollop))))
		return (NULL);

	evsig_init(base);//其他的一些初始化

	return (pollop);
}

经过上面的一些处理后,Libevent在特定的OS下能使用到特定的多路IO复用函数。在之前博文中说到的evmap_io_add和evmap_signal_add函数中都会调用evsel->add。由于在新建event_base时就选定了对应的多路IO复用函数,给evsel、evbase变量赋值了,所以evsel->add能把对应的fd和监听事件加到对应的IO复用结构体保存。比如poll的add函数在一开始就有下面一行代码:

[cpp] view plain copy print ?
  1. structpollop*pop=base->evbase;
struct pollop*pop = base->evbase;

当然,poll的其他函数在一开始时也是会有这行代码的,因为要使用到fd和对应的监听事件等数据,就必须要获取那个IO复用结构体。

由于有evsel和evbase这个两个指针变量,当初始化完成之后,再也不用担心具体使用的多路IO复用函数是哪个了。evsel结构体的函数指针提供了统一的接口,上层的代码要使用到多路IO复用函数的一些操作函数时,直接调用evsel结构体提供的函数指针即可。也正是如此,Libevent实现了统一的跨平台Reactor接口。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读