前面对各个模块进行简单的源码分析,这篇从整体的角度对所有知识进行一次梳理,细节的部分不介绍,源码可自行翻阅之前的博文部分。通过总结,对libevent的整体设计和使用思路进行理解。
reactor模式
还是先说reactor模型,libevent采用的是事件回调机制,也就是reactor模式。正常的事件处理是通过应用程序调用某个接口触发某个功能。reactor相似,但是又不同。它需要我们提前将这些接口以及宿主的指针(谁调用这些接口)注册在reactor,然后在过程中reactor会通过宿主指针调用已经注册好的回调函数。通俗来讲,就是你需要先告诉我你想干什么事(接口)以及怎么干这些事(宿主指针),我会在合适的时候去干你想干的事。
reactor的优点不做多说:响应快,因为不会被单个同步时间阻塞;编程简单,不需要复杂的多线程等考虑;可扩展、可复用。
- reactor反应器:事件管理的接口,内部采用event demultiplexer注册注销事件。内部进行事件循环,当检测到就绪状态时,调用注册的事件回调函数处理。libevent中的event_base(),承担了这个功能块。
- event demultiplexer:后续简称为ed,事件多路分发机制,其实就是IO复用,select、epoll等,首先程序要将关心的句柄注册到ed上,当负责的句柄有事件到来时,触发ed通知程序,程序调用注册的回调函数完成处理。libevent中IO复用接口主要是由结构体eventop封装,之前解释过,通过函数指针来完成具体机制对接。
- handle:句柄。也就是linux中的文件描述符等。
- event handle:事件处理程序。提供一组接口给各类型事件,供reactor在回调时调用,一个接口对应一个类型事件,绑定一个句柄。libevent中就是event结构体。
- concrete event handle:关联的处理程序,具体的事件处理程序。
为什么叫reactor:
其实说了这么多,reactor的本质还是不是很清楚。查阅很多资料后,我总结出了自己的理解。
首先,什么是事件驱动。举个例子,我们去点餐,然后点好之后等餐,一般模式就是你不停的问,我的饭好了没。事件驱动就是,你注册到后厨,6号点餐,当6号做完之后,就会通知你,你的餐好了。这就是事件驱动。
其次,reactor为什么被叫做反应堆。就是当一个事件开始驱动的时候,就会陆续驱动其他的事件。就像核反应堆,能产生很大的能量。
以单reactor模式举例:将acceptor注册进去demultiplexer中,当有连接到来时,acceptor被触发,然后注册的handler被触发,获得新的fd,包装成事件,注册到demultiplexer中。然后继续处理。其实就是IO复用加非阻塞编程。为什么非阻塞呢?因为阻塞其实就是等待,阻塞的情况下是不能做其他事情的,比如这个连接阻塞到这里,在处理这个连接的时候就没法处理新到的连接,甚至可能无限等待。这是不可取的。之后还会细述。
这时候再回过头看框架图,就能明白了。其实reactor就是一个大型的代理单元,可以处理很多的请求,只要你注册或者触发,我都能给你监测。
libevent的API
按照我们上面的分析,第一步就是要有一个reactor。所以第一步初始化一个libevent库。
- 初始化一个reactor实例:
struct event_base *base=event_init();也可以是event_base_new啦。初始化一个libevent,也就是reactor模型。 - 设置event和回调函数:
void event_set(struct event *ev, int fd, short event, void (*cb)(int,
short, void *), void *arg);
具体参数不介绍,这个就相当于是event handler,保存为event结构体形式。libevent不会管理这些事件,所以需要应用程序来进行管理。 - 设置event从属base:
event_base_set(base, &ev);将event设置到这个base 上,因为可能存在很多base - 事件添加到事件队列:
event_add(&ev, timeout);相当于注册到reactor事件。 - 循环等待处理:
event_base_dispatch(base);进入reactor工作模式。
这样,libevent就可以使用了,在用之前看起来就只是记住,然后使用。明白原理后,就可以顺着思路自己往下捋了。
源码思路
我试着用自己不成熟以及理论的角度来分析一下源码的思路。
- 我需要一个reactor模型,里面需要注册注销等功能。所以,我先封装一个结构体,起个名吧:叫event_base,因为事件都是基于此触发的。然后怎么注册注销,写个函数。
- 还需要一个event demultiplexer,包装一下吧。结构体eventop,init、add、del、dispatch等。怎么选用合适的IO机制呢,我们可以将函数设置为指针,将IO机制的对应操作以指针指过来就可以了。这样也就完成了接口实现。
- 事件驱动,还需要一个event,同样包装一下,设置什么属性和参数。然后我需要将事件注册到上面去。event_add一下。
- 好,然后进入事件循环,循环时监测什么呢,怎么处理监测到就绪的事件呢?怎么退出循环呢?(exit和break,插播一下,两个推出方式,一个是优雅退出,我等所有结束,退出循环。后者是结束了当前这个就不循环了,即使还有等待事件,直接退出循环。)
源码分类
在我以自己的思路顺完后,其实还是不严谨,很多问题和东西都没有考虑,也没有很明确的模块化。所以参考《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查询库;
(原文链接:https://blog.csdn.net/sparkliang/article/details/4957885)
其实可以看到libevent的核心东西,IO复用的封装,还有为了IO、信号、timer三个事件的统一的努力。
总结
其实,顺着书和博客,以及源码阅读,真的是一切应了那句话:源码之下,无所遁形。
其实在看源码的时候,自己在看一个函数的时候就会想如果自己设计这部分怎么设计,然后看源码的设计,慢慢就能提升自己的设计思想和编程能力。这也是看源码的作用。
也算粗粗略略的看完了,libevent是一个经典的模型,也是reactor的一个使用。
后续可能还有一点点的小更新,比如很多人都研究了libevent的线程安全,之后有兴趣会演技一下。而且这个也是要基于很深的reactor理解的。
next->muduo
看libevent不是为了使用或者什么的,而是要做准备。下一步,就开始muduo的源码阅读了,而muduo也是我真正的最后目标。实战才能提升,后续我会进行muduo的总结和改进。真正进行一场硬战,写在此处勉励自己。加油!