libev库的框架解析
原文地址: https://blog.****.net/mdpmvpmao/article/details/46011639
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事件的初始化
- #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)
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;
- }
- 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 */
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));
- }
- 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
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