1.简单的介绍一下libevent
2.libevent的工作流程
3.基本的使用以及相关函数的参数
4.对信号事件的监听管理
5.对定时器事件的监听管理
libevent:
libevent是一个轻量级的基于事件驱动的高性能的开源网络库,并且支持多个平台,对多个平台的I/O复用技术进行了封装,当我们编译库的代码时,编译的脚本将会根据OS支持的处理事件机制,来编译相应的代码,从而在libevent接口上保持一致。
编程简单,libevent的框架和要处理的事件的本身没有多大联系
Reactor模式的组成:
1.要处理的对象,一般指的是文件描述符
2.事件多路分发器,即IO复用函数
3.事件/文件描述符对应的回调函数
4.核心:循环的注册、注销、处理事件
基本的使用:
1.
struct event_base *base =event_base_new();
初始化一个base,之后所有的事件将添加到base中;
2.struct event*ev_sock = event_new(base,fd,EV_READ|EV_PERSIST,cb,(void*)base);
创建一个事件,参数分别表示:
base:事件将要添加的目标base
fd:文件描述符
EV_READ|EVPERSIST:事件的属性 ,这里是可读事件并且注册为永久性事件
cb:回调函数的入口地址
最后一个参数时传给cb回调函数的参数
3.event_add(ev_sock,NULL);
将对应的事件ev_sock添加到base中
4.event_base_dispatch();
事件主循环,相当于while循环
5.定义一个超时事件:
struct timeval tv ={5,0};
struct event*ev_time = evtimer_new(base,time_fun,NNULL);
event_add(ev_time,&tv);
time_fun(...)
{
cout<<time out<<endl;
...
}
libevent的基本工作流程
:
1.首先应用程序准备并且初始化event,并且将event添加到base中进行循环监听;
2.事件分为三种:定时事件、IO事件、signal(信号)事件。对于信号和IO事件,libevent将其放置到等待链表中(双向链表)
3.event_base_disatch()是个死循环,以epoll为例,每次循环都会对定时事件进行检查,根据设置的超时时间设置epoll的最大等待时间,以便于后面及时处理超时时间。
4.当epoll返回后,libevent先检查超时时间,后检查IO事件,然后libevent将所有的就绪事件添加到就绪队列中,放入到激活列表,然后
依次对激活列表的就绪事件调用相应的回调函数。
libevent对应信号事件的监听
:
将信号转换为IO事件进行处理,来统一事件源。
统一事件源的工作原理如下:假如用户要监听SIGINT信号,那么在实现的内部就对SIGINT这个信号设置捕抓函数。此外,在实现的内部还要建立一条管道(pipe),并把这个管道加入到多路IO复用函数中。当SIGINT这个信号发生后,捕抓函数将会被调用。而这个捕抓函数的工作就是往管道写入一个字符(这个字符往往等于所捕抓到信号的信号值)。此时,这个管道就变成是可读的了,多路IO复用函数能检测到这个管道变成可读的了。换言之,多路IO复用函数检测到SIGINT信号的发生,也就完成了对信号的监听工作。
从“统一事件源”的工作原理来看,现在已经完成了对信号的捕抓,已经将该信号的当作IO事件写入到socketpair中了。现在event_base应该已经监听到socketpair可读了,并且会为调用回调函数evsig_cb了
一些具体数据结构:
libevent先进入event_base_loop()主循环,等待已经准备好(可读可写或异常)的事件(通过select_dispatch找出已准备好的文件描述符).当有一个信号产生时,由于这个信号的信号处理函数(总是evsignal_handler())总是会往&event_base->sig.ev_signal_pair[0]写1比特数据(
这是由操作系统调用的,对libevent是透明的,对libevent的用户就更加透明了).此时,根据前面的描述,由于ev_signal_pair[0]与ev_signal_pair[1]是一对全双工管道,所以,ev_signal_pair[1]将变得可读.而&event_base->sig.ev_signal事件的文件描述符正是ev_signal_pair[1],所以libevent可以知道&event_base->sig.ev_signal事件准备好了.为此,&event_base->sig.ev_signal事件被移入反应堆实例下的已就绪事件队列.接着在event_base_loop()的后续部分代码中被处理,通过event_process_active()调用其回调函数,也就是evsignal_cb(),从&event_base->sig.ev_signal_pair[1])读1比特的数据.
[
libevent对于定时器事件的监听:
- event_add()会把一个定时器事件压入到其对应的反应堆实例下的定时器最小堆timeheap中(&ev->base.timeheap).
-
-
回到event_dispatch(),它会调用event_base_loop(),此函数对定时器事件处理如下:
-
view sourceprint?
-
01. //事件主循环
-
02. int
-
03.event_base_loop(structevent_base *base, intflags)
-
04.{
-
05.... //不必多虑的其他代码
-
06.
-
07.done = 0;
-
08. while(!done)
-
09.{
-
10. //检查heap中的timer events,将就绪的timer event从heap上删除,并插入到激活链表中,
-
11. //这意味着base->event_count_active会增加
-
12.timeout_process(base);
-
13.
-
14. //有就绪事件了
-
15. if(base->event_count_active)
-
16.{
-
17. //处理就绪事件吧.
-
18.event_process_active(base);
-
19.}
-
20.}
-
21.}
- 其中,timeout_process()会将已超时的定时器事件插入到反应堆实例下的已就绪事件队列中,接着由event_process_active()处理已就绪事件.event_process_active()代码在上一篇文章中已经介绍过了,这里看一下timeout_process():
·
.
/时间到~~~
02.
//开始处理base里面的定时器堆里的事件鸟.
03.
//检查heap中的timer events,将就绪的timer event从heap上删除,并插入到激活链表中
04.
void
timeout_process(
struct
event_base *base)
05.
{
06.
struct
timeval now;
07.
struct
event *ev;
08.
09.
while
((ev = min_heap_top(&base->timeheap)))
10.
{
11.
//ev超时时间的比现在的时间大,也就是说,这个ev还没有超时,那么while循环结束
12.
if
(evutil_timercmp(&ev->ev_timeout, &now, >))
13.
break
;
14.
15.
//else 意味着 evutil_timercmp(&ev->ev_timeout, &now, <=)为真
16.
//也就说明定时器最小堆的根超时了
17.
18.
//从定时器堆删除
19.
event_del(ev);
20.
21.
//把它插到激活链表吧.
22.
event_active(ev, EV_TIMEOUT, 1);
23.
}