libevent源码剖析

来源:互联网 发布:sql cast函数 编辑:程序博客网 时间:2024/06/04 19:17

你的项目为什么使用libevent网络库呢??咳咳~~本篇对libevent进行简单剖析~

首先说几点libevent的显著亮点:
**1、事件驱动,高性能;
2、轻量级,专注于网络;不如ACE那么臃肿庞大;
3、源代码相当精炼、易读;
4、跨平台,支持windows、linux、*BSD和Mac Os;
5、支持多种I/O多路复用技术,epoll、poll、dev/poll、select和kqueue等;
6、支持I/O,定时器和信号等事件;
7、注册事件优先级**

一、Reactor的事件处理机制

(1)Reactor释义“反应堆”,是一种事件驱动机制。

普通函数调用:应用程序主动的调用某个API完成处理。
Reactor:应用程序需要提供相应的接口并注册到Reactor上,如果相应的事件发生,Reactor将主动调用应用程序注册的接口(回调函数)。

使用libevent也是向libevent框架注册相应的事件和回调函数,当这些事件发生时,libevent会调用这些回调函数处理相应的事件(I//O读写、定时和信号)

(2)Reactor模式的优点
Reactor模式是编写高性能网络服务器的必备技术之一,它具有如下的优点:
1)响应快,不必为单个同步时间所阻塞,虽然Reactor本身依然是同步的;
2)编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;
3)可扩展性,可以方便的通过增加Reactor实例个数来充分利用CPU资源;
4)可复用性,reactor框架本身与具体事件处理逻辑无关,具有很高的复用性;

(3)Reactor模式框架
使用Reactor模型,必备的几个组件:事件源、Reactor框架、多路复用机制和事件处理程序,先来看看Reactor模型的整体框架,接下来再对每个组件做逐一说明。
这里写图片描述

1) 事件源
Linux上是文件描述符,Windows上就是Socket或者Handle了,这里统一称为“句柄集”;程序在指定的句柄上注册关心的事件,比如I/O事件。

2) event demultiplexer——事件多路分发机制
由操作系统提供的I/O多路复用机制,比如select和epoll。
程序首先将其关心的句柄(事件源)及其事件注册到event demultiplexer上;
当有事件到达时,event demultiplexer会发出通知“在已经注册的句柄集中,一个或多个句柄的事件已经就绪”;
程序收到通知后,就可以在非阻塞的情况下对事件进行处理了。
对应到libevent中,依然是select、poll、epoll等,但是libevent使用结构体eventop进行了封装,以统一的接口来支持这些I/O多路复用机制,达到了对外隐藏底层系统机制的目的。

3) Reactor——反应器
Reactor,是事件管理的接口,内部使用event demultiplexer注册、注销事件;并运行事件循环,当有事件进入“就绪”状态时,调用注册事件的回调函数处理事件。
对应到libevent中,就是event_base结构体。
一个典型的Reactor声明方式

4) Event Handler——事件处理程序
事件处理程序提供了一组接口,每个接口对应了一种类型的事件,供Reactor在相应的事件发生时调用,执行相应的事件处理。通常它会绑定一个有效的句柄。
对应到libevent中,就是event结构体。

(4)Reactor事件处理流程
这里写图片描述

二、libevent的接口函数
1)首先初始化libevent库,并保存返回的指针
struct event_base * base = event_init();
实际上这一步相当于初始化一个Reactor实例;在初始化libevent后,就可以注册事件了。

2)初始化事件event,设置回调函数和关注的事件
evtimer_set(&ev, timer_cb, NULL);
事实上这等价于调用event_set(&ev, -1, 0, timer_cb, NULL);
event_set的函数原型是:
void event_set(struct event ev, int fd, short event, void (*cb)(int, short, void ), void *arg)
ev:执行要初始化的event对象;
fd:该event绑定的“句柄”,对于信号事件,它就是关注的信号;
event:在该fd上关注的事件类型,它可以是EV_READ, EV_WRITE, EV_SIGNAL;
cb:这是一个函数指针,当fd上的事件event发生时,调用该函数执行处理,它有三个参数,调用时由event_base负责传入,按顺序,实际上就是event_set时的fd, event和arg;
arg:传递给cb函数指针的参数;
由于定时事件不需要fd,并且定时事件是根据添加时(event_add)的超时值设定的,因此这里event也不需要设置。
这一步相当于初始化一个event handler,在libevent中事件类型保存在event结构体中。
注意:libevent并不会管理event事件集合,这需要应用程序自行管理;

