你的项目为什么使用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结构体。
二、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的巧妙设计确实值得我们好好学习啊~_~_~_~