Libevent 2 提供了 bufferevent 接口,简化了编程的难度,bufferevent 实际上是对底层事件核心的封装,因此学习 bufferevent 的实现是研究 Libevent 底层 event、event_base 用法的一个好办法。本文假定你已经对 Libevent 有一定的认识,否则可以先阅读我关于 Libevent 的介绍: 
Libevent(1)— 简介、编译、配置 
Libevent(2)— event、event_base 
Libevent(3)— 基础库 
Libevent(4)— Bufferevent 
Libevent(5)— 连接监听器

Libevent(2)— event、event_base

参考文献列表:
http://www.wangafu.net/~nickm/libevent-book/

此文编写的时候,使用到的 Libevent 为 2.0.21。本文略过了关于 event 优先权和超时相关的讨论。

创建和销毁 event_base

event_base 是首先需要被创建出来的对象。event_base 结构持有了一个 event 集合。如果 event_base 被设置了使用锁,那么它在多个线程中可以安全的访问。但是对 event_base 的循环(下面会马上解释什么是“对 event_base 的循环”)只能在某个线程中执行。如果你希望多个线程进行循环,那么应该做的就是为每一个线程创建一个 event_base。

event_base 存在多个后端可以选择(我们也把 event_base 后端叫做 event_base 的方法):

  1. select
  2. poll
  3. epoll
  4. kqueue
  5. devpoll
  6. evport
  7. win32

在我们创建 event_base 的时候,Libevent 会为我们选择最快的后端。创建 event_base 通过函数 event_base_new() 来完成:

  1. // 创建成功返回一个拥有默认设置的 event base
  2. // 创建失败返回 NULL
  3. struct event_base *event_base_new(void);
  4. // 查看 event_base 实际上使用到的后端
  5. const char *event_base_get_method(const struct event_base *base);

对于大多数程序来说,默认设置已经够用了。

event_base 的释放使用函数:

    1. void event_base_free(struct event_base *base);

事件循环(event loop)

event_base 会持有一组 event(这是我们前面说到的),换而言之就是说,我们可以向 event_base 中注册 event(具体如何注册本文先不谈)。如果我们向 event_base 中注册了一些 event,那么就可以让 Libevent 开始事件循环了:

  1. // 指定一个 event_base 并开始事件循环
  2. // 此函数内部被实现为一个不断进行的循环
  3. // 此函数返回 0 表示成功退出
  4. // 此函数返回 -1 表示存在未处理的错误
  5. int event_base_dispatch(struct event_base *base);

默认的事件循环会在以下的情况停止(也就是 event_base_dispatch 会返回):

  1. 如果 base 中没有 event,那么事件循环将停止
  2. 调用 event_base_loopbreak(),那么事件循环将停止
  3. 调用 event_base_loopexit(),那么事件循环将停止
  4. 如果出现错误,那么事件循环将停止