3)设置event从属的event_base
event_base_set(base, &ev);
这一步相当于指明event要注册到哪个event_base实例上;

4)是正式的添加事件的时候了
event_add(&ev, timeout);
基本信息都已设置完成,只要简单的调用event_add()函数即可完成,其中timeout是定时值;
这一步相当于调用Reactor::register_handler()函数注册事件。

5)程序进入无限循环,等待就绪事件并执行事件处理
event_base_dispatch(base);

当应用程序向libevent注册一个事件后,libevent内部是怎么样进行处理的呢?下面的图就给出了这一基本流程。
这里写图片描述

1) 首先应用程序准备并初始化event,设置好事件类型和回调函数;这对应于前面第步骤2和3;
2) 向libevent添加该事件event。对于定时事件,libevent使用一个小根堆管理,key为超时时间;对于Signal和I/O事件,libevent将其放入到等待链表(wait list)中,这是一个双向链表结构;
3) 程序调用event_base_dispatch()系列函数进入无限循环,等待事件,以select()函数为例;每次循环前libevent会检查定时事件的最小超时时间tv,根据tv设置select()的最大等待时间,以便于后面及时处理超时事件;
当select()返回后,首先检查超时事件,然后检查I/O事件;
Libevent将所有的就绪事件,放入到激活链表中;
然后对激活链表中的事件,调用事件的回调函数执行事件处理;

三、libevent的核心-event

