[libevent]Libevent介绍与编译

时间:2022-03-15 00:15:36

libevent介绍

libevent是一个轻量级的,开源高性能的,基于事件触发的网络库,适用于windowslinuxbsd等多种平台,内部使用selectepollkqueue等系统调用管理事件机制。 

编译库代码,编译脚本会判断OS支持哪种类型的事件机制(selectepollkqueue),然后条件编译相应代码,供上层使用的接口仍然是保持统一的。  

有许多开源项目使用libevent,例如memcached。使用libevent,使得memcached可以适应多种操作系统。Libevent对底层异步函数提供了较薄封装,库本身没有消耗过多性能;另外,使用堆排序管理定时器队列,提供了较高的性能。总体来说,libevent有下面一些特点和优势: 

  • 统一数据源, 统一I/O事件,信号和定时器这三种事件
  • 可移植,跨平台支持多种I/O多路复用技术, epollpolldev/pollselect kqueue 
  • 对并发编程支持,避免竞态条件;
  • 高性能,由事件驱动;
  • 轻量级,专注于网络

libevent有下面几大部分组成:

  • 事件管理包括各种IOsocket)、定时器、信号等事件,也是libevent应用最广的模块;
  • 缓存管理是指evbuffer功能;
  • DNSlibevent提供的一个异步DNS查询功能;
  • HTTPlibevent的一个轻量级http实现,包括服务器和客户端 

libevent的结构

  • evutil 用于抽象不同的平台的网络(基础的)实现
  • eventevent_base 为 Libevent 的核心,为不同的平台下基于事件的非阻塞 I/O 提供了一套抽象的接口
  • bufferevent 对 Libevent 的基于事件的核心的封装。应用程序的读写请求是基于缓冲区的
  • evbuffer 为 bufferevent 实现的缓冲区
  • evhttp 一个简单的 HTTP client/server 的实现
  • evdns 一个简单的 DNS client/server 的实现
  • evrpc 一个简单的 RPC 实现

源代码组织结构

libevent的源代码虽然都在一层文件夹下面,但是其代码分类还是相当清晰的,主要可分为头文件内部使用的头文件辅助功能函数日志libevent框架对系统I/O多路复用机制的封装信号管理定时事件管理缓冲区管理基本数据结构基于libevent的两个实用库等几个部分,有些部分可能就是一个源文件。 

  • 头文件----主要就是event.h:事件宏定义、接口函数声明,主要结构体event的声明;
  • 内部头----xxx-internal.h:内部数据结构和函数,对外不可见,以达到信息隐藏的目的;
  • libevent框架----event.cevent整体框架的代码实现;
  • 对系统I/O多路复用机制的封装----epoll.c:对epoll的封装;select.c:对select的封装;devpoll.c:对dev/poll的封装;kqueue.c:对kqueue的封装;
  • 定时事件管理----min-heap.h:其实就是一个以时间作为key的小根堆结构;
  • 信号管理----signal.c:对信号事件的处理;
  • 辅助功能函数---evutil.h evutil.c:一些辅助功能函数,包括创建socket pair和一些时间操作函数:加、减和比较等。
  • 日志----log.hlog.clog日志函数
  • 缓冲区管理----evbuffer.cbuffr.clibevent对缓冲区的封装;
  • 基本数据结构----compat/sys下的一个源文件:queue.hlibevent基本数据结构的实现,包括链表,双向链表,队列等 
  • 实用网络库----httpevdns:是基于libevent实现的http服务器和异步dns查询库; 

编译libevent

1.在此下载,最新的libevent压缩包libevent-2.0.22-stable.tar.gz 。源文件有个问题,evutil.c源码修改这部分。不然使用会失败
#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib,"ws2_32.lib")
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#undef WIN32_LEAN_AND_MEAN
#include <io.h>
#include <tchar.h>
#endif
2.打开VS2012开发工具命令行,cd libevent目录;键入nmake /f Makefile.nmake完成编译,生成以下3个库文件
  • libevent_core.lib:所有核心事件功能和buffer功能,包括event_base,evbuffer, bufferevent, utility
  • libevent_extra.lib:提供某些特定协议的功能,包括HTTP, DNS, RPC
  • libevent.lib:此库由于历史原因而存在,不要使用它,以后可能消失。

实例--基本应用场景--定时器

新建win32工程,配置库路径xx\libevent-2.0.22-stable\头文件路径xx\libevent-2.0.22-stable\include;xx\libevent-2.0.22-stable\库文件libevent_extras.lib;libevent_core.lib;libevent.lib。并把libevent文件夹下的WIN32-Code\event2\event-config.h拷贝到include/event2文件夹下,这样就ok了。 

#include <event.h>  
struct timeval tv;
struct event ev;
void time_cb(int fd, short event, void *argc)
{
printf("timer wakeup\n");
event_add(&ev, &tv); // reschedule timer
}
void main()
{
struct event_base *base = event_init();
tv.tv_sec = 10 ; // 10s period
tv.tv_usec = 0;
evtimer_set(&ev, time_cb, NULL);
event_add(&ev, &tv);
event_base_dispatch(base);
}
//输出:每10秒,打印一次timer wakeup

应用程序只需要执行下面几个简单的步骤即可。

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, eventarg
  • 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);

事件处理流程

当应用程序向libevent注册一个事件后,libevent内部是怎么样进行处理的呢?下面的图就给出了这一基本流程。

[libevent]Libevent介绍与编译

  1. 首先应用程序准备并初始化event,设置好事件类型和回调函数;这对应于前面第步骤23
  2. libevent添加该事件event。对于定时事件,libevent使用一个小根堆管理,key为超时时间;对于SignalI/O事件,libevent将其放入到等待链表(wait list)中,这是一个双向链表结构;
  3. 程序调用event_base_dispatch()系列函数进入无限循环,等待事件,以select()函数为例;每次循环前libevent会检查定时事件的最小超时时间tv,根据tv设置select()的最大等待时间,以便于后面及时处理超时事件;当select()返回后,首先检查超时事件,然后检查I/O事件;Libevent将所有的就绪事件,放入到激活链表中;然后对激活链表中的事件,调用事件的回调函数执行事件处理;