libevent 是一个轻量级的开源高性能网络库,很多开源软件包括memcached 都是使用他作为网络层组件
他只专注于网络层,采用事件驱动的模式,实现了对 IO 事件、定时器事件、信号事件的处理
同时,他对 windows、linux、BSD 等多个平台的不同接口进行了封装
由于最近在阅读 nginx 源码的事件处理模块,所以对同样是事件驱动模式的 libevent 很感兴趣,打算研读一下他的源码
众所周知,apache 作为应用 web 服务器使用的是同步多进程模型,即在进程池中创建多个进程,当请求到来时,每个进程单独处理一个请求,这样的好处在于进程之间相互独立,服务不会因为某个进程的超时或其他问题而随之出现问题,但是这样的模型占用了大量的系统资源,特别是在大量并发请求到来的时候,大量的进程启动会导致系统资源占用过大
而 nginx 以及 libevent 这样事件驱动模型的异步非阻塞模型的网络层应用,使用的是 Reactor 的模式进行请求的处理,只启动有限的进程,比如 nginx 建议启动与 CPU 核心数目相同的进程数,这样可以有效地利用 CPU 的二级、三级缓存,进一步提高了效率,而同时,所谓的 Reactor 模式,指的是当请求到来时,框架注册相应的时间和回调函数,然后转发给相应的进程处理,如 linux 内核异步 IO、fastcgi 等,待这些进程处理完成后调用已注册的回调函数,完成整个请求的处理
事件模式的好处显而易见,有效地利用资源,也十分节省系统资源,免除了进程、线程创建和管理的开销,处理灵活高效,非常适合用作高并发、能够快速处理的请求服务
但是如果每个请求都需要耗费大量的时长和复杂的计算,这样的场景下,事件驱动模型会因此阻塞,而陷入无法服务,因此,对于 CPU 密集型的应用场景,Apache 这类的应用服务器是最好的选择,但是对于 IO 密集型的应用场景,比如反向代理,nginx 这类的事件驱动模式就显现出了他的强大之处
当然,另一方面,Apache 以 select 作为其网络 IO,也更加适用于有限并发而活跃的应用请求
libevent 的使用也很简单,只需要在编译的时候加上 libevent 的动态链接库即可,-levent,下面是几个例子:
定时器事件
用法如下:
- 调用 event_init 初始化 libevent
- 调用 evtimer_set 设置定时器事件
- 调用 event_add 添加事件
- 使用 event_dispatch 进入事件循环
#include <stdio.h>
#include <iostream>
// libevent头文件
#include <event.h>
using namespace std;
// 定时事件回调函数
void onTime(int sock, short event, void *arg)
{
cout << "Game Over!" << endl;
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
// 重新添加定时事件(定时事件触发后默认自动删除)
event_add((struct event*)arg, &tv);
}
int main()
{
// 初始化
event_init();
struct event evTime;
// 设置定时事件
evtimer_set(&evTime, onTime, &evTime);
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
// 添加定时事件
event_add(&evTime, &tv);
// 事件循环
event_dispatch();
return 0;
}
TCP服务器
下面的代码实现了监听本机8888端口并输出客户端发送过来的信息
- 调用 event_base_new 初始化 event_base 类型指针
- 调用 event_set 设置事件
- 调用 event_base_set 将事件设置为base事件
- 调用 event_add 添加事件
- 调用 event_base_dispatch 进入时间循环
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <event.h>
using namespace std;
// 事件base
struct event_base* base;
// 读事件回调函数
void onRead(int iCliFd, short iEvent, void *arg)
{
int iLen;
char buf[1500];
iLen = recv(iCliFd, buf, 1500, 0);
if (iLen <= 0) {
cout << "Client Close" << endl;
// 连接结束(=0)或连接错误(<0),将事件删除并释放内存空间
struct event *pEvRead = (struct event*)arg;
event_del(pEvRead);
delete pEvRead;
close(iCliFd);
return;
}
buf[iLen] = 0;
cout << "Client Info:" << buf << endl;
}
// 连接请求事件回调函数
void onAccept(int iSvrFd, short iEvent, void *arg)
{
int iCliFd;
struct sockaddr_in sCliAddr;
socklen_t iSinSize = sizeof(sCliAddr);
iCliFd = accept(iSvrFd, (struct sockaddr*)&sCliAddr, &iSinSize);
// 连接注册为新事件 (EV_PERSIST为事件触发后不默认删除)
struct event *pEvRead = new event;
event_set(pEvRead, iCliFd, EV_READ|EV_PERSIST, onRead, pEvRead);
event_base_set(base, pEvRead);
event_add(pEvRead, NULL);
}
int main()
{
int iSvrFd;
struct sockaddr_in sSvrAddr;
memset(&sSvrAddr, 0, sizeof(sSvrAddr));
sSvrAddr.sin_family = AF_INET;
sSvrAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
sSvrAddr.sin_port = htons(8888);
// 创建tcpSocket(iSvrFd),监听本机8888端口
iSvrFd = socket(AF_INET, SOCK_STREAM, 0);
bind(iSvrFd, (struct sockaddr*)&sSvrAddr, sizeof(sSvrAddr));
listen(iSvrFd, 10);
// 初始化base
base = event_base_new();
struct event evListen;
// 设置事件
event_set(&evListen, iSvrFd, EV_READ|EV_PERSIST, onAccept, NULL);
// 设置为base事件
event_base_set(base, &evListen);
// 添加事件
event_add(&evListen, NULL);
// 事件循环
event_base_dispatch(base);
return 0;
}
初始化 -- event_init
struct event_base * base = event_init();
这一步相当于初始化一个Reactor实例
在初始化libevent后,就可以注册事件了
时间设置 -- event_set
evtimer_set(&ev, timer_cb, NULL);
等价于调用:
event_set(&ev, -1, 0, timer_cb, NULL);
void event_set(struct event *ev, int fd, short event, void (*cb)(int, short, void *), void *arg)
event_set 函数参数说明参数 | 说明 |
ev | event 对象 |
fd | 要绑定的句柄(对于信号事件,该参数为关注的信号,对于定时器事件取 -1) |
event | 事件类型(EV_READ、EV_WRITE 或 EV_SIGNAL) |
cb | 回调函数指针,三个参数分别是 event_set 的 fd、event 和 arg |
arg | 传递给 cb 函数指针的参数 |
设置 event 从属的 event_base -- event_base_set
event_base_set(base, &ev);
这一步将 event 注册到 event_base 实例上
添加事件 -- event_add
event_add(&ev, timeout);
分发事件,开始执行 -- event_base_dispatch
event_base_dispatch(base);
欢迎关注微信公众号,以技术为主,涉及历史、人文等多领域的学习与感悟,每周三到七篇推文,只有全部原创,只有干货没有鸡汤

技术帖
web
服务器
技术分享
nginx
server
事件
libevent
reactor
事件驱动
定时器
timer