1.  struct event {  //该I/O事件在链表中的位置;称此链表为“已注册事件链表”;2.   TAILQ_ENTRY (event) ev_next;      //libevent将所有的激活事件放入到链表active list中,然后遍历active list执行调度,ev_active_next就指明了event在active list中的位置;3.   TAILQ_ENTRY (event) ev_active_next;  //signal事件在signal事件链表中的位置;4.   TAILQ_ENTRY (event) ev_signal_next;  5.   unsigned int min_heap_idx; //libevent使用小根堆来管理定时事件6.   struct event_base *ev_base; //该事件所属的反应堆实例7.   int ev_fd; //对于I/O事件,是绑定的文件描述符;对于signal事件,是绑定的信号; 8.   short ev_events;//event关注的事件类型    /*    I/O事件: EV_WRITE和EV_READ    定时事件:EV_TIMEOUT    信号:    EV_SIGNAL    辅助选项:EV_PERSIST,表明是一个永久事件    */9.   short ev_ncalls;  10.  short *ev_pncalls; /* Allows deletes in callback */  11.  struct timeval ev_timeout;  12.  int ev_pri;  /* smaller numbers are higher priority */  13.  void (*ev_callback)(int, short, void *arg); //event的回调函数 14.  void *ev_arg;  15.  int ev_res;  /* result passed to event callback */  16.  int ev_flags;  17. };  

libevent对event的管理方法,可以参见下面的示意图:

这里写图片描述
**每次当有事件event转变为就绪状态时,libevent就会把它移入到active event list[priority]中,其中priority是event的优先级;
接着libevent会根据自己的调度策略选择就绪事件,调用其cb_callback()函数执行事件处理;并根据就绪的句柄和事件类型填充cb_callback函数的参数。**

四、事件处理框架-event_base

1.  struct event_base {  2.   const struct eventop *evsel;  3.   void *evbase;   4.   int event_count;  /* counts number of total events */  5.   int event_count_active; /* counts number of active events */  6.   int event_gotterm;  /* Set to terminate loop */  7.   int event_break;  /* Set to terminate loop immediately */  8.   /* active event management */  9.   struct event_list **activequeues;//其中的元素activequeues[priority]是一个链表,链表的每个节点指向一个优先级为priority的就绪事件event。   10.  int nactivequeues;  11.  /* signal handling info */  12.  struct evsignal_info sig;  13.  struct event_list eventqueue;  //保存了所有的注册事件event的指针。14.  struct timeval event_tv;  15.  struct min_heap timeheap; //管理定时事件的小根堆 16.  struct timeval tv_cache;  17. };  

evsel指向了全局变量static const struct eventop *eventops[]中的一个;
前面也说过,libevent将系统提供的I/O demultiplex机制统一封装成了eventop结构;因此eventops[]包含了select、poll、kequeue和epoll等等其中的若干个全局实例对象。
evbase实际上是一个eventop实例对象;

struct eventop { const char *name; void *(*init)(struct event_base *); // 初始化 int (*add)(void *, struct event *); // 注册事件 int (*del)(void *, struct event *); // 删除事件 int (*dispatch)(struct event_base *, void *, struct timeval *); // 事件分发 void (*dealloc)(struct event_base *, void *); // 注销,释放资源 /* set if we need to reinitialize the event base */ int need_reinit;};

五、Libevent中对应的接口函数

1.  int  event_add(struct event *ev, const struct timeval *timeout);  2.  int  event_del(struct event *ev);  3.  int  event_base_loop(struct event_base *base, int loops);  4.  void event_active(struct event *event, int res, short events);  5.  void event_process_active(struct event_base *base); 

(1)注册事件

1.  int event_add(struct event *ev, const struct timeval *tv)  2.  {  3.   struct event_base *base = ev->ev_base; // 要注册到的event_base  4.   const struct eventop *evsel = base->evsel;  5.   void *evbase = base->evbase; // base使用的系统I/O策略  6.   // 新的timer事件,调用timer heap接口在堆上预留一个位置  7.   // 注:这样能保证该操作的原子性:  8.   // 向系统I/O机制注册可能会失败,而当在堆上预留成功后,  9.   // 定时事件的添加将肯定不会失败;  10.  // 而预留位置的可能结果是堆扩充,但是内部元素并不会改变  11.  if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {  12.   if (min_heap_reserve(&base->timeheap,  13.    1 + min_heap_size(&base->timeheap)) == -1)  14.    return (-1);  /* ENOMEM == errno */  15.  }  16.  // 如果事件ev不在已注册或者激活链表中,则调用evbase注册事件  17.  if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&  18.   !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {  19.    res = evsel->add(evbase, ev);  20.    if (res != -1) // 注册成功,插入event到已注册链表中  21.     event_queue_insert(base, ev, EVLIST_INSERTED);  22.  }  23.  // 准备添加定时事件  24.  if (res != -1 && tv != NULL) {  25.   struct timeval now;  26.   // EVLIST_TIMEOUT表明event已经在定时器堆中了,删除旧的  27.   if (ev->ev_flags & EVLIST_TIMEOUT)  28.    event_queue_remove(base, ev, EVLIST_TIMEOUT);  29.   // 如果事件已经是就绪状态则从激活链表中删除  30.   if ((ev->ev_flags & EVLIST_ACTIVE) &&  31.    (ev->ev_res & EV_TIMEOUT)) {  32.     // 将ev_callback调用次数设置为0  33.     if (ev->ev_ncalls && ev->ev_pncalls) {  34.      *ev->ev_pncalls = 0;  35.     }  36.     event_queue_remove(base, ev, EVLIST_ACTIVE);  37.   }  38.   // 计算时间,并插入到timer小根堆中  39.   gettime(base, &now);  40.   evutil_timeradd(&now, tv, &ev->ev_timeout);  41.   event_queue_insert(base, ev, EVLIST_TIMEOUT);  42.  }  43.  return (res);  44. }  45.    46. event_queue_insert()负责将事件插入到对应的链表中,下面是程序代码;  47. event_queue_remove()负责将事件从对应的链表中删除,这里就不再重复贴代码了;  48. void event_queue_insert(struct event_base *base, struct event *ev, int queue)  49. {  50.  // ev可能已经在激活列表中了,避免重复插入  51.  if (ev->ev_flags & queue) {  52.   if (queue & EVLIST_ACTIVE)  53.    return;  54.  }  55.  // ...  56.  ev->ev_flags |= queue; // 记录queue标记  57.  switch (queue) {  58.  case EVLIST_INSERTED: // I/O或Signal事件,加入已注册事件链表  59.   TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next);  60.   break;  61.  case EVLIST_ACTIVE: // 就绪事件,加入激活链表  62.   base->event_count_active++;  63.   TAILQ_INSERT_TAIL(base->activequeues[ev->ev_pri], ev, ev_active_next);  64.   break;  65.  case EVLIST_TIMEOUT: // 定时事件,加入堆  66.   min_heap_push(&base->timeheap, ev);  67.   break;  68.  }  69. }  

(2)删除事件

1.  int event_del(struct event *ev)  2.  {  3.   struct event_base *base;  4.   const struct eventop *evsel;  5.   void *evbase;  6.   // ev_base为NULL,表明ev没有被注册  7.   if (ev->ev_base == NULL)  8.    return (-1);  9.   // 取得ev注册的event_base和eventop指针  10.  base = ev->ev_base;  11.  evsel = base->evsel;  12.  evbase = base->evbase;  13.  // 将ev_callback调用次数设置为  14.  if (ev->ev_ncalls && ev->ev_pncalls) {  15.   *ev->ev_pncalls = 0;  16.  }  17.    18.  // 从对应的链表中删除  19.  if (ev->ev_flags & EVLIST_TIMEOUT)  20.   event_queue_remove(base, ev, EVLIST_TIMEOUT);  21.  if (ev->ev_flags & EVLIST_ACTIVE)  22.   event_queue_remove(base, ev, EVLIST_ACTIVE);  23.  if (ev->ev_flags & EVLIST_INSERTED) {  24.   event_queue_remove(base, ev, EVLIST_INSERTED);  25.   // EVLIST_INSERTED表明是I/O或者Signal事件,  26.   // 需要调用I/O demultiplexer注销事件  27.   return (evsel->del(evbase, ev));  28.  }  29.  return (0);  30. }  

(3)事件处理主循环

这里写图片描述

1.  int event_base_loop(struct event_base *base, int flags)  2.  {  3.      const struct eventop *evsel = base->evsel;  4.      void *evbase = base->evbase;  5.      struct timeval tv;  6.      struct timeval *tv_p;  7.      int res, done;  8.      // 清空时间缓存  9.      base->tv_cache.tv_sec = 0;  10.     // evsignal_base是全局变量,在处理signal时,用于指名signal所属的event_base实例  11.     if (base->sig.ev_signal_added)  12.         evsignal_base = base;  13.     done = 0;  14.     while (!done) { // 事件主循环  15.         // 查看是否需要跳出循环,程序可以调用event_loopexit_cb()设置event_gotterm标记  16.         // 调用event_base_loopbreak()设置event_break标记  17.         if (base->event_gotterm) {  18.             base->event_gotterm = 0;  19.             break;  20.         }  21.         if (base->event_break) {  22.             base->event_break = 0;  23.             break;  24.         }  25.         // 校正系统时间,如果系统使用的是非MONOTONIC时间,用户可能会向后调整了系统时间  26.         // 在timeout_correct函数里,比较last wait time和当前时间,如果当前时间< last wait time  27.         // 表明时间有问题,这是需要更新timer_heap中所有定时事件的超时时间。  28.         timeout_correct(base, &tv);  29.      30.         // 根据timer heap中事件的最小超时时间,计算系统I/O demultiplexer的最大等待时间  31.         tv_p = &tv;  32.         if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) {  33.             timeout_next(base, &tv_p);  34.         } else {  35.             // 依然有未处理的就绪时间,就让I/O demultiplexer立即返回,不必等待  36.             // 下面会提到,在libevent中,低优先级的就绪事件可能不能立即被处理  37.             evutil_timerclear(&tv);  38.         }  39.         // 如果当前没有注册事件,就退出  40.         if (!event_haveevents(base)) {  41.             event_debug(("%s: no events registered.", __func__));  42.             return (1);  43.         }  44.         // 更新last wait time,并清空time cache  45.         gettime(base, &base->event_tv);  46.         base->tv_cache.tv_sec = 0;  47.         // 调用系统I/O demultiplexer等待就绪I/O events,可能是epoll_wait,或者select等;  48.         // 在evsel->dispatch()中,会把就绪signal event、I/O event插入到激活链表中  49.         res = evsel->dispatch(base, evbase, tv_p);  50.         if (res == -1)  51.             return (-1);  52.         // 将time cache赋值为当前系统时间  53.         gettime(base, &base->tv_cache);  54.         // 检查heap中的timer events,将就绪的timer event从heap上删除,并插入到激活链表中  55.         timeout_process(base);  56.         // 调用event_process_active()处理激活链表中的就绪event,调用其回调函数执行事件处理  57.         // 该函数会寻找最高优先级(priority值越小优先级越高)的激活事件链表,  58.         // 然后处理链表中的所有就绪事件;  59.         // 因此低优先级的就绪事件可能得不到及时处理;  60.         if (base->event_count_active) {  61.             event_process_active(base);  62.             if (!base->event_count_active && (flags & EVLOOP_ONCE))  63.                 done = 1;  64.         } else if (flags & EVLOOP_NONBLOCK)  65.             done = 1;  66.     }  67.     // 循环结束,清空时间缓存  68.     base->tv_cache.tv_sec = 0;  69.     event_debug(("%s: asked to terminate loop.", __func__));  70.     return (0);  71. }  

六、I/O和Timer事件的统一
**首先将Timer事件融合到系统I/O多路复用机制中,因为系统的I/O机制像select()和epoll_wait()都允许程序制定一个最大等待时间(也称为最大超时时间)timeout,即使没有I/O事件发生,它们也保证能在timeout时间内返回。
那么根据所有Timer事件的最小超时时间来设置系统I/O的timeout时间;当系统I/O返回时,再激活所有就绪的Timer事件就可以了,这样就能将Timer事件完美的融合到系统的I/O机制中了。
堆是一种经典的数据结构,向堆中插入、删除元素时间复杂度都是O(lgN),N为堆中元素的个数,而获取最小key值(小根堆)的复杂度为O(1);因此变成了管理Timer事件的绝佳人选(当然是非唯一的),libevent就是采用的堆结构。**

具体的代码在源文件event.c的event_base_loop()中

 if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) {  2.            // 根据Timer事件计算evsel->dispatch的最大等待时间  3.      timeout_next(base, &tv_p);  4.  } else {   5.            // 如果还有活动事件,就不要等待,让evsel->dispatch立即返回  6.      evutil_timerclear(&tv);  7.  }  8.  // ...  9.        // 调用select() or epoll_wait() 等待就绪I/O事件  10. res = evsel->dispatch(base, evbase, tv_p);  11.       // ...  12.       // 处理超时事件,将超时事件插入到激活链表中  13.       timeout_process(base);  

timeout_next()函数根据堆中具有最小超时值的事件和当前时间来计算等待时间,下面看看代码:

1.  static int timeout_next(struct event_base *base, struct timeval **tv_p)  2.  {  3.      struct timeval now;  4.      struct event *ev;  5.      struct timeval *tv = *tv_p;  6.      // 堆的首元素具有最小的超时值  7.      if ((ev = min_heap_top(&base->timeheap)) == NULL) {  8.          // 如果没有定时事件,将等待时间设置为NULL,表示一直阻塞直到有I/O事件发生  9.          *tv_p = NULL;  10.         return (0);  11.     }  12.     // 取得当前时间  13.     gettime(base, &now);  14.     // 如果超时时间<=当前值,不能等待,需要立即返回  15.     if (evutil_timercmp(&ev->ev_timeout, &now, <=)) {  16.         evutil_timerclear(tv);  17.         return (0);  18.     }  19.     // 计算等待的时间=当前时间-最小的超时时间  20.     evutil_timersub(&ev->ev_timeout, &now, tv);  21.     return (0);  22. }  

普通的堆管理代码:

1.  Heap[size++] = new; // 先放到数组末尾,元素个数+1  2.  // 下面就是shift_up()的代码逻辑,不断的将new向上调整  3.  _child = size;  4.  while(_child>0) // 循环  5.  {  6.     _parent = (_child-1)/2; // 计算parent  7.     if(Heap[_parent].key < Heap[_child].key)  8.        break; // 调整结束,跳出循环  9.     swap(_parent, _child); // 交换parent和child  10. }  

而libevent的heap代码对这一过程做了优化,在插入新元素时,只是为新元素预留了一个位置hole(初始时hole位于数组尾部),但并不立刻将新元素插入到hole上,而是不断向上调整hole的值,将父节点向下调整,最后确认hole就是新元素的所在位置时,才会真正的将新元素插入到hole上,因此在调整过程中就比上面的代码少了一次赋值的操作

1.  // 下面就是shift_up()的代码逻辑,不断的将new的“预留位置”向上调整  2.  _hole = size; // _hole就是为new预留的位置,但并不立刻将new放上  3.  while(_hole>0) // 循环  4.  {  5.      _parent = (_hole-1)/2; // 计算parent  6.      if(Heap[_parent].key < new.key)  7.          break; // 调整结束,跳出循环  8.      Heap[_hole] = Heap[_parent]; // 将parent向下调整  9.      _hole = _parent; // 将_hole调整到_parent  10. }  11. Heap[_hole] = new; // 调整结束,将new插入到_hole指示的位置  12. size++; // 元素个数+1  

由于每次调整都少做一次赋值操作,在调整路径比较长时,调整效率会比第一种有所提高

七、I/O和Signal事件的统一

在libevent中这是通过socket pair完成的, Socket pair就是一个socket对,包含两个socket,一个读socket,一个写socket。工作方式如下图所示:
这里写图片描述
如何创建socket pair?
这里写图片描述
Socket pair创建好了,可是libevent的事件主循环还是不知道Signal是否发生了啊,看来我们还差了最后一步,那就是:为socket pair的读socket在libevent的event_base实例上注册一个persist的读事件。
这样当向写socket写入数据时,读socket就会得到通知,触发读事件,从而event_base就能相应的得到通知了。
前面提到过,Libevent会在事件主循环中检查标记,来确定是否有触发的signal,如果标记被设置就处理这些signal,这段代码在各个具体的I/O机制中,以Epoll为例,在epoll_dispatch()函数中,代码片段如下:

1.  res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);  2.  if (res == -1) {  3.      if (errno != EINTR) {  4.          event_warn("epoll_wait");  5.          return (-1);  6.      }  7.      evsignal_process(base);// 处理signal事件  8.      return (0);  9.  } else if (base->sig.evsignal_caught) {  10.     evsignal_process(base);// 处理signal事件  11. }  

这里写图片描述
Libevent中Signal事件的管理是通过结构体evsignal_info完成的,结构体位于evsignal.h文件中,定义如下:

1.  struct evsignal_info {  2.      struct event ev_signal; //为socket pair的读socket向event_base注册读事件时使用的event结构体; 3.      int ev_signal_pair[2];  4.      int ev_signal_added; //记录ev_signal事件是否已经注册了 5.      volatile sig_atomic_t evsignal_caught; //是否有信号发生的标记;是volatile类型,因为它会在另外的线程中被修改; 6.      struct event_list evsigevents[NSIG]; //evsigevents[signo]表示注册到信号signo的事件链表; 7.      sig_atomic_t evsigcaught[NSIG];  //记录信号signo被触发的次数8.  #ifdef HAVE_SIGACTION  9.      struct sigaction **sh_old; //原来的signal处理函数指针 10. #else  11.     ev_sighandler_t **sh_old;  12. #endif  13.     int sh_old_max;  14. };  

evsignal_info的初始化包括,创建socket pair,设置ev_signal事件(但并没有注册,而是等到有信号注册时才检查并注册),并将所有标记置零,初始化信号的注册事件链表指针等。

libevent的巧妙设计确实值得我们好好学习啊~_~_~_~

原创粉丝点击