libevent系列文章
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 的方法):
- select
- poll
- epoll
- kqueue
- devpoll
- evport
- win32
在我们创建 event_base 的时候,Libevent 会为我们选择最快的后端。创建 event_base 通过函数 event_base_new() 来完成:
- // 创建成功返回一个拥有默认设置的 event base
- // 创建失败返回 NULL
- struct event_base *event_base_new(void);
- // 查看 event_base 实际上使用到的后端
- const char *event_base_get_method(const struct event_base *base);
对于大多数程序来说,默认设置已经够用了。
event_base 的释放使用函数:
- void event_base_free(struct event_base *base);
事件循环(event loop)
event_base 会持有一组 event(这是我们前面说到的),换而言之就是说,我们可以向 event_base 中注册 event(具体如何注册本文先不谈)。如果我们向 event_base 中注册了一些 event,那么就可以让 Libevent 开始事件循环了:
- // 指定一个 event_base 并开始事件循环
- // 此函数内部被实现为一个不断进行的循环
- // 此函数返回 0 表示成功退出
- // 此函数返回 -1 表示存在未处理的错误
- int event_base_dispatch(struct event_base *base);
默认的事件循环会在以下的情况停止(也就是 event_base_dispatch 会返回):
- 如果 base 中没有 event,那么事件循环将停止
- 调用 event_base_loopbreak(),那么事件循环将停止
- 调用 event_base_loopexit(),那么事件循环将停止
- 如果出现错误,那么事件循环将停止
事件循环会检测是否存在活跃事件(之前已经介绍过活跃事件这一术语:http://name5566.com/4190.html),若存在活跃事件,那么调用事件对应的回调函数。
停止事件循环的可以通过移除 event_base 中的 event 来实现。如果你希望在 event_base 中存在 event 的情况下停止事件循环,可以通过以下函数完成:
- // 这两个函数成功返回 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);
这里需要区别一下 event_base_loopexit(base, NULL) 和 event_base_loopbreak(base):
- event_base_loopexit(base, NULL) 如果当前正在为多个活跃事件调用回调函数,那么不会立即退出,而是等到所有的活跃事件的回调函数都执行完成后才退出事件循环
- event_base_loopbreak(base) 如果当前正在为多个活跃事件调用回调函数,那么当前正在调用的回调函数会被执行,然后马上退出事件循环,而并不处理其他的活跃事件了
有时候,我们需要在事件的回调函数中获取当前的时间,这时候你不需要调用 gettimeofday() 而是使用 event_base_gettimeofday_cached()(你的系统可能实现 gettimeofday() 为一个系统调用,你可以避免系统调用的开销):
- // 获取到的时间为开始执行此轮事件回调函数的时间
- // 成功返回 0 失败返回负数
- int event_base_gettimeofday_cached(struct event_base *base,
- struct timeval *tv_out);
如果我们需要(为了调试)获取被注册到 event_base 的所有的 event 和它们的状态,调用 event_base_dump_events() 函数:
- // f 表示输出内容的目标文件
- void event_base_dump_events(struct event_base *base, FILE *f);
event
现在开始详细的讨论一下 event。在 Libevent 中 event 表示了一组条件,例如:
- 一个文件描述符可读或者可写
- 超时
- 出现一个信号
- 用户触发了一个事件
event 的相关术语:
- 一个 event 通过 event_new() 创建出来,那么它是已初始化的,另外 event_assign() 也被用来初始化 event(但是有它特定的用法)
- 一个 event 被注册到(通过 add 函数添加到)一个 event_base 中,其为 pending 状态
- 如果 pending event 表示的条件被触发了,那么此 event 会变为活跃的,其为 active 状态,则 event 相关的回调函数被调用
- event 可以是 persistent(持久的)也可以是非 persistent 的。event 相关的回调函数被调用之后,只有 persistent event 会继续转为 pending 状态。对于非 persistent 的 event 你可以在 event 相关的事件回调函数中调用 event_add() 使得此 event 转为 pending 状态
event 常用 API:
// 定义了各种条件
// 超时
#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);
一个范例:
#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 相关的函数:
- // base --- event_base
- // signum --- 信号,例如 SIGHUP
- // callback --- 信号出现时调用的回调函数
- // arg --- 用户自定义数据
- #define evsignal_new(base, signum, callback, arg) \
- event_new(base, signum, EV_SIGNAL|EV_PERSIST, cb, arg)
- // 将信号 event 注册到 event_base
- #define evsignal_add(ev, tv) \
- event_add((ev),(tv))
- // 清理信号 event
- #define evsignal_del(ev) \
- event_del(ev)
在通常的 POSIX 信号处理函数中,不少函数是不能被调用的(例如,不可重入的函数),但是在 Libevent 中却没有这些限制,因为信号 event 设定的回调函数运行在事件循环中。另外需要注意的是,在同一个进程中 Libevent 只能允许一个 event_base 监听信号。
性能相关问题
Libevent 提供了我们机制来重用 event 用以避免 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 使用
- #define evsignal_assign(event, base, signum, callback, arg) \
- 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() 实际上完成了两件事情:
- 通过内存分配函数在堆上分配了 event
- 使用 event_assign() 初始化了此 event
event_free() 实际上完成了两件事情:
- 调用 event_del() 进行 event 的清理工作
- 通过内存分配函数在堆上释放此 event
Libevent(3)— 基础库
参考文献列表:
http://www.wangafu.net/~nickm/libevent-book/
此文编写的时候,使用到的 Libevent 为 2.0.21
常用基本数据类型
- evutil_socket_t 用于保存 socket
- ev_uint64_t 取值范围 [0, EV_UINT64_MAX]
- ev_int64_t 取值范围 [EV_INT64_MIN, EV_INT64_MAX]
- ev_uint32_t 取值范围 [0, EV_UINT32_MAX]
- ev_int32_t 取值范围 [EV_INT32_MIN, EV_INT32_MAX]
- ev_uint16_t 取值范围 [0, EV_UINT16_MAX]
- ev_int16_t 取值范围 [EV_INT16_MIN, EV_INT16_MAX]
- ev_uint8_t 取值范围 [0, EV_UINT8_MAX]
- ev_int8_t 取值范围 [EV_INT8_MIN, EV_INT8_MAX]
- 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);
字符串相关
- // 它们对应于标准的 snprintf 和 vsnprintf
- int evutil_snprintf(char *buf, size_t buflen, const char *format, ...);
- int evutil_vsnprintf(char *buf, size_t buflen, const char *format, va_list ap);
安全的随机数生成
- // 此函数将使用随机的数据填充 n 个字节的 buf
- void evutil_secure_rng_get_bytes(void *buf, size_t n);
Libevent(4)— Bufferevent
此文编写的时候,使用到的 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, , 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
- // 如果存在未完成的延时回调,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
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
);
连接监听器的常用选项如下:
- LEV_OPT_CLOSE_ON_FREE
当关闭连接监听器其底层 socket 也被自动释放 - LEV_OPT_REUSEABLE
设置 socket 绑定的地址可以重用 - LEV_OPT_THREADSAFE
设置连接监听器为线程安全的
释放连接监听器
- void evconnlistener_free(struct evconnlistener *lev);
错误检测
如果连接监听器出错,我们可以得到通知:
- // 连接监听器错误回调函数原型
- typedef void (*evconnlistener_errorcb)(struct evconnlistener *lis, void *ptr);
- // 为连接监听器设置错误回调函数
- void evconnlistener_set_error_cb(struct evconnlistener *lev,
- 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系列文章的更多相关文章
- 基于DDD的现代ASP.NET开发框架--ABP系列文章总目录
ABP相关岗位招聘:给热爱.NET新技术和ABP框架的朋友带来一个高薪的工作机会 ABP交流会录像视频:ABP架构设计交流群-7月18日上海线下交流会的内容分享(有高清录像视频的链接) 代码自动生成: ...
- 【NLP】蓦然回首:谈谈学习模型的评估系列文章(一)
统计角度窥视模型概念 作者:白宁超 2016年7月18日17:18:43 摘要:写本文的初衷源于基于HMM模型序列标注的一个实验,实验完成之后,迫切想知道采用的序列标注模型的好坏,有哪些指标可以度量. ...
- 【微信小程序开发•系列文章六】生命周期和路由
这篇文章理论的知识比较多一些,都是个人观点,描述有失妥当的地方希望读者指出. [微信小程序开发•系列文章一]入门 [微信小程序开发•系列文章二]视图层 [微信小程序开发•系列文章三]数据层 [微信小程 ...
- SQL Server 系列文章快速导航(SWF版)
一.前言 在博客园写博客不自不觉已经有5个年头了,一开始只是为了记录工作中遇到的问题和解决办法,后来写的文章不自不觉的侧重在SQL Server方面的技术文章,在2014年1月终于鼓起勇气申请了微软S ...
- Geotrellis系列文章链接
本文存放了我在博客园中撰写的Geotrellis系列文章链接,方便查阅! 一.geotrellis使用初探 二.geotrellis使用(二)geotrellis-chatta-demo以及geotr ...
- 如何实现一个php框架系列文章【开篇】
1.本系列文章的目的 实现一个小而美的产品级别php框架 自己动手实现一个新框架仅用于学习交流,不打算替代市面上现有的其他主流框架. 2. 我要一个怎样的PHP框架 简单实用,安全优雅,博采众长 安装 ...
- EF和MVC系列文章导航:EF Code First、DbContext、MVC
对于之前一直使用webForm服务器控件.手写ado.net操作数据库的同学,突然来了EF和MVC,好多新概念泉涌而出,的确犹如当头一棒不知所措.本系列文章可以帮助新手入门并熟练使用EF和MVC,有了 ...
- java nio系列文章
java nio系列教程 基于NIO的Client/Server程序实践 (推荐) java nio与并发编程相关电子书籍 (访问密码 48dd) 理解NIO nio学习记录 图解ByteBuff ...
- Java 加解密技术系列文章
Java 加解密技术系列之 总结 Java 加解密技术系列之 DH Java 加解密技术系列之 RSA Java 加解密技术系列之 PBE Java 加解密技术系列之 AES Java 加解密技术系列 ...
随机推荐
- Careercup - Google面试题 - 4847954317803520
2014-05-08 21:33 题目链接 原题: largest number that an int variable can fit given a memory of certain size ...
- android 怎么动态设置button 的style
网上找了很多,还是没有直接的解决办法,button没有setstyle这个方法.因此我的解决办法如下: 直接动态设置各个属性 Button themeBtn = new Button(this); t ...
- 三门概率问题之C#版
前言: 早上看到一片关于三门问题的博客http://www.cnblogs.com/twocats/p/3440398.html,抱着该博客结论的怀疑态度用C#语言写了一些代码.实验证明该博客的结论是 ...
- 浅谈GitHub
Git 是一个面向开源及私有软件项目的托管平台,因为只支持Git作为唯一的版本库格式进行托管,故名GitHub. Gith是一个基于 git 的社会化代码分享社区,所谓 social coding.你 ...
- HDU 5638 拓扑排序+优先队列
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5638 题意: 给你一个DAG图,删除k条边,使得能个得到字典序尽可能小的拓扑排序 题解: 把拓扑排序 ...
- 【BZOJ】【3894】文理分科
网络流/最小割 rausen大爷太神辣-作为一个蒟蒻还是搬运题解吧…… 很明显的一道网络流题.. 首先把所有值的加起来,再减掉网络流最小割值就好了,问题就是如何建图.这貌似也是考了好多次了的... 把 ...
- Leetcode#166 Fraction to Recurring Decimal
原题地址 计算循环小数 先把负数转化成正数,然后计算,最后添加符号 当被除数重复出现的时候,说明开始循环了,所以用一个map保存所有遇到的被除数 需要考虑溢出问题,这也是本题最恶心的地方,看看通过率吧 ...
- 8大排序算法图文讲解 分类: Brush Mode 2014-08-18 11:49 78人阅读 评论(0) 收藏
排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 常见的内部排序算法有:插入排序.希尔排序. ...
- flex Chrome flash调试时 出现Shockwave flash has crashed的解决办法
在Chrome中输入:chrome://plugins/ PPAPI的Flash Player停用. 使用NPAPI的Flash player. 这里好像没有显示是Debug版本. 但是我在调 ...
- Appstore提交 被拒绝
Reasons 16.1: Apps that present excessively objectionable or crude content will be rejected 16.1 We ...