转自:http://name5566.com/4215.html
参考文献列表:
http://www.wangafu.net/~nickm/libevent-book/
此文编写的时候,使用到的 Libevent 为 2.0.21
Buffer IO 模式
bufferevent 提供给我们一种 Buffer IO 模式(这里以写入数据为例):
- 在我们需要通过某个连接发送数据的时候,先将等待发送的数据放入到一个 buffer 中
- 等待此连接可以写入数据
- 尽可能多的获取 buffer 中的数据写入此连接
- 如果 buffer 中还有需要写入的数据则继续等待直到此连接可以写入数据
每一个 bufferevent 都包含了一个输入 buffer 和一个输出 buffer,它们的类型为 evbuffer(结构体)。当我们向 bufferevent 写入数据的时候,实际上数据首先被写入到了输出 buffer,当 bufferevent 有数据可读时,我们实际上是从输入 buffer 中获取数据。
目前 bufferevent 目前仅仅支持 stream-oriented 的协议(例如 TCP)并不支持 datagram-oriented 协议(例如 UDP)。一个 bufferevent 的实例负责一个特定的连接上的数据收发。
Libevent 可以按需要创建多种类型的 bufferevent:
- 基于 socket 的 bufferevent。此类型的 bufferevent 使用 socket 来进行数据的收发,使用 event 机制来判断 socket 是否可以进行读写操作
- 异步 IO bufferevent。此类型的 bufferevent 使用 IOCP 接口实现(仅 Windows 下可用且目前处于实验阶段)
- Filtering bufferevent。此类型的 bufferevent 可以在同底层交互时完成一些额外的数据处理工作,例如可以完成数据的压缩和解析工作。这种类型的 bufferevent 的一个实例会封装了另外的一个 bufferevent,我们把这个被封装的 bufferevent 叫做底层 bufferevent
- Paired bufferevent。本文不谈
bufferevent 的回调函数
每个 bufferevent 实例可以有 3 个回调函数(通过接口 bufferevent_setcb 设置):
- 读取回调函数。默认情况下,只要从底层读取到了数据此回调函数将被调用
- 写入回调函数。默认情况下,足够多的数据被写入底层此回调函数将被调用
- 事件回调函数。当某些事件(错误)发生时被调用
对于 buffer 的读、写和回调行为可以通过几个参数来配置,这几个参数在 Libevent 中被叫做 watermark(水位标记,我们可以将 buffer 想象为一个水池,水位标记用于标记水池中水的多少,也就是说,watermark 用于标记 buffer 中的数据量)。watermark 被实现为整数(类型为 size_t),有几种类型的 watermark:
- Read low-water mark 用于控制读取回调函数的行为。当 bufferevent 进行读取操作时,Read low-water mark 的值决定了输入 buffer 有多少数据后调用读取回调函数。默认的情况下,此值为 0,因此 bufferevent 读取操作都会导致读取回调函数被调用
- Read high-water mark 用于控制输入 buffer 的大小。如果输入 buffer 中的数据量达到 Read high-water mark 的值,那么 bufferevent 将停止读取。默认的情况下,此值为无限大
- Write low-water mark,用于控制写入回调函数的行为。当 bufferevent 进行写入操作时,Write low-water mark 的值决定了输出 buffer 有多少数据后调用写入回调函数。默认的情况下,此值为 0,因此写入回调函数会在输出 buffer 为空的时候被调用
- Write high-water mark,此值在使用 Filtering bufferevent 有特殊的用途
在一些特殊需求中(详细并不讨论),我们可能需要回调函数被延时执行(这种被延时的回调函数被叫做 Deferred callbacks)。延时回调函数会在事件循环中排队,并在普通事件回调函数(regular event’s callback)之后被调用。
从基于 socket 的 bufferevent 开始认识 bufferevent
创建基于 socket 的 bufferevent:
- // 创建一个基于 socket 的 bufferevent
- // 函数执行失败返回 NULL
- struct bufferevent *bufferevent_socket_new(
- struct event_base *base,
- // socket 文件描述符
- // 此 socket 必须被设置为非阻塞的
- // 可以设置为 -1 表示之后再设置
- evutil_socket_t fd,
- // bufferevent 的选项
- enum bufferevent_options options);
buffervent 的选项可以用来改变 bufferevent 的行为。可用的选项包括:
- BEV_OPT_CLOSE_ON_FREE
当 bufferevent 被释放同时关闭底层(socket 被关闭等)
- BEV_OPT_THREADSAFE
为 bufferevent 自动分配锁,这样能够在多线程环境中安全使用
- BEV_OPT_DEFER_CALLBACKS
当设置了此标志,bufferevent 会延迟它的所有回调(参考前面说的延时回调)
- BEV_OPT_UNLOCK_CALLBACKS
如果 bufferevent 被设置为线程安全的,用户提供的回调被调用时 bufferevent 的锁会被持有
如果设置了此选项,Libevent 将在调用你的回调时释放 bufferevent 的锁
建立连接:
- // address 和 addrlen 和标准的 connect 函数的参数没有区别
- // 如果 bufferevent bev 没有设置 socket(在创建时可以设置 socket)
- // 那么调用此函数将分配一个新的 socket 给 bev
- // 连接成功返回 0 失败返回 -1
- int bufferevent_socket_connect(struct bufferevent *bev,
- struct sockaddr *address, int addrlen);
简单的一个范例:
- #include <event2/event.h>
- #include <event2/bufferevent.h>
- #include <sys/socket.h>
- #include <string.h>
-
- // 事件回调函数
- void eventcb(struct bufferevent *bev, short events, void *ptr)
- {
- // 连接成功建立
- if (events & BEV_EVENT_CONNECTED) {
- /* We're connected to 127.0.0.1:8080. Ordinarily we'd do
- something here, like start reading or writing. */
- // 出现错误
- } else if (events & BEV_EVENT_ERROR) {
- /* An error occured while connecting. */
- }
- }
-
- int main_loop(void)
- {
- struct event_base *base;
- struct bufferevent *bev;
- struct sockaddr_in sin;
-
- base = event_base_new();
-
- // 初始化连接地址
- memset(&sin, 0, sizeof(sin));
- sin.sin_family = AF_INET;
- sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
- sin.sin_port = htons(8080); /* Port 8080 */
-
- // 创建一个基于 socket 的 bufferevent
- // 参数 -1 表示并不为此 bufferevent 设置 socket
- bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
-
- // 为 bufferevent bev 设置回调函数
- // 这里仅仅设置了事件回调函数
- // 后面会详细谈及此函数
- bufferevent_setcb(bev, NULL, NULL, eventcb, NULL);
-
- // 进行连接
- if (bufferevent_socket_connect(bev,
- (struct sockaddr *)&sin, sizeof(sin)) < 0) {
- /* Error starting connection */
- bufferevent_free(bev);
- return -1;
- }
-
- // 开始事件循环
- event_base_dispatch(base);
- return 0;
- }
更多的 bufferevent API
释放 bufferevent
- // 如果存在未完成的延时回调,bufferevent 会在回调完成后才被真正释放
- void bufferevent_free(struct bufferevent *bev);
bufferevent 回调函数的设置和获取
- // 读取、写入回调函数原型
- // ctx 为用户自定义数据(由 bufferevent_setcb 设定)
- typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
-
- // 事件回调函数原型
- // ctx 为用户自定义数据(由 bufferevent_setcb 设定)
- // events 选项可以为:
- // BEV_EVENT_READING --- 在 bufferevent 上进行读取操作时出现了一个事件
- // BEV_EVENT_WRITING --- 在 bufferevent 上进行写入操作时出现了一个事件
- // BEV_EVENT_ERROR --- 进行 bufferevent 操作时(例如调用 bufferevent API)出错,获取详细的错误信息使用 EVUTIL_SOCKET_ERROR()
- // BEV_EVENT_TIMEOUT --- 在 bufferevent 上出现了超时
- // BEV_EVENT_EOF --- 在 bufferevent 上遇到了文件结束符
- // BEV_EVENT_CONNECTED --- 在 bufferevent 上请求连接完成了
- typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short events, void *ctx);
-
- // 设置回调函数
- // 如果希望禁用回调函数,那么设置对应的参数为 NULL
- void bufferevent_setcb(
- // bufferevent
- struct bufferevent *bufev,
- // 读取回调函数
- bufferevent_data_cb readcb,
- // 写入回调函数
- bufferevent_data_cb writecb,
- // 事件回调函数
- bufferevent_event_cb eventcb,
- // 用户定义的数据
- // 这三个回调函数均共享此参数
- void *cbarg
- );
-
- // 取回回调函数
- // 参数为 NULL 表示忽略
- void bufferevent_getcb(
- struct bufferevent *bufev,
- bufferevent_data_cb *readcb_ptr,
- bufferevent_data_cb *writecb_ptr,
- bufferevent_event_cb *eventcb_ptr,
- void **cbarg_ptr
- );
设置 watermark
- // events 参数可以为
- // EV_READ 表示设置 read watermark
- // EV_WRITE 表示设置 write watermark
- // EV_READ | EV_WRITE 表示设置 read 以及 write watermark
- void bufferevent_setwatermark(struct bufferevent *bufev, short events,
- size_t lowmark, size_t highmark);
获取到输入和输出 buffer
- // 获取到输入 buffer
- struct evbuffer *bufferevent_get_input(struct bufferevent *bufev);
- // 获取到输出 buffer
- struct evbuffer *bufferevent_get_output(struct bufferevent *bufev);
添加数据到输出 buffer
- // 下面两个函数执行成功返回 0 失败返回 -1
-
- // 向 bufev 的输出 buffer 中添加大小为 size 的数据
- // 数据的首地址为 data
- int bufferevent_write(struct bufferevent *bufev,
- const void *data, size_t size);
- // 向 bufev 的输出 buffer 中添加数据
- // 数据来源于 buf
- // 此函数会清除 buf 中的所有数据
- int bufferevent_write_buffer(struct bufferevent *bufev,
- struct evbuffer *buf);
从输入 buffer 中获取数据
- // 从 bufev 的输入 buffer 中获取最多 size 字节的数据保存在 data 指向的内存中
- // 此函数返回实际读取的字节数
- size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
- // 获取 bufev 的输入 buffer 中的所有数据并保存在 buf 中
- // 此函数成功返回 0 失败返回 -1
- int bufferevent_read_buffer(struct bufferevent *bufev,
- struct evbuffer *buf);
关于 bufferevent 的一些高级话题,可以参考:http://www.wangafu.net/~nickm/libevent-book/Ref6a_advanced_bufferevents.html
evbuffers
evbuffer 是一个队列,在其尾部添加数据和在其头部删除数据均被优化了。evbuffer 相关的 API 在这里可以查看:http://www.wangafu.net/~nickm/libevent-book/Ref7_evbuffer.html