事件循环会检测是否存在活跃事件(之前已经介绍过活跃事件这一术语:http://name5566.com/4190.html),若存在活跃事件,那么调用事件对应的回调函数。

停止事件循环的可以通过移除 event_base 中的 event 来实现。如果你希望在 event_base 中存在 event 的情况下停止事件循环,可以通过以下函数完成:

  1. // 这两个函数成功返回 0 失败返回 -1
  2. // 指定在 tv 时间后停止事件循环
  3. // 如果 tv == NULL 那么将无延时的停止事件循环
  4. int event_base_loopexit(struct event_base *base,
  5. const struct timeval *tv);
  6. // 立即停止事件循环(而不是无延时的停止)
  7. int event_base_loopbreak(struct event_base *base);

这里需要区别一下 event_base_loopexit(base, NULL) 和 event_base_loopbreak(base):

  1. event_base_loopexit(base, NULL) 如果当前正在为多个活跃事件调用回调函数,那么不会立即退出,而是等到所有的活跃事件的回调函数都执行完成后才退出事件循环
  2. event_base_loopbreak(base) 如果当前正在为多个活跃事件调用回调函数,那么当前正在调用的回调函数会被执行,然后马上退出事件循环,而并不处理其他的活跃事件了

有时候,我们需要在事件的回调函数中获取当前的时间,这时候你不需要调用 gettimeofday() 而是使用 event_base_gettimeofday_cached()(你的系统可能实现 gettimeofday() 为一个系统调用,你可以避免系统调用的开销):

  1. // 获取到的时间为开始执行此轮事件回调函数的时间
  2. // 成功返回 0 失败返回负数
  3. int event_base_gettimeofday_cached(struct event_base *base,
  4. struct timeval *tv_out);

如果我们需要(为了调试)获取被注册到 event_base 的所有的 event 和它们的状态,调用 event_base_dump_events() 函数:

  1. // f 表示输出内容的目标文件
  2. void event_base_dump_events(struct event_base *base, FILE *f);

event

现在开始详细的讨论一下 event。在 Libevent 中 event 表示了一组条件,例如:

  1. 一个文件描述符可读或者可写
  2. 超时
  3. 出现一个信号
  4. 用户触发了一个事件

event 的相关术语:

  1. 一个 event 通过 event_new() 创建出来,那么它是已初始化的,另外 event_assign() 也被用来初始化 event(但是有它特定的用法)
  2. 一个 event 被注册到(通过 add 函数添加到)一个 event_base 中,其为 pending 状态
  3. 如果 pending event 表示的条件被触发了,那么此 event 会变为活跃的,其为 active 状态,则 event 相关的回调函数被调用
  4. event 可以是 persistent(持久的)也可以是非 persistent 的。event 相关的回调函数被调用之后,只有 persistent event 会继续转为 pending 状态。对于非 persistent 的 event 你可以在 event 相关的事件回调函数中调用 event_add() 使得此 event 转为 pending 状态

event 常用 API:

  1. // 定义了各种条件
    // 超时
    #define EV_TIMEOUT 0x01
    // event 相关的文件描述符可以读了
    #define EV_READ 0x02
    // event 相关的文件描述符可以写了
    #define EV_WRITE 0x04
    // 被用于信号检测(详见下文)
    #define EV_SIGNAL 0x08
    // 用于指定 event 为 persistent
    #define EV_PERSIST 0x10
    // 用于指定 event 会被边缘触发(Edge-triggered 可参考 http://name5566.com/3818.html)
    #define EV_ET 0x20 // event 的回调函数
    // 参数 evutil_socket_t --- event 关联的文件描述符
    // 参数 short --- 当前发生的条件(也就是上面定义的条件)
    // 参数 void* --- 其为 event_new 函数中的 arg 参数
    typedef void (*event_callback_fn)(evutil_socket_t, short, void *); // 创建 event
    // base --- 使用此 event 的 event_base
    // what --- 指定 event 关心的各种条件(也就是上面定义的条件)
    // fd --- 文件描述符
    // cb --- event 相关的回调函数
    // arg --- 用户自定义数据
    // 函数执行失败返回 NULL
    struct event *event_new(struct event_base *base, evutil_socket_t fd,
    short what, event_callback_fn cb,
    void *arg); // 释放 event(真正释放内存,对应 event_new 使用)
    // 可以用来释放由 event_new 分配的 event
    // 若 event 处于 pending 或者 active 状态释放也不会存在问题
    void event_free(struct event *event); // 清理 event(并不是真正释放内存)
    // 可用于已经初始化的、pending、active 的 event
    // 此函数会将 event 转换为非 pending、非 active 状态的
    // 函数返回 0 表示成功 -1 表示失败
    int event_del(struct event *event); // 用于向 event_base 中注册 event
    // tv 用于指定超时时间,为 NULL 表示无超时时间
    // 函数返回 0 表示成功 -1 表示失败
    int event_add(struct event *ev, const struct timeval *tv);

一个范例:

  1. #include <event2/event.h>
    
    // event 的回调函数
    void cb_func(evutil_socket_t fd, short what, void *arg)
    {
    const char *data = arg;
    printf("Got an event on socket %d:%s%s%s%s [%s]",
    (int) fd,
    (what&EV_TIMEOUT) ? " timeout" : "",
    (what&EV_READ) ? " read" : "",
    (what&EV_WRITE) ? " write" : "",
    (what&EV_SIGNAL) ? " signal" : "",
    data);
    } void main_loop(evutil_socket_t fd1, evutil_socket_t fd2)
    {
    struct event *ev1, *ev2;
    struct timeval five_seconds = {, };
    // 创建 event_base
    struct event_base *base = event_base_new(); // 这里假定 fd1、fd2 已经被设置好了(它们都被设置为非阻塞的) // 创建 event
    ev1 = event_new(base, fd1, EV_TIMEOUT | EV_READ | EV_PERSIST, cb_func,
    (char*)"Reading event");
    ev2 = event_new(base, fd2, EV_WRITE | EV_PERSIST, cb_func,
    (char*)"Writing event"); // 注册 event 到 event_base
    event_add(ev1, &five_seconds);
    event_add(ev2, NULL); // 开始事件循环
    event_base_dispatch(base);
    }

Libevent 能够处理信号。信号 event 相关的函数:

  1. // base --- event_base
  2. // signum --- 信号,例如 SIGHUP
  3. // callback --- 信号出现时调用的回调函数
  4. // arg --- 用户自定义数据
  5. #define evsignal_new(base, signum, callback, arg) \
  6. event_new(base, signum, EV_SIGNAL|EV_PERSIST, cb, arg)
  7. // 将信号 event 注册到 event_base
  8. #define evsignal_add(ev, tv) \
  9. event_add((ev),(tv))
  10. // 清理信号 event
  11. #define evsignal_del(ev) \
  12. event_del(ev)

在通常的 POSIX 信号处理函数中,不少函数是不能被调用的(例如,不可重入的函数),但是在 Libevent 中却没有这些限制,因为信号 event 设定的回调函数运行在事件循环中。另外需要注意的是,在同一个进程中 Libevent 只能允许一个 event_base 监听信号。

性能相关问题

Libevent 提供了我们机制来重用 event 用以避免 event 在堆上的频繁分配和释放。相关的接口:

  1. // 此函数用于初始化 event(包括可以初始化栈上和静态存储区中的 event)
  2. // event_assign() 和 event_new() 除了 event 参数之外,使用了一样的参数
  3. // event 参数用于指定一个未初始化的且需要初始化的 event
  4. // 函数成功返回 0 失败返回 -1
  5. int event_assign(struct event *event, struct event_base *base,
  6. evutil_socket_t fd, short what,
  7. void (*callback)(evutil_socket_t, short, void *), void *arg);
  8. // 类似上面的函数,此函数被信号 event 使用
  9. #define evsignal_assign(event, base, signum, callback, arg) \
  10. event_assign(event, base, signum, EV_SIGNAL|EV_PERSIST, callback, arg)

已经初始化或者处于 pending 的 event,首先需要调用 event_del() 后再调用 event_assign()。

这里我们进一步认识一下 event_new()、event_assign()、event_free()、event_del()
event_new() 实际上完成了两件事情:

  1. 通过内存分配函数在堆上分配了 event
  2. 使用 event_assign() 初始化了此 event

event_free() 实际上完成了两件事情:

  1. 调用 event_del() 进行 event 的清理工作
  2. 通过内存分配函数在堆上释放此 event

Libevent(3)— 基础库

参考文献列表:
http://www.wangafu.net/~nickm/libevent-book/

此文编写的时候,使用到的 Libevent 为 2.0.21

常用基本数据类型

  1. evutil_socket_t 用于保存 socket
  2. ev_uint64_t 取值范围 [0, EV_UINT64_MAX]
  3. ev_int64_t 取值范围 [EV_INT64_MIN, EV_INT64_MAX]
  4. ev_uint32_t 取值范围 [0, EV_UINT32_MAX]
  5. ev_int32_t 取值范围 [EV_INT32_MIN, EV_INT32_MAX]
  6. ev_uint16_t 取值范围 [0, EV_UINT16_MAX]
  7. ev_int16_t 取值范围 [EV_INT16_MIN, EV_INT16_MAX]
  8. ev_uint8_t 取值范围 [0, EV_UINT8_MAX]
  9. ev_int8_t 取值范围 [EV_INT8_MIN, EV_INT8_MAX]
  10. ev_ssize_type(signed size_t)取值范围 [EV_SSIZE_MIN, EV_SSIZE_MAX]

时间相关

// 用于加或者减前两个参数,结果被保存在第三个参数中
#define evutil_timeradd(tvp, uvp, vvp) /* ... */
#define evutil_timersub(tvp, uvp, vvp) /* ... */ // 清除 timeval 将其值设置为 0
#define evutil_timerclear(tvp) /* ... */
// 判断 timeval 是否为 0,如果是 0 返回 false,否则返回 true
#define evutil_timerisset(tvp) /* ... */ // 比较两个 timeval
// 使用的时候这样用:
// evutil_timercmp(t1, t2, <=) 含义为判断 t1 <= t2 是否成立
// cmp 为所有的 C 关系操作符
#define evutil_timercmp(tvp, uvp, cmp) // 获取当前时间并保存到 tv
// tz 目前无用
int evutil_gettimeofday(struct timeval *tv, struct timezone *tz);

Socket API

// 用于关闭一个 socket
int evutil_closesocket(evutil_socket_t s);
#define EVUTIL_CLOSESOCKET(s) evutil_closesocket(s) // 返回当前线程的最后一次 socket 操作的错误码
#define EVUTIL_SOCKET_ERROR()
// 改变当前 socket 的错误码
#define EVUTIL_SET_SOCKET_ERROR(errcode)
// 返回特定的 sock 的错误码
#define evutil_socket_geterror(sock)
// 通过 socket 错误码获取到一个字符串描述
#define evutil_socket_error_to_string(errcode) // 设置 sock 为非阻塞的 socket
int evutil_make_socket_nonblocking(evutil_socket_t sock); // 设置 sock 的地址可重用
int evutil_make_listen_socket_reuseable(evutil_socket_t sock);

字符串相关

  1. // 它们对应于标准的 snprintf 和 vsnprintf
  2. int evutil_snprintf(char *buf, size_t buflen, const char *format, ...);
  3. int evutil_vsnprintf(char *buf, size_t buflen, const char *format, va_list ap);

安全的随机数生成

  1. // 此函数将使用随机的数据填充 n 个字节的 buf
  2. void evutil_secure_rng_get_bytes(void *buf, size_t n);

Libevent(4)— Bufferevent

此文编写的时候,使用到的 Libevent 为 2.0.21

Buffer IO 模式

bufferevent 提供给我们一种 Buffer IO 模式(这里以写入数据为例):

  1. 在我们需要通过某个连接发送数据的时候,先将等待发送的数据放入到一个 buffer 中
  2. 等待此连接可以写入数据
  3. 尽可能多的获取 buffer 中的数据写入此连接
  4. 如果 buffer 中还有需要写入的数据则继续等待直到此连接可以写入数据

每一个 bufferevent 都包含了一个输入 buffer 和一个输出 buffer,它们的类型为 evbuffer(结构体)。当我们向 bufferevent 写入数据的时候,实际上数据首先被写入到了输出 buffer,当 bufferevent 有数据可读时,我们实际上是从输入 buffer 中获取数据。

目前 bufferevent 目前仅仅支持 stream-oriented 的协议(例如 TCP)并不支持 datagram-oriented 协议(例如 UDP)。一个 bufferevent 的实例负责一个特定的连接上的数据收发。

Libevent 可以按需要创建多种类型的 bufferevent:

  1. 基于 socket 的 bufferevent。此类型的 bufferevent 使用 socket 来进行数据的收发,使用 event 机制来判断 socket 是否可以进行读写操作
  2. 异步 IO bufferevent。此类型的 bufferevent 使用 IOCP 接口实现(仅 Windows 下可用且目前处于实验阶段)
  3. Filtering bufferevent。此类型的 bufferevent 可以在同底层交互时完成一些额外的数据处理工作,例如可以完成数据的压缩和解析工作。这种类型的 bufferevent 的一个实例会封装了另外的一个 bufferevent,我们把这个被封装的 bufferevent 叫做底层 bufferevent
  4. Paired bufferevent。本文不谈

bufferevent 的回调函数

每个 bufferevent 实例可以有 3 个回调函数(通过接口 bufferevent_setcb 设置):

  1. 读取回调函数。默认情况下,只要从底层读取到了数据此回调函数将被调用
  2. 写入回调函数。默认情况下,足够多的数据被写入底层此回调函数将被调用
  3. 事件回调函数。当某些事件(错误)发生时被调用

对于 buffer 的读、写和回调行为可以通过几个参数来配置,这几个参数在 Libevent 中被叫做 watermark(水位标记,我们可以将 buffer 想象为一个水池,水位标记用于标记水池中水的多少,也就是说,watermark 用于标记 buffer 中的数据量)。watermark 被实现为整数(类型为 size_t),有几种类型的 watermark:

  1. Read low-water mark 用于控制读取回调函数的行为。当 bufferevent 进行读取操作时,Read low-water mark 的值决定了输入 buffer 有多少数据后调用读取回调函数。默认的情况下,此值为 0,因此 bufferevent 读取操作都会导致读取回调函数被调用
  2. Read high-water mark 用于控制输入 buffer 的大小。如果输入 buffer 中的数据量达到 Read high-water mark 的值,那么 bufferevent 将停止读取。默认的情况下,此值为无限大
  3. Write low-water mark,用于控制写入回调函数的行为。当 bufferevent 进行写入操作时,Write low-water mark 的值决定了输出 buffer 有多少数据后调用写入回调函数。默认的情况下,此值为 0,因此写入回调函数会在输出 buffer 为空的时候被调用
  4. 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 的行为。可用的选项包括:

    1. BEV_OPT_CLOSE_ON_FREE
      当 bufferevent 被释放同时关闭底层(socket 被关闭等)
    2. BEV_OPT_THREADSAFE
      为 bufferevent 自动分配锁,这样能够在多线程环境中安全使用
    3. BEV_OPT_DEFER_CALLBACKS
      当设置了此标志,bufferevent 会延迟它的所有回调(参考前面说的延时回调)
    4. 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, , sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
sin.sin_port = htons(); /* Port 8080 */ // 创建一个基于 socket 的 bufferevent
// 参数 -1 表示并不为此 bufferevent 设置 socket
bev = bufferevent_socket_new(base, -, BEV_OPT_CLOSE_ON_FREE); // 为 bufferevent bev 设置回调函数
// 这里仅仅设置了事件回调函数
// 后面会详细谈及此函数
bufferevent_setcb(bev, NULL, NULL, eventcb, NULL); //setcb cb表示callback,回调 // 进行连接
if (bufferevent_socket_connect(bev,
(struct sockaddr *)&sin, sizeof(sin)) < ) {
/* Error starting connection */
bufferevent_free(bev);
return -;
} // 开始事件循环
event_base_dispatch(base);
return ;
}

