libev库的框架解析

原文地址: https://blog.csdn.net/mdpmvpmao/article/details/46011639


libev库的设计框架简介

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

[cpp]  view plain  copy
  1. while(!done)  
  2. {  
  3. int timeout_ms = max(1000, getNextTimedCa llback();  
  4.  int retVal = ::poll(fds, nfs, timeout_ms);  
  5.  if(retVal < 0){  
  6.  //处理错误   
  7.  } else {  
  8.  // 处理到期的timers,回调用户的timer handler  
  9.  if(retVal > 0){  
  10.   // 处理IO事件,回调用户的IO event handler  
  11.   }  
  12.  }  
  13. }   

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结构体的基类。

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

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

[cpp]  view plain  copy
  1. //内容就是EV_WATCHER宏的内容  
  2. typedef struct ev_watcher  
  3. {  
  4. EV_WATCHER (ev_watcher)  
  5. } ev_watcher;  
  6. //内容就是EV_WATCHER_TIME宏的内容  
  7. typedef struct ev_watcher_time  
  8. {  
  9. EV_WATCHER_TIME (ev_watcher_time)  
  10. } ev_watcher_time;  
  11. //可以理解为一个带有next指针的基类  
  12. typedef struct ev_watcher_list  
  13. {  
  14. EV_WATCHER_LIST (ev_watcher_list)  
  15. } ev_watcher_list;   

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

[cpp]  view plain  copy
  1. //ev_io 封装io事件的"派生类",结构体前部就是宏EV_WATCHER_LIST,fd和events是"派生类"变量  
  2. typedef struct ev_io  
  3. {  
  4. EV_WATCHER_LIST (ev_io)  
  5.   
  6. int fd; /* ro */  
  7. int events; /* ro */  
  8. } ev_io;  
  9.   
  10. //ev_signal 封装信号事件的"派生类",signum是"派生类"变量  
  11. typedef struct ev_signal  
  12. {  
  13. EV_WATCHER_LIST (ev_signal)  
  14.   
  15. int signum; /* ro */  
  16. } ev_signal;  
  17.   
  18. //ev_timer 封装相对定时器事件的"派生类",定时器用堆管理,不需要next指针  
  19. typedef struct ev_timer  
  20. {  
  21. EV_WATCHER_TIME (ev_timer)  
  22.   
  23. ev_tstamp repeat; /* rw */  
  24. } ev_timer;   

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

事件的初始化

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

[cpp]  view plain  copy
  1. #define ev_init(ev,cb_) do {    \  
  2. ((ev_watcher *)(void *)(ev))->active =   \  
  3. ((ev_watcher *)(void *)(ev))->pending = 0;   \  
  4. ev_set_priority ((ev), 0);  \  
  5. ev_set_cb ((ev), cb_);  \  
  6. while (0)  
"派生类"的初始化,例举IO事件、timer事件,如下:
[cpp]  view plain  copy
  1. //IO事件的初始化  
  2. #define ev_io_init(ev,cb,fd,events) \   
  3. do { ev_init ((ev), (cb)); ev_io_set ((ev),(fd),(events)); } while (0)  
  4.   
  5. #define ev_io_set(ev,fd_,events_) \  
  6. do { (ev)->fd = (fd_); (ev)->events = (events_) | EV__IOFDSET; } while (0)  
  7.   
  8. //timer事件的初始化  
  9. #define ev_timer_init(ev,cb,after,repeat) \  
  10. do { ev_init ((ev), (cb)); ev_timer_set ((ev),(after),(repeat)); } while (0)  
  11.   
  12. #define ev_timer_set(ev,after_,repeat_) \  
  13. 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()。

[cpp]  view plain  copy
  1. void ev_start (EV_P_ W w, int active)   
  2. {  
  3. pri_adjust (EV_A_ w); //调整优先级  
  4. w->active = active;  //watcher激活  
  5. ev_ref (EV_A);  //loop的激活引用递增  
  6. }  
  7.   
  8. void ev_stop (EV_P_ W w)   
  9. {  
  10. ev_unref (EV_A); //loop的引用递减  
  11. w->active = 0;  
  12. }  

等待执行列表 pendings

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

[cpp]  view plain  copy
  1. typedef struct  
  2. {  
  3. W w; //events只指这个watcher关注了并且已经发生了的,还没有处理的事件   
  4. int events;   
  5. } ANPENDING;  


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

IO事件的相关存储结构

IO事件存储需要两种结构: ANFD *anfd 和 int *fdchanges 。其中:
[cpp]  view plain  copy
  1. typedef struct  
  2. {  
  3. //typedef ev_watcher_list *WL; 关注同一个fd的事件的watcher链表,一个fd可以有多个watcher监听它的事件  
  4. WL head;  
  5. //watcher链表中所有watcher关注事件的按位与  
  6. unsigned char events;  
  7. //当这个结构体需要重新epoll_ctl则设置,说明关注的事件发生了变化  
  8. unsigned char reify;   
  9. //epoll后端使用,实际发生的事件  
  10. unsigned char emask;   
  11. unsigned char unused;  
  12. } 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事件启动的主要代码
[cpp]  view plain  copy
  1. <pre name="code" class="cpp">void  ev_io_start (EV_P_ ev_io *w)   
  2. {  
  3.   //激活相关   
  4.   ev_start (EV_A_ (W)w, 1);    
  5.   
  6.   //判断当前的anfds能否存放fd, 若不能则重新分配空间  
  7.   array_needsize (ANFD, anfds, anfdmax, fd + 1, array_init_zero);  
  8.   
  9.    //将该事件插入到anfds[fd]的头部,挂一个未处理事件,比如读事件、写事件  
  10.   wlist_add (&anfds[fd].head, (WL)w);       
  11.   //将该fd加入到fdchanges数组里,  
  12.   fd_change (EV_A_ fd, w->events & EV__IOFDSET | EV_ANFD_REIFY);  
  13.   
  14.   //start结束,取消该事件的标记(init时候添加)  
  15.   w->events &= ~EV__IOFDSET;  
  16. }  
 
  IO事件停止的主要代码 
 
[cpp]  view plain  copy
  1. void ev_io_stop (EV_P_ ev_io *w)   
  2. {  
  3. //如果该事件正在pending(等待执行的事件)中,从pending列表中移除该事件。  
  4. //这里的一个技巧是不用真的移除掉(数组删除复杂度O(n)),只要将pending列表对应位置的指针指向一个空事件就可以了。  
  5. clear_pending (EV_A_ (W)w);  
  6. //从链表中删除一个节点  
  7. wlist_del (&anfds[w->fd].head, (WL)w);  
  8. //取消fd的active状态  
  9. ev_stop (EV_A_ (W)w);  
  10. //将fd加到fdchanges数组中,只设置REIFY标记,表示有改动  
  11. //之后事件驱动器扫描fdchanges数组会发现该fd不再监听任何事件,作出相应操作  
  12. fd_change (EV_A_ w->fd, EV_ANFD_REIFY);  
  13. }  

Timer事件的相关存储结构

Timer事件存储结构为ANHE *timers 以及W* rfeeds。其中:
[cpp]  view plain  copy
  1. typedef struct {  
  2. ev_tstamp at;  
  3. WT w;  
  4. } ANHE;  
  5.   
  6. #define ANHE_w(he) (he).w /* access watcher, read-write */  
  7. #define ANHE_at(he) (he).at /* access cached at, read-only */  
  8. #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(): 用当前时间与根节点时间比较,超时则加入到待处理队列中,然后进行堆调整,再次与根节点比较。主体代码如下:
[cpp]  view plain  copy
  1. void timers_reify (EV_P)   
  2. {  
  3. if (timercnt && ANHE_at (timers [HEAP0]) < mn_now)   
  4. {  
  5. do  
  6. {  
  7. ev_timer *w = (ev_timer *)ANHE_w (timers [HEAP0]);  
  8. //如果定时器重复,则w的当前计时+repeat,组成下一个   
  9. if (w->repeat)  
  10. {  
  11. ev_at (w) += w->repeat;  
  12. if (ev_at (w) < mn_now)  
  13. ev_at (w) = mn_now;  
  14. ANHE_at_cache (timers [HEAP0]);  
  15. //向下调整堆,定时器仍然存在该数组中  
  16. downheap (timers, timercnt, HEAP0);  
  17. }  
  18. else  
  19. //该定时器watcher关闭  
  20. ev_timer_stop (EV_A_ w); /* nonrepeating: stop timer */  
  21. //将超时的事件加入rfeeds[]结构中   
  22. feed_reverse (EV_A_ (W)w);   
  23. }  
  24. while (timercnt && ANHE_at (timers [HEAP0]) < mn_now);  
  25. //将超时的事件加入rfeeds[]结构中  
  26. feed_reverse_done (EV_A_ EV_TIMER);  
  27. }  
  28. }  


Timer事件的启动、停止

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

libev的事件调度

libev的ev_loop结构

struct ev_loop结构是该库的主要结构,很多变量都是基于该结构的(ev_loop的具体结构参见 Ev.c)。而EV_MULTIPLICITY是一个条件编译的宏,表明是否支持有多个ev_loop实例存在。一般而言,每个线程中有且仅有一个ev_loop实例。如果整个上层业务都是单线程的,程序中可以不适用EV_MULTIPLICITY宏,则很多变量都是全局变量(具体变量的相关信息参考"ev_vars.h"头文件)。
[cpp]  view plain  copy
  1. #if EV_MULTIPLICITY  
  2. struct ev_loop;  
  3. # define EV_P struct ev_loop *loop /* a loop as sole parameter in a declaration */  
  4. # define EV_P_ EV_P, /* a loop as first of multiple parameters */  
  5. # define EV_A loop /* a loop as sole argument to a function call */  
  6. # define EV_A_ EV_A, /* a loop as first of multiple arguments */  
  7. #else  
  8. # define EV_P void  
  9. # define EV_P_  
  10. # define EV_A  
  11. # define EV_A_  
  12. #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的请求响应式协议,它容易割裂业务逻辑,使其分散于多个回调函数之中。

猜你喜欢

转载自blog.csdn.net/VHeroin/article/details/80673409