1 前言

学习源代码该从哪里入手?我觉得从程序的基本使用场景和代码的整体处理流程入手是个不错的方法,至少从个人的经验上讲,用此方法分析 libevent 是比较有效的。

2 基本应用场景

基本应用场景也是使用 libevnet 的基本流程,下面来考虑一个最简单的场景,使用

livevent 设置定时器,应用程序只需要执行下面几个简单的步骤即可。

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)

3 实例代码

上面例子的程序代码如下所示

4 事件处理流程

当应用程序向 libevent 注册一个事件后, libevent 内部是怎么样进行处理的呢?下面的

图就给出了这一基本流程。

1) 首先应用程序准备并初始化 event,设置好事件类型和回调函数;这对应于前面第步骤2 和 3;

2) 向 libevent 添加该事件 event。对于定时事件, libevent 使用一个小根堆管理, key 为超时时间;对于 Signal 和 I/O 事件, libevent 将其放入到等待链表(wait list)中,这是一

个双向链表结构;

3) 程序调用 event_base_dispatch()系列函数进入无限循环,等待事件,以 select()函数为例;

1,每次循环前 libevent 会检查定时事件的最小超时时间 tv,根据 tv 设置 select()的最大等

待时间,以便于后面及时处理超时事件;

2,当 select()返回后,首先检查超时事件,然后检查 I/O 事件;

3,Libevent 将所有的就绪事件,放入到激活链表中;

4,然后对激活链表中的事件,调用事件的回调函数执行事件处理

5 小结

本文介绍了 libevent 的简单实用场景,并旋风般的介绍了 libevent 的事件处理流程,读者应该对 libevent 有了基本的印象,下面将会详细介绍 libevent 的事件管理框架(Reactor 模

式中的 Reactor 框架)做详细的介绍,在此之前会对源代码文件做简单的分类。

本文原创,转载标明出处。

作者:cpp软件架构狮

感谢各位支持,点击屏幕右上角的【关注】每天文章不落下。感激不尽!

本头条号文章分类目录(精心整理)

相关文章