libevent 简介及使用

2015-07-15 17:43:16   最后更新: 2015-07-16 12:07:51   访问数量:3024




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,下面是几个例子:

 

定时器事件

用法如下:

  1. 调用 event_init 初始化 libevent
  2. 调用 evtimer_set 设置定时器事件
  3. 调用 event_add 添加事件
  4. 使用 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端口并输出客户端发送过来的信息

 

  1. 调用 event_base_new 初始化 event_base 类型指针
  2. 调用 event_set 设置事件
  3. 调用 event_base_set 将事件设置为base事件
  4. 调用 event_add 添加事件
  5. 调用 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);

 

 

  • event_set
void event_set(struct event *ev, int fd, short event, void (*cb)(int, short, void *), void *arg)

 

 

  • 参数说明
event_set 函数参数说明
参数说明
evevent 对象
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     


京ICP备15018585号