1.libevent安装
作为一个轻量级服务端库,libevent应用场景主要在linux环境(也支持windows环境)。可以在官网下载所需版本的libevent: 在官网下载http://libevent.org,之后做完基本配置、编译、安装即可:
配置:./configure --prefix=/usr
编译:make
安装:make install
我在安装过程中碰到的问题是,如果安装的版本较低时,在包含了envent.h的模块进行编译时会提示各种类型不识别导致无法编译,从网上的信息看应该是需要另外安装libevent-devel包,之后重新下载较新版本安装后(libevent-2.0.21-stable),应用模块编译正常。
2.基础事件处理流程
事件类型 | 处理流程 |
同步IO | 通过一个简单的使用libevent的服务端例子,梳理了下调用流程: 1) event_init()取得libevent实例; 2) event_set先设置监听事件对象,包括将监听事件绑定服务端套接字、设置事件类型、和监听触发回调函数及其参数; 3) event_base_set将监听事件绑定libevent实例; 4) event_add将监听事件加入监听列表; 5) event_base_dispatch启动事件循环。 注意的问题: 1) event_set的事件参数,要注意其生命期,如果在函数内设置事件,则事件往往是内存堆中申请的,否则函数退出后事件对象就回收了,会出问题; 2) EV_PERSIST参数的使用很关键,默认是非持续性事件,在非持续性事件中,触发一次后,事件就退出事件监听队列; 3) 回调函数的参数,是void *类型,如果有多个参数,则需要以封装成结构对象后传入地址指针的形式传递。 |
定时器 | 定时器的应用与上面同步IO测试基本使用方式相当,区别在于: 1) 定时器使用中调用的是evtimer_set(&ev, timer_cb, NULL);类似于调用上面的event_set(&ev, -1, 0, timer_cb, NULL); 2) event_add添加定时器事件时,必须设置一个非空的时间,表示多久后触发。 |
信号 | libevent对信号的捕捉,使用方式与同步IO测试完全一样,唯一的区别是绑定的是信号而不是套接字。这样,当该系统发出该信号时,就会触发回调函数。 |
3.例子分析
libevent源码的sample目录下,有一些使用libevent接口的例子。
hello-world.c>
编译后直接运行,然后找一台网络可通的机器telnet运行hello-world程序的机器,测试的现象是:
1)telnet客户端连接很快断开,hello-world服务程序打印"flushed answer"。
从代码中可以看出是由于conn_writecb回调里面evbuffer_get_length判断bufferevent的output为空,所以将连接断开。如果去掉该判断,会发现telnet客户端能正常打印"Hello World!",连接也会保持。
从该现象看,表明conn_writecb是在bufferevent_write写入发送信息且信息已经从bufferevent的outbuf走出后,因此回调里面会发现buf数据已经没有了。
另外,自己加打印测试看了下,在listener_cb走完前,写到bufferevent的outbuf的数据是不会发出去的,所以注意这个顺序:listener_cb -> 发送outbuf数据 -> conn_writecb。
2)当服务端触发"ctrl + c"后,会出现打印"Caught an interrupt signal; exiting cleanly in two seconds."
程序中有对SIGINT信号进行监控,libevent支持对信号的捕获处理,并将其整合到基本IO流程中,手动按下"ctrl + c"即触发了SIGINT信号,所以signal_cb被调用并给出上面的打印。
本例中采用bufferevent的异步IO方式,从中可看出bufferevent基本调用流程:
1)调用bufferevent_socket_new获取bufferevent实例,参数中需要提供event_base、连接套接字;
2)调用bufferevent_setcb设置bufferevent实例的可读、可写、出错的回调;
3)调用bufferevent_enable设置bufferevent实例的读写使能情况,之后就可以通过bufferevent_write和bufferevent_read进行异步数据处理了。
另外,可以注意到本里中通过evconnlistener_new_bind方法,取代了上面同步IO测试的2-4步流程,且封装了IP:端口绑定相关操作,即通过这一个方法调用,实现了一系列过程。
time-test.c>
定时器测试很简单,基本流程与上面定时器测试中相当,区别是例子中用的event_assign将定时器事件绑定libevent实例、设置标记、绑定回调函数等。需要注意的问题是:
event_assign中设置的标记为EV_PERSIST和0的区别:EV_PERSIST表示事件被重复监测、0表示事件被处理后就被清离监测列表。而当设置为0时,event_base_dispatch在定时回调函数执行一次后就退出,这样程序也就退出了。这里也要注意event_base_dispatch和event_base_loop的区别,可以在libevent源码中看到:
int event_base_dispatch(struct event_base *event_base)
{
return (event_base_loop(event_base, 0));
}
event_base_loop第二个参数为0表示循环在处理完一次监测回调事件后不会退出。
signal-test.c与time-test.c流程几乎一样。
event-test.c>
这个例子中监测事件绑定的是文件句柄,代码中区分windows和linux不同平台做了差异化处理,linux下主要是用有名管道作为测试对象。编译程序,运行后,另启一个shell会话输入"echo aaa >> event.fifo",这时,例程会打印出监测到管道的输入信息。
整个流程除了对文件句柄的处理外,其它与之前的例子没有太大区别。
le-proxy.c>
本例是一个网络代理模块,监听客户端的连接,对每一个客户端连接对应建立一个与目标服务器的连接,然后中转客户端到目标服务器的数据,同时也能将目标服务器数据回传给对应客户端,下面是一个简图:
b_in和b_out是一组对应的bufferevent,另外从main中可以看出,通过进程参数-s可以控制b_out是否选择采用openssl加密。
本例的精彩之处全在readcb回调,b_in和b_out这对bufferevent在运作时,并不区分in/out,只管将自己收到的数据给另一方,并由另一方发送出去。同时,做了流量控制处理,即如果另一方发送缓存里数据太多,则自己先停止向自己的读缓存接数据,待另一方发送之后则放开自己的读缓存,这里用到了bufferevent_setwatermark高低水位协助控制。
Http-server.c>
这是一个使用libevent http接口的简单http服务器例子,编译运行后,通过指定一个路径作为进程参数,之后用客户端浏览器访问服务进程的http服务端口,浏览器上就会显示指定路径的目录列表。而如果路径后面接上html文件的相对路径,则会将该网页内容返回。
源码自带的例子一般都是相关接口的标准使用方式,照此可以得出http服务器基本使用流程:
- event_base_new建立libevent实例;
- evhttp_new生成一个http服务实例,需要提供libevent实例参数;
- evhttp_set_cb或evhttp_set_gencb设置http服务收到请求后的处理回调,前者是针对uri解析,后者是一个通用的回调;
- evhttp_bind_socket_with_handle设置IP端口绑定;
- 之后在event_base_dispatch中就可以开始事件处理循环了。
libevent同步IO建立服务端的经典例子:
#include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <string.h> #include <event.h> #include <stdlib.h> #include <unistd.h> #define PORT 8000 #define BACKLOG 5 #define MEM_SIZE 1024 #define EV_FREE(EV_PTR) if(NULL != EV_PTR) \ { \ free(EV_PTR); \ EV_PTR = NULL; \ } struct event_base* base; struct sockEvent { struct event* readEvent; struct event* writeEvent; char* buffer; }; void releaseSockEvent(struct sockEvent* ev)//delete from base and free it { event_del(ev->readEvent); event_del(ev->writeEvent); EV_FREE(ev->readEvent); EV_FREE(ev->writeEvent); EV_FREE(ev->buffer); EV_FREE(ev); } void handleWrite(int sock, short event, void* arg) { char* buffer = (char*)arg; send(sock, buffer, strlen(buffer), 0); } void handldRead(int sock, short event, void* arg) { struct event* writeEvent; int size; struct sockEvent* ev = (struct sockEvent*)arg; 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) //client has send FIN { releaseSockEvent(ev); close(sock); return; } //add event to base to send the received data event_set(ev->writeEvent, sock, EV_WRITE, handleWrite, ev->buffer); event_base_set(base, ev->writeEvent); event_add(ev->writeEvent, NULL); } void handleAccept(int sock, short event, void* arg)//when new connection coming, calling this func { struct sockaddr_in cli_addr; int newfd; socklen_t sinSize; struct sockEvent* ev = (struct sockEvent*)malloc(sizeof(struct sockEvent)); ev->readEvent = (struct event*)malloc(sizeof(struct event)); ev->writeEvent = (struct event*)malloc(sizeof(struct event)); ev->buffer = (char*)malloc(MEM_SIZE); sinSize = sizeof(struct sockaddr_in); newfd = accept(sock, (struct sockaddr*)&cli_addr, &sinSize); //set the new coming connection event event_set(ev->readEvent, newfd, EV_READ|EV_PERSIST, handldRead, ev); event_base_set(base, ev->readEvent); event_add(ev->readEvent, NULL); } int main(int argc, char* argv[]) { struct sockaddr_in serverAddr; int sock; sock = socket(AF_INET, SOCK_STREAM, 0); int on = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int)); //memset(&serverAddr, 0, sizeof(serverAddr)); bzero(&serverAddr, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(PORT); serverAddr.sin_addr.s_addr = INADDR_ANY; bind(sock, (struct sockaddr*)&serverAddr, sizeof(struct sockaddr)); listen(sock, BACKLOG); struct event listenEvent; base = event_init();//Create new EventBase event_set(&listenEvent, sock, EV_READ|EV_PERSIST, handleAccept, NULL);//conbine listenEvent and it's callback function event_base_set(base, &listenEvent); event_add(&listenEvent, NULL); event_base_dispatch(base);//start base return 0; }