Libevent 学习及源码剖析(一)
libevent概述
Libevent 是一个用C语言编写的、轻量级的开源高性能事件通知库,主要有以下几个亮点:事件驱动( event-driven),高性能;轻量级,专注于网络,不如 ACE 那么臃肿庞大;源代码相当精炼、易读;跨平台,支持 Windows、 Linux、 *BSD 和 Mac Os;支持多种 I/O 多路复用技术, epoll、 poll、 dev/poll、 select 和 kqueue 等;支持 I/O,定时器和信号等事件;注册事件优先级。
Libevent 已经被广泛的应用,作为底层的网络库;比如 memcached、 Vomit、 Nylon、 Netchat等等。
链接-来自百度百科: https://baike.baidu.com/item/libevent/.
libevent下载和安装
github:https://github.com/libevent/libevent
官网:https://libevent.org/
安装:tar -zxvf libevent-2.1.11-stable.tar.gz cd
libevent-2.1.11-stable ./configure --prefix=/usr/local/ (你需要的指定路径)
sudo make && make install
注:运行是出现找不到libevent.so库的情况,这是链接时没有将你的链接库添加进去
可以参考:https://blog.csdn.net/mybelief321/article/details/9099659
libevent源码组织架构
- 头文件 :主要就是event.h :事件宏定义,接口函数声明,主体结构体event的声明
- 内部头文件:xxx-internal.h,内部的数据结构,对外界不可见,以达到信息隐藏的目的
- libevent框架:event.c 对整体框架的实现
- 对IO复用的封装:epoll.c/select.c/devpoll/kequeue.c
- 定时事件管理:min-heap.h 一个以时间作为key的小根堆的结构
- 信号管理:signal.c:对信号的处理
- 辅助功能函数:evutil.h/evutil.c:包括创建socket pair和一些时间操作函数:加减和比较等等
- 日志:log.h/log.c : log日志函数
- 缓冲区管理:evbuffer.c / buffer.c : libevent对缓冲区的封装
- 基本数据结构:compat/sys : queue.h是libevent基本数据结构的实现,包括链表,双向链表,队列等等;_libevent_time.h : 用于时间操作的结构体定义函数和宏定义
- 实用网络库:http/evdns : 基于libevent实现的http服务器和异步dns查询库
Reactor模式
组成:事件源、框架部分(Reactor)、事件多路分发机制(event demultiplexing)、事件处理程序(event handler)。
事件源:Linux 上的文件描述符,程序指定的句柄上注册关心的事件,比如IO事件
event demultiplexer——事件多路分发机制
由操作系统提供的I/O多路复用机制,比如select和epoll。
程序首先将其关心的句柄(事件源)及其事件注册到event demultiplexer上;
当有事件到达时,event demultiplexer会发出通知“在已经注册的句柄集中,一个或多个句柄的事件已经就绪”;
程序收到通知后,就可以在非阻塞的情况下对事件进行处理了。
对应到libevent中,依然是select、poll、epoll等,但是libevent使用结构体eventop进行了封装,以统一的接口来支持这些I/O多路复用机制,达到了对外隐藏底层系统机制的目的。
Reactor——反应器
Reactor,是事件管理的接口,内部使用event demultiplexer注册、注销事件;并运行事件循环,当有事件进入“就绪”状态时,调用注册事件的回调函数处理事件。
对应到libevent中,就是event_base结构体。
一个典型的Reactor声明方式
class Reactor
{
public:
int register_handler(Event_Handler *pHandler, int event);
int remove_handler(Event_Handler *pHandler, int event);
void handle_events(timeval *ptv);
// ...
};
Event Handler——事件处理程序
事件处理程序提供了一组接口,每个接口对应了一种类型的事件,供Reactor在相应的事件发生时调用,执行相应的事件处理。通常它会绑定一个有效的句柄。
对应到libevent中,就是event结构体。
下面是两种典型的Event Handler类声明方式,二者互有优缺点。
class Event_Handler
{
public:
virtual void handle_read() = 0;
virtual void handle_write() = 0;
virtual void handle_timeout() = 0;
virtual void handle_close() = 0;
virtual HANDLE get_handle() = 0;
// ...
};
class Event_Handler
{
public:
// events maybe read/write/timeout/close .etc
virtual void handle_events(int events) = 0;
virtual HANDLE get_handle() = 0;
// ...
};
EPOLL反应堆详解(libevent核心)
- 第一步,epoll反应堆模型雏型 ----- epoll模型
epoll模型和epoll接口本质区别在于epoll模型传入联合体的是一个自定义结构体指针,该结构体的基本结构包括
struct my_events {
int m_fd; //监听的文件描述符
void *m_arg; //泛型参数
void (*call_back)(void *arg); //回调函数
/*
* 你可以在此处封装更多的数据内容
* 例如用户缓冲区、节点状态、节点上树时间等等
*/
int Status; //1 代表被监听(添加到RBTree), 0 代表没有被监听
char buf[BUFLEN];
long last_active;//记录最后一次响应时间,做超时处理
};
/*
* 注意:用户需要自行开辟空间存放my_events类型的数组,并在每次上树前用epoll_data_t里的
* ptr指向一个my_events元素。
*/
2.epoll_wait()返回直接调用事件中对应的回调函数,就像这样
/*
* -[ epoll模型使用描述01 ]-
*/
while(1) {
/* 监听红黑树, 1秒没事件满足则返回0 */
int n_ready = epoll_wait(ep_fd, events, MAX_EVENTS, 1000);
if (n_ready > 0) {
for (i=0; i<n_ready; i++)
events[i].data.ptr->call_back(/* void *arg */);
}
else
/*
* (3) 这里可以做很多很多其他的工作,例如定时清除没读完的不要的数据
* 也可以做点和数据库有关的设置
* 玩大点你在这里搞搞分布式的代码也可以
*/
}
到了这里,也将是epoll的最终成型,如果从前面到这里你都明白了,epoll的知识你已经十之七八了
让我们先回想以下epoll模型的那张图,我们来理一理思路。
(1) 程序设置边沿触发以及每一个上树的文件描述符设置非阻塞
(2) 调用epoll_create()创建一个epoll对象
(3) 调用epoll_ctl()向epoll对象中进行增加、删除等操作
上树的文件描述符与之对应的结构体,该结构体应该满足填充事件与自定义结构体ptr,此时,监听的事件与回调函数已经确定了对吧?
(4) 调用epoll_wait()(定时检测) 返回待处理的事件集合。
(5) 依次调用事件集合中的每一个元素中的ptr所指向那个结构体中的回调函数。
以上为雏形版本,那么epoll反应堆模型还要比这个雏形版本多了什么呢?
请看第三步的粗体字,当我们把描述符和自定义结构体上树以后,如果放的是监听可读事件并做其对应的回调操作。也就是说,它将一直作为监听可读事件而存在。
其流程是:
监听可读事件(ET) ⇒ 数据到来 ⇒ 触发事件 ⇒ epoll_wait()返回 ⇒ 处理回调 ⇒ 继续epoll_wait() ⇒ 直到程序停止前都是这么循环
那么接下来升级为成型版epoll反应堆模型
其流程是:
监听可读事件(ET) ⇒ 数据到来 ⇒ 触发事件 ⇒ epoll_wait()返回 ⇒
读取完数据(可读事件回调函数内) ⇒ 将该节点从红黑树上摘下(可读事件回调函数内) ⇒ 设置可写事件和对应可写回调函数(可读事件回调函数内) ⇒ 挂上树(可读事件回调函数内) ⇒ 处理数据(可读事件回调函数内)
⇒ 监听可写事件(ET) ⇒ 对方可读 ⇒ 触发事件 ⇒ epoll_wait()返回 ⇒
写完数据(可写事件回调函数内) ⇒ 将该节点从红黑树上摘下(可写事件回调函数内) ⇒ 设置可读事件和对应可读回调函数(可写读事件回调函数内) ⇒ 挂上树(可写事件回调函数内) ⇒ 处理收尾工作(可写事件回调函数内) ⇒ 直到程序停止前一直这么交替循环
————————————————
https://blog.csdn.net/qq_36359022/article/details/81355897