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等等。
1. 介绍
libevent是一个用来开发可扩展的网络服务器的事件通知函数库。当一个文件描述符上的特定事件发生或是一个超时时间到达后,libevent API提供一种执行回调函数的机制。而且,libevent还支持基于信号或定期超时的回调功能。
libevent旨在替换在原有事件驱动网络服务器事件循环而设计的。应用程序仅仅需要调用event_dispatch(),然后动态地添加或是移除事件就可以了,而不需要改变原有的事件循环。
目前,libevent支持/dev/poll,kqueue(2),select(2),poll(2)和epoll(4)等高并发网络编程模型。而它对实时信号的支持正处于实验性阶段。内部的事件处理机制是完全独立于暴露出来的API的,并且新功能的加入并不需要重新设计应用程序,而是仅仅需要做一个简单的libevent更新即可。因此,lievent允许可移植性的应用程序开发,并且能够提供适合特定操作系统的最具可扩展性的事件通知机制。libevent同时也可用于多线程编程环境,更多说明请看Steven Grimm的说明。libevent可以在Linux,*BSD,Mac OS X,Solaris和Windows系统上编译。
2. 标准用法
每一个使用libevent的程序,都需要包含<event.h>头文件,并且需要传递-levent标志给连接器linker。在使用任何库函数之前,需要先调用event_init()或者event_base_new()函数制执行一次libevent库的初始化。
3. 事件通知
对于每一个你想监视的文件描述符,你必须声明一个事件结构并且调用event_set()去初始化结构中的成员。为了激活通知,你需要通过调用event_add()将该结构添加到监视事件列表。只要是该事件存活,那么就需要保持该已allocated的事件结构,因此该事件结构需要在堆(heap)上申请。最后,需要调用event_dispatch()函数循环和调度事件。
4. I/O缓冲区
libevent提供了一个定期回调事件顶层的抽象。该抽象被称为缓冲事件(buffered event)。缓冲事件提供自动地填充和流掉(drained)的输入和输出缓冲区。缓冲时间的用户不再需要直接操作I/O,取而待之的是仅仅从输入缓冲区读,向输出缓冲区写就可以了。
一旦通过bufferevent_new()进行了初始化,bufferevent结构就可以通过bufferevent_enable()和bufferevent_disable()重复地使用了。作为替代,对一个套接口的读写需要通过调用bufferevent_read()和bufferevent_write()函数来完成。
当由于读事件而激活bufferevent时,那么后续将会自动回调读函数从该文件描述符读取数据。写函数将会被回调,无论何时这个输出缓冲区空间被耗尽到低于写的下水位(low watemark),通常该值默认为0。
5. 定时器
libevent通过创建一个定时器来参与到一个经过一定超时时间后的回调事件中。evtimer_set()函数将准备(分配)一个事件结构被用于作为一个定时器。为了激活定时器,需要调用evtimer_add()函数。相反,需要调用evtimer_del()函数。
6. 超时
除了简单的定时器,libevent可以为文件描述符指定一个超时事件,用于触发经过一段时间后而没有被激活的文件描述符执行相应的操作。timeout_set()函数可以为一个超时时间初始化一个事件结构。一旦被初始化成功,那么这个事件必须通过timeout_add()函数激活。为了取消一个超时事件,可以调用timeout_del()函数。
7. 异步DNS解析
libevent提供了一个异步DNS解析器,可用于代替标准的DNS解析器。这些函数可以通过在程序中包含<evdns.h>头文件而将其导入。在使用任何解析器函数之前,你必须调用evdns_init()函数初始化函数库。为转化一个域名到IP地址,可以调用evdns_resolve_ipv4()函数。为了执行一个反响查询,你可以调用evdns_resolve_reverse()函数。所有的这些函数,在查找时都会使用回调的方式而避免阻塞的发生。
8. 事件驱动的HTTP服务器
libevent提供了一个简单的可以嵌入到你的程序中的并能处理HTTP请求的事件驱动HTTP服务器。
为了使用这种能力,你应该在你的程序中包含<evhttp.h>头文件。你可以通过调用evhttp_new()函数来创建一个服务器。通过evhttp_bind_socket()函数添加用于监听的地址和端口。然后,你可以注册一个或多个对到来请求的处理句柄。对于每一个URI可以通过evhttp_set_cb()函数指定一个回调。通常,一个回调函数也可以通过evhttp_set_gencb()函数完成注册;如果没有其他的回调已经被注册得到该URI,那么这个回调将会与其关联。
9. RPC服务器和客户端框架
libevent提供了一个创建RPC服务器和客户端的编程框架。它将托管所有的编组和解组的数据结构。
10. API参考
要浏览完整的libevent的API文档,点击以下链接。
http://www.wangafu.net/~nickm/libevent-1.4/doxygen/html/event_8h.html -> event.h libevent主要的头文件
http://www.wangafu.net/~nickm/libevent-1.4/doxygen/html/evdns_8h.html -> evdns.h 异步DNS解析器
http://www.wangafu.net/~nickm/libevent-1.4/doxygen/html/evhttp_8h.html -> evhttp.h 内置的基于libevent的HTTP服务器
http://www.wangafu.net/~nickm/libevent-1.4/doxygen/html/evrpc_8h.html -> evrpc.h 创建RPC服务器和客户端的编程框架
一、安装
安装libevent到任意目录下
wget http://monkey.org/~provos/libevent-1.4.13-stable.tar.gz tar –xzvf libevent-1.4.13-stable.tar.gz cd libevent-1.4.13-stable ./configure --prefix=/home/mydir/libevent make && make install
二、源码组织结构
Libevent 的源代码虽然都在一层文件夹下面,但是其代码分类还是相当清晰的,主要可分为头文件、内部使用的头文件、辅助功能函数、日志、libevent框架、对系 统I/O多路复用机制的封装、信号管理、定时事件管理、缓冲区管理、基本数据结构和基于libevent的两个实用库等几个部分,有些部分可能就是一个源文件。
1)头文件
主要就是event.h:事件宏定义、接口函数声明,主要结构体event的声明;
2)内部头文件
xxx-internal.h:内部数据结构和函数,对外不可见,以达到信息隐藏的目的;
3)libevent框架
event.c:event整体框架的代码实现;
4)对系统I/O多路复用机制的封装
epoll.c:对epoll的封装;
select.c:对select的封装;
devpoll.c:对dev/poll的封装;
kqueue.c:对kqueue的封装;
5)定时事件管理
min-heap.h:其实就是一个以时间作为key的小根堆结构;
6)信号管理
signal.c:对信号事件的处理;
7)辅助功能函数
evutil.h 和evutil.c:一些辅助功能函数,包括创建socket pair和一些时间操作函数:加、减和比较等。
8)日志
log.h和log.c:log日志函数
9)缓冲区管理
evbuffer.c和buffer.c:libevent对缓冲区的封装;
10)基本数据结构
compat/sys下的两个源文件:queue.h是libevent基本数据结构的实现,包括链表,双向链表,队列等;_libevent_time.h:一些用于时间操作的结构体定义、函数和宏定义;
11)实用网络库
http和evdns:是基于libevent实现的http服务器和异步dns查询库;
三、示例
1、获取版本
// gcc getVersion.c -o getVersion -levent #include <event.h> #include <stdio.h> int main() { const char *version = event_get_version(); printf("%s ",version); return 0; }
2、timer程序
// gcc timer.c -o timer -levent #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <event2/event.h> #include <event2/event_struct.h> #define N 300 #define BUFLEN 256 struct timeval lasttime; struct ST_EventWithDescription { struct event *p_event; int time_interval; char lable[BUFLEN]; }; static void timeout_cb(evutil_socket_t fd, short event, void *arg) { struct timeval newtime, difference; struct ST_EventWithDescription *pSTEvent = arg; struct event *timeout = pSTEvent->p_event; double elapsed; evutil_gettimeofday(&newtime, NULL); evutil_timersub(&newtime, &lasttime, &difference); elapsed = difference.tv_sec + (difference.tv_usec / 1.0e6); printf("%s called at %d: %.3f seconds since my last work. ", (char*)pSTEvent->lable,(int)newtime.tv_sec, elapsed); lasttime = newtime; struct timeval tv; evutil_timerclear(&tv); tv.tv_sec = pSTEvent->time_interval; event_add(timeout, &tv); } void setParam(struct ST_EventWithDescription *stEventDescription, struct event *m_event,int time_interval,char* m_lable) { stEventDescription->p_event = m_event; stEventDescription->time_interval = time_interval; memset(stEventDescription->lable,0,sizeof(stEventDescription->lable)); memcpy(stEventDescription->lable,m_lable,strlen(m_lable)+1); } void setTimeIntervalArr(int *arr,int n) { int i; srand(time(NULL)); for(i=0; i<n; ++i) { *(arr+i) = rand()%n + 1; //*(arr+i) = i+1; } } int main(int argc, char **argv) { struct event timeout[N]; struct ST_EventWithDescription stEvent[N]; int time_interval[N]; int i=0; struct timeval tv; struct event_base *base; int flags = 0; setTimeIntervalArr(time_interval,N); base = event_base_new(); evutil_timerclear(&tv); for(i=0; i<N; ++i) { char buf[BUFLEN]= {0}; sprintf(buf,"task%d",i+1); setParam(stEvent+i,timeout+i,time_interval[i],buf); event_set(timeout+i, -1, flags, timeout_cb, (void*)(stEvent+i)); event_base(base, timeout+i);
event_add(timeout+i, &tv); } evutil_gettimeofday(&lasttime, NULL); event_base_dispatch(base); return (0); }
3、服务器程序
现在假定我们要设计一个服务器程序,用于接收客户端的数据,并将接收的数据回写给客户端。下面来构造该程序,由于本仅仅是展示一个Demo,因此程序中将不对错误进行处理,假设所有的调用都成功
#include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <event.h> #define PORT 25341 #define BACKLOG 5 #define MEM_SIZE 1024 struct event_base* base; struct sock_ev { struct event* read_ev; struct event* write_ev; char* buffer; }; void release_sock_event(struct sock_ev* ev) { event_del(ev->read_ev); free(ev->read_ev); free(ev->write_ev); free(ev->buffer); free(ev); } void on_write(int sock, short event, void* arg) { char* buffer = (char*)arg; send(sock, buffer, strlen(buffer), 0); free(buffer); } void on_read(int sock, short event, void* arg) { struct event* write_ev; int size; struct sock_ev* ev = (struct sock_ev*)arg; ev->buffer = (char*)malloc(MEM_SIZE); bzero(ev->buffer, MEM_SIZE); size = recv(sock, ev->buffer, MEM_SIZE, 0); printf("receive data:%s, size:%d ", ev->buffer, size); if (size == 0) { release_sock_event(ev); close(sock); return; } event_set(ev->write_ev, sock, EV_WRITE, on_write, ev->buffer); event_base_set(base, ev->write_ev); event_add(ev->write_ev, NULL); } void on_accept(int sock, short event, void* arg) { struct sockaddr_in cli_addr; int newfd, sin_size; struct sock_ev* ev = (struct sock_ev*)malloc(sizeof(struct sock_ev)); ev->read_ev = (struct event*)malloc(sizeof(struct event)); ev->write_ev = (struct event*)malloc(sizeof(struct event)); sin_size = sizeof(struct sockaddr_in); newfd = accept(sock, (struct sockaddr*)&cli_addr, &sin_size); event_set(ev->read_ev, newfd, EV_READ|EV_PERSIST, on_read, ev); event_base_set(base, ev->read_ev); event_add(ev->read_ev, NULL); } int main(int argc, char* argv[]) { struct sockaddr_in my_addr; int sock; sock = socket(AF_INET, SOCK_STREAM, 0); int yes = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)); memset(&my_addr, 0, sizeof(my_addr)); my_addr.sin_family = AF_INET; my_addr.sin_port = htons(PORT); my_addr.sin_addr.s_addr = INADDR_ANY; bind(sock, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)); listen(sock, BACKLOG); struct event listen_ev; base = event_base_new(); event_set(&listen_ev, sock, EV_READ|EV_PERSIST, on_accept, NULL); event_base_set(base, &listen_ev); event_add(&listen_ev, NULL); event_base_dispatch(base); return 0; }
4、演示使用libevent(http://www.monkey.org/~provos/libevent/)中的Timer的功能输出当前距离Epoch(即1970-01-01 00:00:00 UTC)的秒数
创建计时器event按照如下步骤:
1、初始化event_base event_init(); 2、创建timer event struct event timer_ev; 3、 将event加入到被监视的事件集合,evtimer_set(ev, cb, arg)宏相当于event_set(ev, -1, 0, cb, arg) evtimer_set(&timer_ev, callback_fn, NULL); 4、添加timer event到侦听中,evtimer_add(ev, tv)宏相当于event_add(ev, tv) struct timeval tv; tv.tv_sec = 5; tv.tv_usec = 0; evtimer_add(&timer_ev, &tv); 5、侦听处理 event_dispatch();
注意:如上创建的timer event不是persistent的(因为evtimer_set等价的event_set(ev, -1, 0, cb, arg)的第三个参数没有指定EV_PERSIST),即该事件在被触发一次(timeout达到5秒)之后即返回,不再参与到libevent的下次侦听了。
//gcc -Wall -levent testTime.c -o testTime #include <stdio.h> #include <event.h> #include <sys/time.h> #include <unistd.h> #include <stdbool.h> #include <stdint.h> #include <fcntl.h> #include <stdlib.h> #define rel_time_t unsigned int static struct event clockevent; static struct event_base *main_base; volatile rel_time_t current_time; static void set_current_time(void) { struct timeval timer; gettimeofday(&timer, NULL); current_time = (rel_time_t) (timer.tv_sec); } static void clock_handler(const int fd, const short which, void *arg) { struct timeval t = {.tv_sec = 1, .tv_usec = 0 }; static bool initialized = false; if(initialized){ evtimer_del(&clockevent); }else{ initialized = true; } evtimer_set(&clockevent, clock_handler, 0); event_base_set(main_base, &clockevent); evtimer_add(&clockevent, &t); set_current_time(); printf("current_time: %d ", current_time); } int main() { main_base = event_init(); clock_handler(0, 0, 0); event_base_loop(main_base, 0); return 0; }