最近自学libevent事件驱动库,参考的资料为libevent2.2版本以及张亮提供的《Libevent源码深度剖析》,参考资料: http://blog.csdn.net/sparkliang/article/details/4957667, libevent好处之类的就不赘述了,libevent和libiop,redis等一样都是采用事件回调机制,这种模式被称作Reactor模式。正常事件处理流程是应用程序调用某个接口触发某个功能,而Reactor模式需要我们将这些接口和宿主指针(谁调用这些接口)注册在Reactor,在合适的时机Reactor使用宿主指针调用注册好的回调函数。
一: Reactor基本知识
Reactor 模式是编写高性能网络服务器的必备技术之一
,它具有如下的优点:1)响应快
,不必为单个同步时间所阻塞,虽然 Reactor 本身依然是同步的;2)编程相对简单
,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;3)可扩展性
,可以方便的通过增加 Reactor 实例个数来充分利用 CPU 资源;4)可复用性
, reactor 框架本身与具体事件处理逻辑无关,具有很高的复用性;
Reactor模式框架
1) Handle 意思为句柄
,在Linux表示文件描述符,在windows是socket或者handle。
2)EventDemultiplexer 表示事件多路分发机制
,调用系统提供的I/O多路复用机制,比如select,epoll,程序先将关注的句柄注册到EventDemultiplexer上,当有关注的事件
到来时,触发EventDemultiplexer通知程序,程序调用之前注册好的回调函数完成消息相应。对应到 libevent 中,依然是 select、 poll、 epoll 等,但是 libevent 使用结构体eventop进行了 封装,以统一的接口来支持这些 I/O 多路复用机制,达到了对外隐藏底层系统机制的目的。3)Reactor——反应器
Reactor,是事件管理的接口,内部使用 event demultiplexer 注册、注销事件;并运行事件循环,当有事件进入“就绪”状态时,调用注册事件的回调函数处理事件。
对应到 libevent 中,就是 event_base 结构体。一个典型的Reactor声明方式
1 | class Reactor |
4) Event Handler——事件处理程序
事件处理程序提供了一组接口,每个接口对应了一种类型的事件,供 Reactor 在相应的事件发生时调用,执行相应的事件处理。通常它会绑定一个有效的句柄。
对应到 libevent 中,就是 event 结构体。下面是两种典型的 Event Handler 类声明方式, 二者互有优缺点。
1 | class Event_Handler |
二:如何使用libevent库提供的API
1)首先初始化 libevent 库,并保存返回的指针struct event_base * base = event_init();
实际上这一步相当于初始化一个 Reactor 实例;在初始化 libevent 后,就可以注册事件了。
2)设置event属性和回调函数
调用函数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 发生时,调用该函数执行处理,它有三个参数,分别是关注的fd, 关注的事件类型(读/写/信号),回调函数的参数void* arg,调用时由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提供的sample
1 | int |
main函数里调用event_init()初始化一个event_base
,之后调用event_set对event设置了回调函数和读事件关注
,event_add将此事件加入event队列里
,超时设置为空最后调用event_dispatch()进行事件轮训派发
。fifo_read是一个回调函数,格式就是之前说的cb格式
1 | static void |
三:《libevent 代码深度剖析》中对文件组织进行了归类
1)头文主要就是 event.h:事件宏定义、接口函数声明,主要结构体 event 的声明;
2)内部头文件
xxx-internal.h:内部数据结构和函数,对外不可见,以达到信息隐藏的目的;3) libevent 框架
event.c: event 整体框架的代码实现;4)对系统 I/O 多路复用机制的封装
epoll.c:对 epoll 的封装;
select.c:对 select 的封装;
devpoll.c:对 dev/poll 的封装;
kqueue.c:对 kqueue 的封装;5)定时事件管理
min-heap.h:其实就是一个以时间作为 key 的小根堆结构;6)信号管理
signal.c:对信号事件的处理;7)辅助功能函数
evutil.h 和 evutil.c:一些辅助功能函数,包括创建 socket pair 和一些时间操作函数:加、减
和比较等。8)日志
log.h 和 log.c: log 日志函数9)缓冲区管理
evbuffer.c 和 buffer.c: libevent 对缓冲区的封装;10)基本数据结构
compat\sys 下的两个源文件: queue.h 是 libevent 基本数据结构的实现,包括链表,双向链表,
队列等; _libevent_time.h:一些用于时间操作的结构体定义、函数和宏定义;11)实用网络库
http 和 evdns:是基于 libevent 实现的 http 服务器和异步 dns 查询库;
四:event结构知识
下面着重看下event结构体,这是libevent核心结构
1 | struct event { |
ev_active_next: 表示就绪状态的事件链表指针
,当关注的事件就绪后,会把
对应的event放入active的队列里。表示该事件在active队列里的位置
ev_next:表示所有事件队列链表的指针
。表示该事件在所有时间列表的位置。
ev_timeout_pos:用于管理超时
ev_fd:event绑定的socket描述符
ev_events:event关注的事件类型
,它可以是以下3种类型:I/O事件: EV_WRITE和EV_READ
定时事件: EV_TIMEOUT
信号: EV_SIGNAL
辅助选项: EV_PERSIST,表明是一个永久事件
Libevent中的定义为:
1 |
ev_res:记录了当前激活事件的类型
;
ev_flags: libevent 用于标记 event 信息的字段,表明其当前的状态,可能的值有
:
1 |
ev_pri:当前事件的优先级
ev_timeout:超时时间设置
ev_callback:该事件对应的回调函数,和cb类型一样
ev_arg:回调函数用到参数
ev_ncalls:事件就绪执行时,调用 ev_callback 的次数,通常为 1
;ev_pncalls:指针,通常指向 ev_ncalls 或者为 NULL
;
五:libevent对于event的管理和使用
对于event使用流程之前有讲过,需要设置event的属性和回调函数,然后将其加入event队列里。设置event属性和回调函数的api如下
1 | void |
ev:表示event指针
fd:event要关注的socket fd
events:event关注的事件类型(读写I/O,信号,时间事件等)
callback:event就绪后会调用的回调函数
arg:调用回调函数时,函数的参数
该函数内部调用了event_assign完成设置
1 | int |
设置好event属性和回调函数后,需要将event设置到指定的event_base中,因为有可能存在很多event_base。调用如下函数
1 | int |
该函数设置了优先级和隶属于哪个base另外还有一个设置优先级的函数
int event_priority_set(struct event *ev, int pri)
设置event ev的优先级,没什么可说的,注意的一点就是:当ev正处于就绪状态时,不能设置,返回-1
六:event_base结构分析和使用
event_base结构如下
1 | struct event_base { |
evsel:eventop类型的指针,针对不同的模型封装了同一套操作
evbase: 不同模型开辟的数据空间,放到event_base里
。
evbase和evsel配套使用,eventop结构如下
1 | struct eventop { |
eventop封装了epoll, select 等不同网络模型的init,add,deldispatch等回调函数
。
changelist:通知后端改变的列表
。
evsigsel:告诉后台 eventbase用于处理signal事件的函数指针
event_count:eventbase中总共的event数量
event_count_active: eventbase中激活的event数量
event_gotterm:这个参数设置后,一旦我们对事件做了处理,就要终止循环。
event_break:立刻结束循环
event_continue:立刻开启一个新的循环实例
event_running_priority:当前事件队列的优先级。
activequeues:激活的事件队列,priority数字小的先触发。
struct event_io_map io: 用于管理io事件的描述符
struct event_signal_map sigmap: 用于管理signal的描述符
eventqueue:所有被加入event_base的事件组成的队列
event_tv:后台记录运行的时间
timeheap:用小根堆管理超时事件队列
其他的参数不是很了解,以后用到了再琢磨。
libevent提供如下两个函数可以生成eventbase实例
1 | event_init(void) |
到此为止基本的结构介绍完了。
七libevent事件添加/删除/初始化/派发接口分析
1 event_base初始化和模型初始化
先看下初始化,实际上初始化是在event_base_new_with_config函数
中完成的。
1 | struct event_base * |
event_init和eventbase_new都会调用event_base_new_with_config这个函数
.
而base->evsel是不同模型的指针,进而实现调用不同模型的init
2 事件添加注册函数
1 | int |
防止多线程访问出错,加了锁,并且调用了
1 | static inline int |
在evmap_io_add里完成了io事件对于不同网络模型(select/epoll等)的绑定
1 | int |
event_queue_insert()负责将事件插入到对应的链表中
;event_queue_remove()负责将事件从对应的链表中删除
;
1 | void event_queue_insert(struct event_base *base, struct event *ev, |
3删除事件
1 | int |
内部调用了
1 | static inline int |
evmap_io_del 中完成了不同模型del函数调用
1 | int |
4 事件派发
1 | int |
调用evsel->dispatch后将事件放入active队列,然后process_active处理激活队列里的事件
1 | static int |
优先级数字base->nactivequeues 以下的会被先处理
。到目前为止介绍了libevent库的基本api和流程。对于不同的网络模型libevent是如何封装的呢?
八libevent对于网络模型的封装(epoll为例)
epoll基本单元封装
1 | struct epollop { |
epfd:epoll_create 返回的epoll表句柄
events: 表示epoll表监听的epoll_event 队列
nevents:epoll_event队列大小
在介绍epoll封装的一些接口前,先看以下两个结构体定义的对象
1 | static const struct eventop epollops_changelist = { |
结构体对象封装了epoll操作的函数指针,
event_changelist_add表示设置changlist标记位时将事件加入changelist
event_changelist_del表示如果设置了changelist标记位事件从changelist中移除。
1 | const struct eventop epollops = { |
这个结构体对象对应的是没设置changelist标记位时epoll的操作接口接下来看下epoll_init函数
1 | epoll_init(struct event_base *base) |
接下来看下epoll封装的事件注册接口,只看epoll_nochangelist_add
,epoll_changelist_add都是类似的。
1 | static int |
在epoll_apply_one_change中完成事件添加
1 | static int |
删除事件也是类似的,内部调用epoll_apply_onechange知识将事件标记位EV_CHANGE_DEL,到函数内部解析为epoll_del或者epoll_mod
1 | static int |
最后是事件派发
1 | static int |
以上就是livevent对于epoll的基本封装。整个结构体对象epollops在event.c中被赋值给eventops[]的
1 | static const struct eventop *eventops[] = { |
之后在event_base_new_with_config中调用base->evsel = eventops[i];完成不同模型的结构体对象赋值给base的evsel指针的。
目前对于libevent的分析和理解先告一段落,以后会更新对libevent的更深刻认识。