更多的 bufferevent API

释放 bufferevent

  1. // 如果存在未完成的延时回调,bufferevent 会在回调完成后才被真正释放
  2. 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

  1. // events 参数可以为
  2. // EV_READ 表示设置 read watermark
  3. // EV_WRITE 表示设置 write watermark
  4. // EV_READ | EV_WRITE 表示设置 read 以及 write watermark
  5. void bufferevent_setwatermark(struct bufferevent *bufev, short events,
  6. size_t lowmark, size_t highmark);

获取到输入和输出 buffer

  1. // 获取到输入 buffer
  2. struct evbuffer *bufferevent_get_input(struct bufferevent *bufev);
  3. // 获取到输出 buffer
  4. struct evbuffer *bufferevent_get_output(struct bufferevent *bufev);

添加数据到输出 buffer

  1. // 下面两个函数执行成功返回 0 失败返回 -1
  2. // 向 bufev 的输出 buffer 中添加大小为 size 的数据
  3. // 数据的首地址为 data
  4. int bufferevent_write(struct bufferevent *bufev,
  5. const void *data, size_t size);
  6. // 向 bufev 的输出 buffer 中添加数据
  7. // 数据来源于 buf
  8. // 此函数会清除 buf 中的所有数据
  9. int bufferevent_write_buffer(struct bufferevent *bufev,
  10. struct evbuffer *buf);

