Libevent是一个事件触发的网络库,适用于windows、linux、bsd等多种平台,Libevent在底层select、pool、kqueue和epoll等机制基础上,封装出一致的事件接口。可以注册可读、可写、超时等事件,指定回调函数;当事件发生后,Libevent调用回调函数,可以在回调函数里实现自定义功能。编译库代码,编译脚本会判断OS支持哪种类型的事件机制(select、epoll或kqueue),然后条件编译相应代码,供上层使用的接口仍然是保持统一的。
著名分布式缓存软件memcached也是Libevent based,而且Libevent在使用上可以做到跨平台,而且根据Libevent官方网站上公布的数据统计,似乎也有着非凡的性能。
Libevent支持用户使用三种类型的事件,分别是网络IO、定时器、信号三种。定时器的数据结构使用最小堆(Min Heap),以提高效率。网络IO和信号的数据结构采用了双向链表(TAILQ)。在实现上主要有3种链表:EVLIST_INSERTED, EVLIST_ACTIVE, EVLIST_TIMEOUT,一个ev在这3种链表之间被插入或删除,处于EVLIST_ACTIVE链表中的ev最后将会被调度执行。
Libevent基本函数介绍:
event_init:
初始化Libevent库。
event_set:
赋值structevent结构。可以用event_add把该事件结构增加到事件循环,用event_del从事件循环中删除。支持的事件类型可以是下面组合:EV_READ(可读), EV_WRITE(可写),EV_PERSIST(除非调用event_del,否则事件一直在事件循环中)。
event_base_set:
修改structevent事件结构所属的event_base为指定的event_base。Libevnet内置一个全局的event_base结构。多个线程应用中,如果多个线程都需要一个Libevent事件循环,需要调用event_base_setp修改事件结构基于的event_base。
event_add:
增加事件到事件监控中。
event_base_loop:
事件循环。调用底层的select、poll或epoll等,如监听事件发生,调用事件结构中指定的回调函数。
Libevent有下面一些特点和优势:
* 事件驱动,高性能;
* 轻量级,专注于网络;
* 跨平台,支持 Windows、Linux、Mac Os等;
* 支持多种 I/O多路复用技术, epoll、poll、dev/poll、select 和kqueue 等;
* 支持 I/O,定时器和信号等事件;
Libevent 库的结构
Libevent_core
包含所有核心的事件和缓冲功能
event_base,evbuffer,bufferevent,和几个附加补助功能
Libevent_extra
定义了程序可能需要,也可能不需要的协议特定功能,包括HTTP、DNS和RPC
Libevent
这个库没用以后版本会删掉
Libevent_pthreads
pthread可一直线程库的线程和锁定实现
Libevent_openssl这个库为使用bufferevent和OpenSSL进行加密的通信提供支持。
Libevent主要功能主键
Evutil : 网络补助工具
Event,eventbase : Libevent核心事件和事件管理
Bufferevent :bufferevent由一个底层的传输端口(如套接字),一个读取缓冲区和一个写入缓冲区组成。与通常的事件在底层传输端口已经就绪,可以读取或者写入的时候执行回调不同的是,bufferevent在读取或者写入了足够量的数据之后调用用户提供的回调。 当前bufferevent只能用于像TCP这样的面向流的协议,将来才可能会支持像UDP这样的面向数据报的协议。
Evbuffer : 在bufferevent层之下实现了缓冲功能,并且提供了方便有效的访问函数。
基本应用场景:
基本应用场景也是使用libevnet的基本流程,下面来考虑一个最简单的场景,使用livevent设置定时器,应用程序只需要执行下面几个简单的步骤即可。
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);
下面是程序参考代码:
#include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <event.h> #define PORT 12345 #define BACKLOG 5 #define MEM_SIZE 1024 struct event_base* base; struct sock_ev { struct event* read_ev; struct event* write_ev; char* buffer; }; int main(int argc, char* argv[]) { struct sockaddr_in my_addr; int sock; sock = socket(AF_INET, SOCK_STREAM, 0);//生成一个TCP的socket 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;//协议类型是INET,将点分的IP地址转换为4字节的整数并赋值给s_addr my_addr.sin_port = htons(PORT);//将主机的无符号短整形数转换成网络字节顺序 my_addr.sin_addr.s_addr = INADDR_ANY;//0.0.0.0的地址,即“所有地址”、“任意地址”。 bind(sock, (struct sockaddr*)&my_addr, sizeof(struct sockaddr));//绑定端口 listen(sock, BACKLOG);//建立监听 struct event listen_ev; base = event_base_new();//初始化base,用于创建一个事件处理的全局变量,负责集中处理各种出入IO事件 event_set(&listen_ev, sock, EV_READ|EV_PERSIST, on_accept, NULL);//设置事件,在listen_ev这个事件监听sock这个描述字的读操作,当读消息到达是调用on_accept函数,EV_PERSIST参数告诉系统持续的监听sock上的读事件 event_base_set(base, &listen_ev);//设置为base事件,将listen_ev注册到base这个事件中,相当于告诉处理IO的管家请留意我的listen_ev上的事件 event_add(&listen_ev, NULL);//添加事件,通知处理IO的base,当有事件到达时调用on_accept函数,至此对listen_ev的初始化完毕。 event_base_dispatch(base);//事件循环,启动libevent的事件处理机制,使系统运行起来,event_base_dispatch是一个无限循环。 return 0; } 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\n", 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); }