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事件的启动、停止
<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事件的相关存储结构
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。
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事件的启动、停止
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结构
#if EV_MULTIPLICITY
struct 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的后台复用
参考阅读:
陈硕 《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