libevent介绍
libevent是一个轻量级的,开源高性能的,基于事件触发的网络库,适用于windows、linux、bsd等多种平台,内部使用select、epoll、kqueue等系统调用管理事件机制。
编译库代码,编译脚本会判断OS支持哪种类型的事件机制(select、epoll或kqueue),然后条件编译相应代码,供上层使用的接口仍然是保持统一的。
有许多开源项目使用libevent,例如memcached。使用libevent,使得memcached可以适应多种操作系统。Libevent对底层异步函数提供了较薄封装,库本身没有消耗过多性能;另外,使用堆排序管理定时器队列,提供了较高的性能。总体来说,libevent有下面一些特点和优势:
- 统一数据源, 统一I/O事件,信号和定时器这三种事件;
- 可移植,跨平台支持多种I/O多路复用技术, epoll、poll、dev/poll、select 和kqueue 等;
- 对并发编程支持,避免竞态条件;
- 高性能,由事件驱动;
- 轻量级,专注于网络
libevent有下面几大部分组成:
- 事件管理包括各种IO(socket)、定时器、信号等事件,也是libevent应用最广的模块;
- 缓存管理是指evbuffer功能;
- DNS是libevent提供的一个异步DNS查询功能;
- HTTP是libevent的一个轻量级http实现,包括服务器和客户端
libevent的结构
- evutil 用于抽象不同的平台的网络(基础的)实现
- event、event_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.c:event整体框架的代码实现;
- 对系统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.h和log.c:log日志函数
- 缓冲区管理----evbuffer.c和buffr.c:libevent对缓冲区的封装;
- 基本数据结构----compat/sys下的一个源文件:queue.h是libevent基本数据结构的实现,包括链表,双向链表,队列等
- 实用网络库----http和evdns:是基于libevent实现的http服务器和异步dns查询库;
编译libevent
1.在此下载,最新的libevent压缩包libevent-2.0.22-stable.tar.gz 。源文件有个问题,evutil.c源码修改这部分。不然使用会失败。#ifdef WIN322.打开VS2012开发工具命令行,cd 到libevent目录;键入nmake /f Makefile.nmake;完成编译,生成以下3个库文件
#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
- 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, event和arg;
- 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内部是怎么样进行处理的呢?下面的图就给出了这一基本流程。
- 首先应用程序准备并初始化event,设置好事件类型和回调函数;这对应于前面第步骤2和3;
- 向libevent添加该事件event。对于定时事件,libevent使用一个小根堆管理,key为超时时间;对于Signal和I/O事件,libevent将其放入到等待链表(wait list)中,这是一个双向链表结构;
- 程序调用event_base_dispatch()系列函数进入无限循环,等待事件,以select()函数为例;每次循环前libevent会检查定时事件的最小超时时间tv,根据tv设置select()的最大等待时间,以便于后面及时处理超时事件;当select()返回后,首先检查超时事件,然后检查I/O事件;Libevent将所有的就绪事件,放入到激活链表中;然后对激活链表中的事件,调用事件的回调函数执行事件处理;