转自:http://blog.sina.com.cn/s/blog_9f1496990102vshz.html
原文:http://www.lvtao.net/c/631.html
Libevent介绍
libevent是一个事件触发的网络库,适用于windows、linux、bsd等多种平台,内部使用select、epoll、kqueue等系统调用管理事件机制。著名分布式缓存软件memcached也是libevent based,而且libevent在使用上可以做到跨平台,而且根据libevent官方网站上公布的数据统计,似乎也有着非凡的性能。
libevent官方网站 http://libevent.org/
英文文档 http://www.wangafu.net/~nickm/libevent-book/
中文文档 http://www.cppblog.com/mysileng/category/20374.html
Libevent是基于事件的网络库。说的通俗点,例如我的客户端连接到服务端属于一个连接的事件,当这个事件触发的时候就会去处理。
该文章阅读过程中,请结合下面的socket例子,可能会更加清晰的理解每一个接口的用法。
event_base
1. 创建event_base
event_base是event(事件,后面会讲event)的一个集合。event_base中存放你是监听是否就绪的event。一般情况下一个线程一个event_base,多个线程的情况下需要开多个event_base。
event_base主要是用来管理和实现事件的监听循环。
一般情况下直接new一个event_base就可以满足大部分需求了,如果需要配置参数的,可以参见libevent官网。
创建方法:
struct event_base *event_base_new(void);
销毁方法:
void event_base_free(struct event_base *base);
重新初始化:
int event_reinit(struct event_base *base);
2. 查看IO模型
IO多路复用模型中,有多种方法可以供我们选择,但是这些模型是在不同的平台下面的: select poll epoll kqueue devpoll evport win32
当我们创建一个event_base的时候,libevent会自动为我们选择最快的IO多路复用模型,Linux下一般会用epoll模型。
下面这个方法主要是用来获取IO模型的名称。
const char *event_base_get_method(const struct event_base *base);
3. 销毁event_base
void event_base_free(struct event_base *base);
4. 事件循环 event loop
我们上面说到 event_base是一组event的集合,我们也可以将event事件注册到这个集合中。当需要事件监听的时候,我们就需要对这个event_base进行循环。
下面这个函数非常重要,会在内部不断的循环监听注册上来的事件。
int event_base_dispatch(struct event_base *base);
返回值:0 表示成功退出 -1 表示存在错误信息。
还可以用这个方法:
#define EVLOOP_ONCE 0x01 #define EVLOOP_NONBLOCK 0x02 #define EVLOOP_NO_EXIT_ON_EMPTY 0x04 int event_base_loop(struct event_base *base, int flags);
event_base_loop这个方法会比event_base_dispatch这个方法更加灵活一些。
EVLOOP_ONCE: 阻塞直到有一个活跃的event,然后执行完活跃事件的回调就退出。
EVLOOP_NONBLOCK : 不阻塞,检查哪个事件准备好,调用优先级最高的那一个,然后退出。
0:如果参数填了0,则只有事件进来的时候才会调用一次事件的回调函数,比较常用
事件循环停止的情况:
1. event_base中没有事件event
2. 调用event_base_loopbreak(),那么事件循环将停止
3. 调用event_base_loopexit(),那么事件循环将停止
4. 程序错误,异常退出
两个退出的方法:
// 这两个函数成功返回 0 失败返回 -1 // 指定在 tv 时间后停止事件循环 // 如果 tv == NULL 那么将无延时的停止事件循环 int event_base_loopexit(struct event_base *base,const struct timeval *tv); // 立即停止事件循环(而不是无延时的停止) int event_base_loopbreak(struct event_base *base);
两个方法区别:
1. event_base_loopexit(base, NULL) 如果当前正在为多个活跃事件调用回调函数,那么不会立即退出,而是等到所有的活跃事件的回调函数都执行完成后才退出事件循环
2. event_base_loopbreak(base) 如果当前正在为多个活跃事件调用回调函数,那么当前正在调用的回调函数会被执行,然后马上退出事件循环,而并不处理其他的活跃事件了
5. event_base的例子:
#include <<span>stdio.h> #include <<span>stdlib.h> #include <<span>unistd.h> #include <<span>sys/types.h> #include <<span>sys/socket.h> #include <<span>netinet/in.h> #include <<span>arpa/inet.h> #include <<span>string.h> #include <<span>fcntl.h> #include <<span>event2/event.h> #include <<span>event2/bufferevent.h> int main() { puts("init a event_base!"); struct event_base *base; //定义一个event_base base = event_base_new(); //初始化一个event_base const char *x = event_base_get_method(base); //查看用了哪个IO多路复用模型,linux一下用epoll printf("METHOD:%s\n", x); int y = event_base_dispatch(base); //事件循环。因为我们这边没有注册事件,所以会直接退出 event_base_free(base); //销毁libevent return 1; }
返回:
event 事件
event_base是事件的集合,负责事件的循环,以及集合的销毁。而event就是event_base中的基本单元:事件。
我们举一个简单的例子来理解事件。例如我们的socket来进行网络开发的时候,都会使用accept这个方法来阻塞监听是否有客户端socket连接上来,如果客户端连接上来,则会创建一个线程用于服务端与客户端进行数据的交互操作,而服务端会继续阻塞等待下一个客户端socket连接上来。客户端连接到服务端实际就是一种事件。
1. 创建一个事件event
struct event *event_new(struct event_base *base, evutil_socket_t fd,short what, event_callback_fn cb,void *arg);
参数:
1. base:即event_base
2. fd:文件描述符。
3. what:event关心的各种条件。
4. cb:回调函数。
5. arg:用户自定义的数据,可以传递到回调函数中去。
libevent是基于事件的,也就是说只有在事件到来的这种条件下才会触发当前的事件。例如:
1. fd文件描述符已准备好可写或者可读
2. fd马上就准备好可写和可读。
3. 超时的情况 timeout
4. 信号中断
5. 用户触发的事件
what参数 event各种条件:
// 超时 #define EV_TIMEOUT 0x01 // event 相关的文件描述符可以读了 #define EV_READ 0x02 // event 相关的文件描述符可以写了 #define EV_WRITE 0x04 // 被用于信号检测(详见下文) #define EV_SIGNAL 0x08 // 用于指定 event 为 persistent 持久类型。当事件执行完毕后,不会被删除,继续保持pending等待状态; // 如果是非持久类型,则回调函数执行完毕后,事件就会被删除,想要重新使用这个事件,就必须将这个事件继续添加event_add #define EV_PERSIST 0x10 // 用于指定 event 会被边缘触发 #define EV_ET 0x20
2. 释放event_free
真正的释放event的内存。
void event_free(struct event *event);
event_del 清理event的内存。这个方法并不是真正意义上的释放内存。
当函数会将事件转为 非pending和非activing的状态。
int event_del(struct event *event);
3. 注册event
该方法将用于向event_base注册事件。
参数:ev 为事件指针;tv 为时间指针。当tv = NULL的时候则无超时时间。
函数返回:0表示成功 -1 表示失败。
int event_add(struct event *ev, const struct timeval *tv);
tv时间结构例子:
struct timeval five_seconds = {5, 0}; event_add(ev1, &five_seconds);
4.event_assign
event_new每次都会在堆上分配内存。有些场景下并不是每次都需要在堆上分配内存的,这个时候我们就可以用到event_assign方法。
已经初始化或者处于 pending 的 event,首先需要调用 event_del() 后再调用 event_assign()。这个时候就可以重用这个event了。
// 此函数用于初始化 event(包括可以初始化栈上和静态存储区中的 event) // event_assign() 和 event_new() 除了 event 参数之外,使用了一样的参数 // event 参数用于指定一个未初始化的且需要初始化的 event // 函数成功返回 0 失败返回 -1 int event_assign(struct event *event, struct event_base *base,evutil_socket_t fd, short what,void (*callback)(evutil_socket_t, short, void *), void *arg); // 类似上面的函数,此函数被信号 event 使用 event_assign(event, base, signum, EV_SIGNAL|EV_PERSIST, callback, arg)
5. 信号事件
信号事件也可以对信号进行事件的处理。用法和event_new类似。只不过处理的是信号而已
// base --- event_base // signum --- 信号,例如 SIGHUP // callback --- 信号出现时调用的回调函数 // arg --- 用户自定义数据 evsignal_new(base, signum, cb, arg) //将信号 event 注册到 event_base evsignal_add(ev, tv) // 清理信号 event evsignal