从输入 buffer 中获取数据

  1. // 从 bufev 的输入 buffer 中获取最多 size 字节的数据保存在 data 指向的内存中
  2. // 此函数返回实际读取的字节数
  3. size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
  4. // 获取 bufev 的输入 buffer 中的所有数据并保存在 buf 中
  5. // 此函数成功返回 0 失败返回 -1
  6. int bufferevent_read_buffer(struct bufferevent *bufev,
  7. 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

Libevent(5)— 连接监听器

参考文献列表:
http://www.wangafu.net/~nickm/libevent-book/

此文编写的时候,使用到的 Libevent 为 2.0.21

Libevent 提供了连接监听器 evconnlistener

创建 evconnlistener 实例

// 连接监听器回调函数原型
typedef void (*evconnlistener_cb)(
struct evconnlistener *listener,
// 新的 socket
evutil_socket_t sock,
// 新的 socket 对应的地址
struct sockaddr *addr,
int len,
// 用户自定义数据
void *ptr
); // 创建一个新的连接监听器
struct evconnlistener *evconnlistener_new(
struct event_base *base,
// 一个新的连接到来时此回调被调用
evconnlistener_cb cb,
// 用户自定义数据,会被传递给 cb 回调函数
void *ptr,
// 连接监听器的选项(下面会详细谈到)
unsigned flags,
// 为标准的 listen 函数的 backlog 参数
// 如果为负数,Libevent 将尝试选择一个合适的值
int backlog,
// socket
// Libevent 假定此 socket 已经绑定
evutil_socket_t fd
); // 创建一个新的连接监听器
// 大多数参数含义同于 evconnlistener_new
struct evconnlistener *evconnlistener_new_bind(
struct event_base *base,
evconnlistener_cb cb,
void *ptr,
unsigned flags,
int backlog,
// 指定需要绑定的 socket 地址
const struct sockaddr *sa,
int socklen
);

连接监听器的常用选项如下:

  1. LEV_OPT_CLOSE_ON_FREE
    当关闭连接监听器其底层 socket 也被自动释放
  2. LEV_OPT_REUSEABLE
    设置 socket 绑定的地址可以重用
  3. LEV_OPT_THREADSAFE
    设置连接监听器为线程安全的

释放连接监听器

  1. void evconnlistener_free(struct evconnlistener *lev);

错误检测
如果连接监听器出错,我们可以得到通知:

  1. // 连接监听器错误回调函数原型
  2. typedef void (*evconnlistener_errorcb)(struct evconnlistener *lis, void *ptr);
  3. // 为连接监听器设置错误回调函数
  4. void evconnlistener_set_error_cb(struct evconnlistener *lev,
  5. evconnlistener_errorcb errorcb);

一个详细的范例(echo 服务器)

#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h> #include <arpa/inet.h> #include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h> // 读取回调函数
static void
echo_read_cb(struct bufferevent *bev, void *ctx)
{
struct evbuffer *input = bufferevent_get_input(bev);
struct evbuffer *output = bufferevent_get_output(bev); // 将输入缓冲区的数据直接拷贝到输出缓冲区
evbuffer_add_buffer(output, input);
} // 事件回调函数
static void
echo_event_cb(struct bufferevent *bev, short events, void *ctx)
{
if (events & BEV_EVENT_ERROR)
perror("Error from bufferevent");
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
bufferevent_free(bev);
}
} // 连接监听器回调函数
static void
accept_conn_cb(struct evconnlistener *listener,
evutil_socket_t fd, struct sockaddr *address, int socklen,
void *ctx)
{
// 为新的连接分配并设置 bufferevent
struct event_base *base = evconnlistener_get_base(listener);
struct bufferevent *bev = bufferevent_socket_new(
base, fd, BEV_OPT_CLOSE_ON_FREE); bufferevent_setcb(bev, echo_read_cb, NULL, echo_event_cb, NULL); bufferevent_enable(bev, EV_READ|EV_WRITE);
} // 连接监听器错误回调函数
static void
accept_error_cb(struct evconnlistener *listener, void *ctx)
{
struct event_base *base = evconnlistener_get_base(listener);
// 获取到错误信息
int err = EVUTIL_SOCKET_ERROR();
fprintf(stderr, "Got an error %d (%s) on the listener. "
"Shutting down.\n", err, evutil_socket_error_to_string(err)); // 退出事件循环
event_base_loopexit(base, NULL);
} int
main(int argc, char **argv)
{
struct event_base *base;
struct evconnlistener *listener;
struct sockaddr_in sin; int port = ; if (argc > ) {
port = atoi(argv[]);
}
if (port<= || port>) {
puts("Invalid port");
return ;
} base = event_base_new();
if (!base) {
puts("Couldn't open event base");
return ;
} memset(&sin, , sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl();
sin.sin_port = htons(port); listener = evconnlistener_new_bind(base, accept_conn_cb, NULL,
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -,
(struct sockaddr*) & sin, sizeof(sin));
if (!listener) {
perror("Couldn't create listener");
return ;
}
evconnlistener_set_error_cb(listener, accept_error_cb); event_base_dispatch(base);
return ;
}

ubuntu下头文件:

#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>

#include<netinet/in.h>

#include<event2/listener.h>
#include<event2/bufferevent.h>
#include<event2/buffer.h>

关于echo 的读的客户端看:

http://blog.sina.com.cn/s/blog_87d946d40100vrv9.html

libevent系列文章的更多相关文章

  1. 基于DDD的现代ASP.NET开发框架--ABP系列文章总目录

    ABP相关岗位招聘:给热爱.NET新技术和ABP框架的朋友带来一个高薪的工作机会 ABP交流会录像视频:ABP架构设计交流群-7月18日上海线下交流会的内容分享(有高清录像视频的链接) 代码自动生成: ...

  2. 【NLP】蓦然回首:谈谈学习模型的评估系列文章(一)

    统计角度窥视模型概念 作者:白宁超 2016年7月18日17:18:43 摘要:写本文的初衷源于基于HMM模型序列标注的一个实验,实验完成之后,迫切想知道采用的序列标注模型的好坏,有哪些指标可以度量. ...

  3. 【微信小程序开发•系列文章六】生命周期和路由

    这篇文章理论的知识比较多一些,都是个人观点,描述有失妥当的地方希望读者指出. [微信小程序开发•系列文章一]入门 [微信小程序开发•系列文章二]视图层 [微信小程序开发•系列文章三]数据层 [微信小程 ...

  4. SQL Server 系列文章快速导航(SWF版)

    一.前言 在博客园写博客不自不觉已经有5个年头了,一开始只是为了记录工作中遇到的问题和解决办法,后来写的文章不自不觉的侧重在SQL Server方面的技术文章,在2014年1月终于鼓起勇气申请了微软S ...

  5. Geotrellis系列文章链接

    本文存放了我在博客园中撰写的Geotrellis系列文章链接,方便查阅! 一.geotrellis使用初探 二.geotrellis使用(二)geotrellis-chatta-demo以及geotr ...

  6. 如何实现一个php框架系列文章【开篇】

    1.本系列文章的目的 实现一个小而美的产品级别php框架 自己动手实现一个新框架仅用于学习交流,不打算替代市面上现有的其他主流框架. 2. 我要一个怎样的PHP框架 简单实用,安全优雅,博采众长 安装 ...

  7. EF和MVC系列文章导航:EF Code First、DbContext、MVC

    对于之前一直使用webForm服务器控件.手写ado.net操作数据库的同学,突然来了EF和MVC,好多新概念泉涌而出,的确犹如当头一棒不知所措.本系列文章可以帮助新手入门并熟练使用EF和MVC,有了 ...

  8. java nio系列文章

    java nio系列教程 基于NIO的Client/Server程序实践 (推荐) java nio与并发编程相关电子书籍   (访问密码 48dd) 理解NIO nio学习记录 图解ByteBuff ...

  9. Java 加解密技术系列文章

    Java 加解密技术系列之 总结 Java 加解密技术系列之 DH Java 加解密技术系列之 RSA Java 加解密技术系列之 PBE Java 加解密技术系列之 AES Java 加解密技术系列 ...

随机推荐

  1. android 通过httpclient下载文件并保存

    代码:(主要针对图片.gif下载无问题) /** * 下载网络文件 * @param url 请求的文件链接 * @param IsMD5Name 是否MD5加密URL来命名文件名 * @param ...

  2. Netsharp快速入门(之5) 基础档案(之D 实体建模 生成实体代码、同步数据库、配置插件运行时)

    作者:秋时 杨昶   时间:2014-02-15  转载须说明出处 3.3.1  同步数据库并生成dll文件 1.在基础档案和销售管理项目上右击,选择同步数据库结构来创建数据库表 2. 在基础档案项目 ...

  3. 对git的初步认识

    虽然经常听说博客,但是却是第一次用.就像,虽然经常见电脑,但是却第一次接触软件.对于git也是一样,从来没听过,更不了解. 因为自己私下也没有去过多的了解,所以对于git只有一些有关书面资料的很片面的 ...

  4. UVA 10078 The Art Gallery

    Problem: Century Arts has hundreds of art galleries scattered all around the country and you are hir ...

  5. 在C++的类中,普通成员函数不能作为pthread_create的线程函数,如果要作为pthread_create中的线程函数,必须是static

    在C++的类中,普通成员函数不能作为pthread_create的线程函数,如果要作为pthread_create中的线程函数,必须是static ! 在C语言中,我们使用pthread_create ...

  6. URAL题解—不断跟新中

    1014:简单题,忘了0的情况可以是10,== 1219:找呀找规律,满足N*(N-1)/2+1=X;就是1 的情况了

  7. javascript实现数据结构:线性表--线性链表(链式存储结构)

    上一节中, 线性表的顺序存储结构的特点是逻辑关系上相邻的两个元素在物理位置上也相邻,因此可以随机存取表中任一元素,它的存储位置可用一个简单,直观的公式来表示.然后,另一方面来看,这个特点也造成这种存储 ...

  8. javascript 注意事项

    1.submit input 标签的 type里的submit是提交表单的按钮 提交时应注意 为 标签加上 “name=""”字段和form标签,才能达到想要的效果  2.正则表达 ...

  9. ZOJ3229 Shoot the Bullet(有源汇的上下界最大流)

    #pragma warning(disable:4996) #include <iostream> #include <cstring> #include <string ...

  10. ZOJ3718 Diablo II(状态压缩dp)

    题意:一个人物有K(K<=7)种技能,每种技能都有bi,ci,di值,表示该技能不能点超过bi次,每点一次加ci,点满bi次有一个附加得分di.然后还有N件武器,武器本身会有能力加成,然后每个武 ...