1 环境搭建
Libevent API 提供了一种机制,可以在文件描述符上发生特定事件或达到超时后执行回调函数。此外,Libevent 还支持由于信号或定期超时而引起的回调。libevent旨在替换事件驱动网络服务器中的事件循环。应用程序只需要调用 event_dispatch(),然后动态添加或删除事件,而无需更改事件循环。
1.1下载
git clone https://github.com/libevent/libevent.git
1.2编译
(1)openssl安装依赖
这里安装openssl库,也可以disable掉:EVENT__DISABLE_OPENSSL看实际需要。
apt-get install libssl-dev
(2)编译
MBEDTLS默认是开的,关闭防止报错
cd libevent
mkdir bulid
cmake .. \
-DEVENT__DISABLE_MBEDTLS=ON
make -j4 && make install
2 libevent网络IO实践
下面分析源码由官方文档提供的实例,非常具有代表性,分析libevent是如何做到将网络IO操作转换为对缓冲区的读写操作。如果需要系统学习可以参考官方给出的文档:http://www.wangafu.net/~nickm/libevent-book/TOC.html
2.1 主循环
(1)创建一个libevent对象:event_base_new();
(2)创建服务器监听fd设置为非阻塞:evutil_make_socket_nonblocking(listener);
(3)创建监听事件:event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);这里加入了一个接收新连接的回调函数。
(4)将服务器监听事件加入到libevent队列:event_add(listener_event, NULL);
(5)启动libevent:event_base_dispatch(base);
void
run(void)
{
evutil_socket_t listener;
struct sockaddr_in sin;
struct event_base *base;
struct event *listener_event;
base = event_base_new();
if (!base)
return; /*XXXerr*/
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(40713);
listener = socket(AF_INET, SOCK_STREAM, 0);
evutil_make_socket_nonblocking(listener);
#ifndef WIN32
{
int one = 1;
setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
}
#endif
if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
perror("bind");
return;
}
if (listen(listener, 16)<0) {
perror("listen");
return;
}
listener_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);
/*XXX check it */
event_add(listener_event, NULL);
event_base_dispatch(base);
}
2.2 监听网络连接
(1)调用网络accept等待网络连接:accept(listener, (struct sockaddr*)&ss, &slen);
(2)设置客户端fd为非阻塞:evutil_make_socket_nonblocking(fd);
(3)创建双向队列缓冲区:bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
(4)注册读事件和错误事件回调:bufferevent_setcb(bev, readcb, NULL, errorcb, NULL);
(5)设置缓冲区上下限:bufferevent_setwatermark(bev, EV_READ, 0, MAX_LINE);
(6)启动缓冲区读写:bufferevent_enable(bev, EV_READ|EV_WRITE);
void
do_accept(evutil_socket_t listener, short event, void *arg)
{
struct event_base *base = arg;
struct sockaddr_storage ss;
socklen_t slen = sizeof(ss);
int fd = accept(listener, (struct sockaddr*)&ss, &slen);
if (fd < 0) {
perror("accept");
} else if (fd > FD_SETSIZE) {
close(fd);
} else {
struct bufferevent *bev;
evutil_make_socket_nonblocking(fd);
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, readcb, NULL, errorcb, NULL);
bufferevent_setwatermark(bev, EV_READ, 0, MAX_LINE);
bufferevent_enable(bev, EV_READ|EV_WRITE);
}
}
2.3 读事件回调
(1)首先获取读缓冲区句柄:input = bufferevent_get_input(bev);
(2)写缓冲区句柄 output = bufferevent_get_output(bev);
(3)循环读直到没有数据:line = evbuffer_readln(input, &n, EVBUFFER_EOL_LF)
(4)读出来处理完后加入写缓冲区:evbuffer_add(output, line, n);注意:读出来的缓冲区需要应用程序自己释放:free(line);
(5)另一种读数据的方式:evbuffer_get_length(input)获取数据长度
(6)利用remove函数移除数据:evbuffer_remove(input, buf, sizeof(buf));这种方式不需要单独释放缓冲区:
char
rot13_char(char c)
{
/* We don't want to use isalpha here; setting the locale would change
* which characters are considered alphabetical. */
if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
return c + 13;
else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
return c - 13;
else
return c;
}
void
readcb(struct bufferevent *bev, void *ctx)
{
struct evbuffer *input, *output;
char *line;
size_t n;
int i;
input = bufferevent_get_input(bev);
output = bufferevent_get_output(bev);
while ((line = evbuffer_readln(input, &n, EVBUFFER_EOL_LF))) {
for (i = 0; i < n; ++i)
line[i] = rot13_char(line[i]);
evbuffer_add(output, line, n);
evbuffer_add(output, "\n", 1);
free(line);
}
if (evbuffer_get_length(input) >= MAX_LINE) {
/* Too long; just process what there is and go on so that the buffer
* doesn't grow infinitely long. */
char buf[1024];
while (evbuffer_get_length(input)) {
int n = evbuffer_remove(input, buf, sizeof(buf));
for (i = 0; i < n; ++i)
buf[i] = rot13_char(buf[i]);
evbuffer_add(output, buf, n);
}
evbuffer_add(output, "\n", 1);
}
}
2.4 错误事件回调
根据错误码进行相关处理,同时释放缓冲区: bufferevent_free(bev);
void
errorcb(struct bufferevent *bev, short error, void *ctx)
{
if (error & BEV_EVENT_EOF) {
/* connection has been closed, do any clean up here */
/* ... */
} else if (error & BEV_EVENT_ERROR) {
/* check errno to see what error occurred */
/* ... */
} else if (error & BEV_EVENT_TIMEOUT) {
/* must be a timeout event handle, handle it */
/* ... */
}
bufferevent_free(bev);
}
3 libevent定时器实践
定时器整体操作比较简单:
3.1 定时器主循环
(1)创建libevent对象: event_base_new();
(2)创建定时器事件:ev = evtimer_new(base, cb, NULL);
(3)将事件加入libevent队列:evtimer_add(ev, &tv);
(4)libevent主循环:event_base_loop(base, 0);
3.2 定时器事件回调
(1)等待定时器事件触发:evtimer_pending(ev, NULL)
(2)从受监视的事件集中删除一个事件。:event_del(ev);
(3)重新加入定时器事件到受监视的事件集:evtimer_add(ev, &tv);
struct event *ev;
struct timeval tv;
static void cb(int sock, short which, void *arg) {
if (!evtimer_pending(ev, NULL)) {
event_del(ev);
evtimer_add(ev, &tv);
}
}
int main(int argc, char **argv) {
struct event_base *base = event_base_new();
tv.tv_sec = 0;
tv.tv_usec = 0;
ev = evtimer_new(base, cb, NULL);
evtimer_add(ev, &tv);
event_base_loop(base, 0);
return 0;
}
3.3 优化定时器的精度
主要是利用:event_base_init_common_timeout获取实际剩余的超时时间,调整精度。在下一次加入定时器任务时,修正超时时间来达到保证定时器的时间精度。
#include <event2/event.h>
#include <string.h>
/* We're going to create a very large number of events on a given base,
* nearly all of which have a ten-second timeout. If initialize_timeout
* is called, we'll tell Libevent to add the ten-second ones to an O(1)
* queue. */
struct timeval ten_seconds = { 10, 0 };
void initialize_timeout(struct event_base *base)
{
struct timeval tv_in = { 10, 0 };
const struct timeval *tv_out;
tv_out = event_base_init_common_timeout(base, &tv_in);
memcpy(&ten_seconds, tv_out, sizeof(struct timeval));
}
int my_event_add(struct event *ev, const struct timeval *tv)
{
/* Note that ev must have the same event_base that we passed to
initialize_timeout */
if (tv && tv->tv_sec == 10 && tv->tv_usec == 0)
return event_add(ev, &ten_seconds);
else
return event_add(ev, tv);
}