libev库的框架解析

来源:互联网 发布:微信扫码 ppt制作软件 编辑:程序博客网 时间:2024/06/12 17:13

libev库的设计框架简介

年初就已经将libev库的源码通读过,初次感觉非常晦涩难懂。但是随着反复的阅读,感觉其设计思想及效率优化方面都非常出色。
libev库不是一个单线程的框架么?怎么应用于大型服务器网络模型中呢?后续通过memcached、muduo等库的阅读。才深入理解了该库的框架模型。libev库不涉及具体的网络编程,而真正的主流网络编程框架需要基于此基础上实现,还需要大量的工作。
在高性能的网络程序中,使用得最广泛的要数“non-blocking IO+IO multiplexing”这种模型(Reactor模式)。
在该模型中,程序基本结构是: 一个事件循环(eventloop),以事件驱动(event-driven)和事件回调方式实现业务的逻辑。

while(!done){int timeout_ms = max(1000, getNextTimedCa llback(); int retVal = ::poll(fds, nfs, timeout_ms); if(retVal < 0){ //处理错误  } else { // 处理到期的timers,回调用户的timer handler if(retVal > 0){  // 处理IO事件,回调用户的IO event handler  } }} 

libev网络库的核心框架可以简单的用上述代码表示,这也可以看作Reactor模式的框架。

网络编程中有很多是事务性的工作,可以提取为公用的框架或库,而用户只需要填上关键的业务逻辑代码,并将回调注册到框架中,就可以实现完整的网络服务,这正是Reactor模式的主要思想。

Reactor模式介绍

在Reactor中,有5个关键的参与者:

描述符(handle):由OS提供,用于识别每一个时间。
同步事件分离器(demultiplexer):是一个函数,用来等待一个或多个事件的发生。调用者会被阻塞,知道分离器分离的描述符集上有事件发生。 Linux上select、poll、epoll等都是经常被使用的。
事件处理器接口(event handler):由一个或多个模板函数组成的接口,这些模板函数描述了应用程序相关的对某个时间的操作。
具体的事件处理器(concrete event handler):事件处理器接口的实现。它实现了应用程序提供的某个服务。每个具体的事件处理器总和一个描述符相关。
Reactor管理器(reactor):定义一些接口,用于应用程序控制事件调度、时间处理器的注册、删除等。它是时间处理器的调度核心。一旦事件发生,Reactor管理器先是分离每个事件,然后调度时间处理器(调用相关模板函数来处理事件)。


 该模式图可以大致对应上述代码段:

同步事件分离器: 即对应代码中的poll(2) 函数【当然,还包括select(2)/epoll(4),在libev中对应的是接口函数backend_poll】;
事件处理器:即上述代码中timer handler/ IO event handler段,用于处理不同的注册事件【libev中事件注册后续将介绍】;
Reactor管理器:即上述代码中整个while循环,用于事件注册、调度、删除等【对应libev中的ev_run( )函数】。

Reactor模式作为网络服务器的常用模型,优点明显,能够提高并发量和吞吐量,对于IO密集的应用是不错的选择。但是基于事件驱动的模型也有其缺陷:它要求事件回调函数必须是非阻塞的;且对于设计网络IO的请求响应式协议,它容易割裂业务逻辑,使其分散于多个回调函数之中。

libev的事件定义

libev的事件结构

libev中支持多种事件,包括IO事件、定时器事件(绝对定时、相对定时)、信号等等。对于每一种事件,都有结构体与之对应。如ev_io、ev_timer、ev_signal、ev_child等。

libev在C中使用结构体布局实现了多态,可以将ev_watcher结构体看做所有ev_TYPE结构体的基类。

/* 该宏为所有ev_TYPE的"基类",为其他事件结构所拥有 */#define EV_WATCHER(type) int active; /* private */ \ /*该watcher是否被激活,加入到loop中*/int pending; /* private */ \ /*该watcher关注的events是否已触发*/EV_DECL_PRIORITY /* private */ \ /*int priority; 优先级,watcher是有优先级的*/EV_COMMON /* rw */ \ /*void *data*/EV_CB_DECLARE (type) /* private */ /*void (*cb)(struct ev_loop *loop, type *w, int revents);回调函数*//*构成事件链表*/#define EV_WATCHER_LIST(type)\EV_WATCHER (type)\struct ev_watcher_list *next; /*该成员变量用于将watcher串起来*/ /*在EV_WATCHER基础上增加时间戳,定义与定时器相关的事件*/#define EV_WATCHER_TIME(type)\EV_WATCHER (type)\ev_tstamp at; /* private */

通过以上的结构,libev定义了公用的结构体(可以理解为"基类")。从上述结构中可以看出,每个事件结构的公有字段包括:事件状态、优先级、参数信息、回调函数等。

//内容就是EV_WATCHER宏的内容typedef struct ev_watcher{EV_WATCHER (ev_watcher)} ev_watcher;//内容就是EV_WATCHER_TIME宏的内容typedef struct ev_watcher_time{EV_WATCHER_TIME (ev_watcher_time)} ev_watcher_time;//可以理解为一个带有next指针的基类typedef struct ev_watcher_list{EV_WATCHER_LIST (ev_watcher_list)} ev_watcher_list; 

对于具体的事件(可以理解为ev_watcher的"派生类"),主要列举常用的IO、定时器、信号事件:

//ev_io 封装io事件的"派生类",结构体前部就是宏EV_WATCHER_LIST,fd和events是"派生类"变量typedef struct ev_io{EV_WATCHER_LIST (ev_io)int fd; /* ro */int events; /* ro */} ev_io;//ev_signal 封装信号事件的"派生类",signum是"派生类"变量typedef struct ev_signal{EV_WATCHER_LIST (ev_signal)int signum; /* ro */} ev_signal;//ev_timer 封装相对定时器事件的"派生类",定时器用堆管理,不需要next指针typedef struct ev_timer{EV_WATCHER_TIME (ev_timer)ev_tstamp repeat; /* rw */} ev_timer; 

每个不同事件都有该类型事件的私有信息,比如IO事件对应的fd、定时器时间的发生时间等。

事件的初始化

事件的初始化也可以看做"基类"的初始化、以及"派生类"的初始化。
"基类"的初始化定义宏 ev_init(ev,cb_)

#define ev_init(ev,cb_) do {\((ev_watcher *)(void *)(ev))->active =\((ev_watcher *)(void *)(ev))->pending = 0;\ev_set_priority ((ev), 0);\ev_set_cb ((ev), cb_);\} while (0)
"派生类"的初始化,例举IO事件、timer事件,如下:
//IO事件的初始化#define ev_io_init(ev,cb,fd,events) \ do { ev_init ((ev), (cb)); ev_io_set ((ev),(fd),(events)); } while (0)#define ev_io_set(ev,fd_,events_) \do { (ev)->fd = (fd_); (ev)->events = (events_) | EV__IOFDSET; } while (0)//timer事件的初始化#define ev_timer_init(ev,cb,after,repeat) \do { ev_init ((ev), (cb)); ev_timer_set ((ev),(after),(repeat)); } while (0)#define ev_timer_set(ev,after_,repeat_) \do { ((ev_watcher_time *)(ev))->at = (after_); (ev)->repeat = (repeat_); } while (0)
从上面各个事件的初始化宏定义可以看出,都是先调用公共的初始化 ev_init(2),然后调用自身的初始化函数ev_TYPE_set()。

libev事件的启动、停止

通过以上对libev事件结构的定义,事件的初始化的分析。即完成了事件的初始设置,其他事件也是类同。下面分别解析IO事件、定时器时间是如何启动的。

事件启动、停止的函数的格式为:ev_TYPE_start( ), ev_TYPE_stop( )。
同样有"基类"的启动、停止。分别为ev_start() ev_stop()。

void ev_start (EV_P_ W w, int active) {pri_adjust (EV_A_ w); //调整优先级w->active = active;//watcher激活ev_ref (EV_A);//loop的激活引用递增}void ev_stop (EV_P_ W w) {ev_unref (EV_A); //loop的引用递减w->active = 0;}

等待执行列表 pendings

pendings为事件等待执行列表(pendings为指针数组,当启动优先级模式,则优先级即为指针数组的数组下标。否则为普通模式),只有当事件触发了,才将事件添加到该列表中。数组结构为:

typedef struct{W w; //events只指这个watcher关注了并且已经发生了的,还没有处理的事件 int events; } ANPENDING;


最终每次的loop循环,在ev_invoke_pending()函数中,会依次调用各个watcher的回调函数,优先级从高到低(如果开启存在优先级模式)。

IO事件的相关存储结构

IO事件存储需要两种结构: ANFD *anfd 和 int *fdchanges 。其中:
typedef struct{//typedef ev_watcher_list *WL; 关注同一个fd的事件的watcher链表,一个fd可以有多个watcher监听它的事件WL head;//watcher链表中所有watcher关注事件的按位与unsigned char events;//当这个结构体需要重新epoll_ctl则设置,说明关注的事件发生了变化unsigned char reify; //epoll后端使用,实际发生的事件unsigned char emask; unsigned char unused;} ANFD;

anfd 存放未注册的事件,且以fd为下标,后面挂接该fd不同的事件(读事件、写事件,分别有各自的回调函数)。
fdchanges 对于启动的fd(ev_io_start( )函数中调用),将fd加入该数组中。该数组存在的目的是:为了每次loop循环时,将fdchanges里面在ev_io_start里面设置记录的这些新事件一个个处理,真正加入epoll里面( 即fd_reify()函数的实现, fd的具现化)。


关于IO事件,重点有两个函数: fd_change(), fd_reify()。
fd_change(): 该函数在ev_io_start(), ev_stop()中调用,通过anfds的reify标志判断是否需要加入 fdchanges数组中。
fd_reify(): 该函数在ev_run()的每轮循环中都会调用。将fdchanges中记录的这些新事件一个个的处理,并调用后端IO复用的backend_modify宏。
这里需要注意fd_reify()中的思想,anfd[fd] 结构体中,还有一个events事件,它是原先的所有watcher 的事件的 "|" 操作,向系统的epoll 从新添加描述符的操作 是在下次事件迭代开始前进行的,当我们依次扫描fdchangs,找到对应的anfd 结构,如果发现先前的events 与 当前所有的watcher 的"|" 操作结果不等,则表示我们需要调用epoll_ctrl 之类的函数来进行更改,反之不做操作。

IO事件的启动、停止

IO事件启动的主要代码
<pre name="code" class="cpp">void  ev_io_start (EV_P_ ev_io *w) {  //激活相关   ev_start (EV_A_ (W)w, 1);    //判断当前的anfds能否存放fd, 若不能则重新分配空间  array_needsize (ANFD, anfds, anfdmax, fd + 1, array_init_zero);   //将该事件插入到anfds[fd]的头部,挂一个未处理事件,比如读事件、写事件  wlist_add (&anfds[fd].head, (WL)w);       //将该fd加入到fdchanges数组里,  fd_change (EV_A_ fd, w->events & EV__IOFDSET | EV_ANFD_REIFY);  //start结束,取消该事件的标记(init时候添加)  w->events &= ~EV__IOFDSET;}
IO事件停止的主要代码
void ev_io_stop (EV_P_ ev_io *w) {//如果该事件正在pending(等待执行的事件)中,从pending列表中移除该事件。//这里的一个技巧是不用真的移除掉(数组删除复杂度O(n)),只要将pending列表对应位置的指针指向一个空事件就可以了。clear_pending (EV_A_ (W)w);//从链表中删除一个节点wlist_del (&anfds[w->fd].head, (WL)w);//取消fd的active状态ev_stop (EV_A_ (W)w);//将fd加到fdchanges数组中,只设置REIFY标记,表示有改动//之后事件驱动器扫描fdchanges数组会发现该fd不再监听任何事件,作出相应操作fd_change (EV_A_ w->fd, EV_ANFD_REIFY);}

Timer事件的相关存储结构

Timer事件存储结构为ANHE *timers 以及W* rfeeds。其中:
typedef struct {ev_tstamp at;WT w;} ANHE;#define ANHE_w(he) (he).w /* access watcher, read-write */#define ANHE_at(he) (he).at /* access cached at, read-only */#define ANHE_at_cache(he) (he).at = (he).w->at /* update at from watcher */
对每一个注册的Timer事件,其存放在timers[]数组中,并以heap结构方式存储在数组中(下面只对2-heap进行分析)。 堆的基本操作包括向上调整堆upheap、向下调整堆downheap。
upheap: 将该节点与其父结点进行比较,如果其值比父结点小,则交换,然后对其父结点重复upheap操作。
downheap:与其两个子节点比较(也可能一个),如果两个子节点中有一个是小于当前节点的值,则交换并重复以上操作,如果两个子节点都小于当前节点的值,则选择最小的那个交换并重复,如果2个子节点都大于等于当前的权,当然不用做任何操作了。
当我们添加一个timer时,我们直接在数组末尾位置添加,然后执行upheap 操作,其复杂度也是O(lgn);当我们删除一个节点,则用最后一个timer替换待删除的timer,然后对新交换的timer进行堆调整。

关于IO事件,重点函数为:timers_reify()。
timers_reify(): 用当前时间与根节点时间比较,超时则加入到待处理队列中,然后进行堆调整,再次与根节点比较。主体代码如下:
void timers_reify (EV_P) {if (timercnt && ANHE_at (timers [HEAP0]) < mn_now) {do{ev_timer *w = (ev_timer *)ANHE_w (timers [HEAP0]);//如果定时器重复,则w的当前计时+repeat,组成下一个 if (w->repeat){ev_at (w) += w->repeat;if (ev_at (w) < mn_now)ev_at (w) = mn_now;ANHE_at_cache (timers [HEAP0]);//向下调整堆,定时器仍然存在该数组中downheap (timers, timercnt, HEAP0);}else//该定时器watcher关闭ev_timer_stop (EV_A_ w); /* nonrepeating: stop timer *///将超时的事件加入rfeeds[]结构中 feed_reverse (EV_A_ (W)w); }while (timercnt && ANHE_at (timers [HEAP0]) < mn_now);//将超时的事件加入rfeeds[]结构中feed_reverse_done (EV_A_ EV_TIMER);}}


Timer事件的启动、停止

Timer事件启动的主体代码
void ev_timer_start (EV_P_ ev_timer *w) {//更新当前时间 ev_at (w) += mn_now; //定时器累加 ++timercnt; //事件激活相关 其中active即为timercnt(timers下标) ev_start (EV_A_ (W)w, timercnt + HEAP0 - 1); array_needsize (ANHE, timers, timermax, ev_active (w) + 1, EMPTY2);ANHE_w (timers [ev_active (w)]) = (WT)w;ANHE_at_cache (timers [ev_active (w)]);//新添加的元素放在数组后面,然后向上调整堆 upheap (timers, ev_active (w));}
Timer事件停止的主体代码
void ev_timer_stop (EV_P_ ev_timer *w) {//清除pendings[]激活事件队列中,关于w的事件clear_pending (EV_A_ (W)w);//清除pendings[]激活事件队列中,关于w的事件int active = ev_active (w);//定时器个数减1(即timer[]数组个数减1)--timercnt;if (expect_true (active < timercnt + HEAP0)) {//timers [active]中的元素用数组最后一个元素替换timers [active] = timers [timercnt + HEAP0]; //比较下标为active处的元素与其父节点的元素,来决定采用向上、向下调整adjustheap (timers, timercnt, active); }ev_at (w) -= mn_now; ev_stop (EV_A_ (W)w);}

libev的事件调度

libev的ev_loop结构

struct ev_loop结构是该库的主要结构,很多变量都是基于该结构的(ev_loop的具体结构参见 Ev.c)。而EV_MULTIPLICITY是一个条件编译的宏,表明是否支持有多个ev_loop实例存在。一般而言,每个线程中有且仅有一个ev_loop实例。如果整个上层业务都是单线程的,程序中可以不适用EV_MULTIPLICITY宏,则很多变量都是全局变量(具体变量的相关信息参考"ev_vars.h"头文件)。
#if EV_MULTIPLICITYstruct ev_loop;# define EV_P struct ev_loop *loop /* a loop as sole parameter in a declaration */# define EV_P_ EV_P, /* a loop as first of multiple parameters */# define EV_A loop /* a loop as sole argument to a function call */# define EV_A_ EV_A, /* a loop as first of multiple arguments */#else# define EV_P void# define EV_P_# define EV_A# define EV_A_#endif
从上面宏定义可知,如果未定义EV_MULTIPLICITY, void ev_io_start (EV_P_ ev_io *w) 会被编译成ev_io_start (ev_io *w) ,否则编译成 ev_io_start(struct ev_loop *loop, ev_io *w)。

libev的事件调度

libev的整个事件都是在ev_run()函数中完成的。该函数相当于Reactor管理器角色,负责事件注册、调度、删除等。ev_run( ) 中代码较多,这里简要给出整个loop的流程图(参照阅读[4]):

libev的后台复用

libev使用函数指针来实现不同的IO复用机制,每个复用机制都要实现init,modify,poll,destroy这些函数,也就是初始化、修改关注事件、等待事件发生、销毁等。具体IO复用机制的选取在loop_init()函数中完成。

参考阅读:

陈硕 《Linux多线程服务器端编程—使用C++网络库》
Reactor结构模式 http://www.cnblogs.com/hzbook/archive/2012/07/19/2599698.html
libev设计分析 https://cnodejs.org/topic/4f16442ccae1f4aa270010a3
libev事件源码阅读笔记 http://www.tuicool.com/articles/Nfy6Bn
ev_io源码分析 http://csrd.aliapp.com/?p=1604



Reactor模式作为网络服务器的常用模型,优点明显,能够提高并发量和吞吐量,对于IO密集的应用是不错的选择。但是基于事件驱动的模型也有其缺陷:它要求事件回调函数必须是非阻塞的;且对于设计网络IO的请求响应式协议,它容易割裂业务逻辑,使其分散于多个回调函数之中。
0 0