事件驱动组件libevent实战

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);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值