libevent 常用函数速查 链接到标题
libevent 是一个轻量级、高性能的事件通知库,封装了 select/poll/epoll/kqueue 等底层多路复用机制,提供统一的事件驱动 API。
安装 链接到标题
Ubuntu / Debian 链接到标题
sudo apt install libevent-dev
安装后头文件在 /usr/include/event2/,库文件为 libevent_core.so 和 libevent_extra.so。
源码编译 链接到标题
wget https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz
tar xzf libevent-2.1.12-stable.tar.gz
cd libevent-2.1.12-stable
./configure --prefix=/usr/local
make -j$(nproc)
sudo make install
# ldconfig 更新动态库缓存
sudo ldconfig
编译链接 链接到标题
gcc -o myapp myapp.c -levent
# 如果使用了 bufferevent 等扩展功能:
gcc -o myapp myapp.c -levent -levent_extra
注意:
-levent链接的是libevent_core.so,bufferevent / evhttp / evdns 等功能在libevent_extra.so中,需要-levent_extra。
使用方法 链接到标题
libevent 编程的基本流程:
① 创建 event_base(相当于一个事件循环实例)
② 创建 event(把 fd + 事件类型 + 回调 绑定到一起),设置好监听的事件类型
③ event_add 将 event 加入 event_base 开始监听
④ event_base_dispatch 启动事件循环(阻塞直到没有活跃事件)
⑤ 退出时 event_free 释放 event,event_base_free 释放 event_base
核心 API 链接到标题
libevent 2.x 使用 event2/ 前缀的头文件。
event_base_new — 创建事件循环 链接到标题
#include <event2/event.h>
struct event_base *event_base_new(void);
功能:创建一个新的事件循环实例(内部自动选择最优的后端,如 epoll)。成功返回 event_base *,失败返回 NULL。
注意:每个线程应该有自己的 event_base,跨线程共享需加锁。event_base_new 内部分配内存,使用完毕后必须 event_base_free 释放。也可以传 event_config 来定制后端行为(如禁用某些后端、设置优先级等),用 event_base_new_with_config。
对应 linux_coding:相当于 epoll_create1 得到 epoll fd。
struct event_base *base = event_base_new();
if (!base) { fprintf(stderr, "event_base_new failed\n"); return -1; }
event_get_supported_methods — 查看支持的多路复用后端 链接到标题
#include <event2/event.h>
const char **event_get_supported_methods(void);
功能:返回一个以 NULL 结尾的字符串数组,列出当前平台所有可用的多路复用后端(如 epoll、poll、select、kqueue、devpoll 等)。libevent 创建 event_base 时自动选最优的那个。
典型用法:
const char **methods = event_get_supported_methods();
printf("Supported backends:\n");
for (int i = 0; methods[i] != NULL; i++)
printf(" %s\n", methods[i]);
注意:返回的数组是静态区,不要 free。不同平台编译的 libevent 可用后端不同——Linux 有 epoll,macOS 有 kqueue,Windows 有 IOCP。
event_base_get_method — 查看当前使用的后端 链接到标题
#include <event2/event.h>
const char *event_base_get_method(const struct event_base *base);
功能:返回当前 event_base 实例实际使用的多路复用后端名称。成功返回字符串指针,失败返回 NULL。
注意:返回的字符串是内部静态 buffer,不要释放。常用做日志/调试输出:
printf("Using backend: %s\n", event_base_get_method(base));
// 典型输出: "epoll"、"poll"、"kqueue" 等
event_reinit — fork 后重新初始化 event_base 链接到标题
#include <event2/event.h>
int event_reinit(struct event_base *base);
功能:在 fork() 之后,子进程中必须调用此函数来重新初始化 event_base 的内部状态(尤其是多路复用 fd,如 epoll fd),否则子进程会继续使用父进程遗留的 epoll fd,导致诡异的行为。成功返回 0,失败返回 -1。
典型用法:
pid_t pid = fork();
if (pid == 0) {
// 子进程
if (event_reinit(base) == -1) {
perror("event_reinit");
_exit(1);
}
// 不要再使用父进程创建的 event(event_del / event_free 它们)
// 然后重新 event_new 子进程自己的 event
event_base_dispatch(base);
}
注意:
- 只在子进程调用,父进程不需要(父进程的 event_base 不受影响)。
event_reinit只修复内核数据结构(epoll fd 等),不会迁移父进程的 event 列表——子进程应event_del所有从父进程继承的 event,然后重新创建自己的。- 如果你用
pthread_atfork注册回调,把event_reinit放在child回调中可以保证每次 fork 都自动调用。 - 有些后端(如 kqueue、IOCP)不支持
event_reinit,fork后应直接在子进程中event_base_free+event_base_new,此方式最保险。
event_new — 创建事件 链接到标题
#include <event2/event.h>
struct event *event_new(struct event_base *base, evutil_socket_t fd,
short what, event_callback_fn cb, void *arg);
typedef void (*event_callback_fn)(evutil_socket_t fd, short events, void *arg);
功能:在一个 event_base 上创建一个事件。fd 为要监控的文件描述符,what 为事件类型,cb 为回调函数,arg 为用户自定义参数。
what 事件类型(可用 | 组合):
| 宏 | 含义 |
|---|---|
EV_READ |
fd 可读 |
EV_WRITE |
fd 可写 |
EV_PERSIST |
事件触发后自动重新注册,无需在回调中再次 event_add,直到手动 event_del |
EV_ET |
边缘触发(需后端支持,epoll 支持)——与 epoll 的 EPOLLET 含义相同 |
EV_TIMEOUT |
超时事件(通常不直接用,而是配合 event_add 的 timeval 参数) |
EV_SIGNAL |
信号事件(用 evsignal_new 更简洁) |
注意:
- 不加
EV_PERSIST时,事件触发一次后自动变为"未决"(pending)状态,不会再触发,除非在回调中再次event_add——这是select/epoll没有的概念。 - 回调原型是
void cb(evutil_socket_t fd, short events, void *arg),其中events参数告诉你实际触发了哪些事件(EV_READ/EV_WRITE等),arg是event_new传入的自定义参数。 - 只分配内存和初始化,尚未开始监听,还需要
event_add。
对应 linux_coding:相当于 epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev),但更抽象——event 把 fd、事件类型、回调、参数打包成一个对象。
struct event *ev = event_new(base, sockfd, EV_READ | EV_PERSIST, on_read, NULL);
event_add — 将事件加入事件循环 链接到标题
#include <event2/event.h>
int event_add(struct event *ev, const struct timeval *tv);
功能:将 event 注册到循环中,使其开始"监控"对应的 fd。tv 为超时时间,NULL 表示无超时等待。成功返回 0,失败返回 -1。
tv 参数的三种用法:
tv 值 |
含义 |
|---|---|
NULL |
无限等待,事件触发时回调 |
tv = {0, 0} |
超时为 0,立即触发(配合 EV_TIMEOUT 做定时任务) |
tv = {n, 0} |
n 秒后超时触发(即使 fd 上没有活动) |
注意:
- 同一个 event 可以多次
event_add(例如修改超时时间),但必须先event_del再event_add(除非变化可以自动合并)。 EV_PERSIST事件触发后会自动维持"已添加"状态,不需要在回调里重新event_add。如果有EV_TIMEOUT配合EV_PERSIST,则是周期性定时器。- 如果 event 已经 active,
event_add行为未定义——一般 pattern 是回调末尾重新 add。
对应 linux_coding:相当于 epoll_ctl(epfd, EPOLL_CTL_ADD, ...) 真正开始监控。
// 加入循环,永久等待可读事件
event_add(ev, NULL);
// 带超时:5 秒内没可读就触发超时
struct timeval tv = {5, 0};
event_add(ev, &tv);
event_base_dispatch — 启动事件循环 链接到标题
#include <event2/event.h>
int event_base_dispatch(struct event_base *base);
功能:启动事件循环,阻塞等待事件发生并分派给对应的回调。循环持续运行直到所有事件都被移除(或 event_base_loopbreak 被调用)。成功返回 0,失败返回 -1。
注意:
- 内部等价于
event_base_loop(base, 0)。 - 只要 event_base 中还有 pending/active 事件,就不会返回——所以必须有一个 “退出条件”,比如在某个回调中调用
event_base_loopbreak,或在信号回调中进行清理。 - 没有事件时(所有 event 都已
event_del且没有 pending 的),event_base_dispatch会立即返回。 - 多线程场景每个线程调用自己的 event_base 的
event_base_dispatch。 - 只调用一次——不要在循环中反复调
event_base_dispatch,而是让循环本身活着,在回调里处理逻辑。
对应 linux_coding:相当于 while (1) { epoll_wait(epfd, events, max, -1); /* 遍历 events 调回调 */ }。
event_base_dispatch(base); // 阻塞,直到所有事件被移除
event_base_loopbreak — 停止事件循环 链接到标题
#include <event2/event.h>
int event_base_loopbreak(struct event_base *base);
功能:通知 event_base 在完成当前正在处理的回调后退出循环。成功返回 0,失败返回 -1。
注意:
- 与
event_base_loopexit的区别:loopbreak立即生效(处理完当前回调就退出),loopexit要等到下一个超时时间过后才退出。 - 通常在信号处理回调或"关闭"事件中调用。
- 不会释放任何资源——你仍需手动
event_free和event_base_free。
event_del — 从事件循环中移除事件 链接到标题
#include <event2/event.h>
int event_del(struct event *ev);
功能:将 event 从 event_base 中移除(不再监听)。成功返回 0,失败返回 -1。
注意:即使事件是 active 状态(回调正在执行),也可以调用 event_del,它会在回调返回后生效。非 EV_PERSIST 事件触发后自动变"未激活",不需要显式 event_del,但 EV_PERSIST 事件必须用 event_del 来停止。
对应 linux_coding:相当于 epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL)。
event_free — 释放事件 链接到标题
#include <event2/event.h>
void event_free(struct event *ev);
功能:释放 event_new 分配的内存。如果 event 还在 pending 或 active 状态,会先隐式调用 event_del。
注意:调用后 ev 指针不可再使用。通常配合 event_del → event_free 顺序调用。即使 ev 是 EV_PERSIST 且加入了循环,event_free 也会安全地移除并释放。
event_base_free — 释放事件循环 链接到标题
#include <event2/event.h>
void event_base_free(struct event_base *base);
功能:释放 event_base_new 分配的所有资源。循环必须已停止(event_base_loopbreak 后或 dispatch 已返回),否则行为未定义。
注意:
- 调用前应确保所有 event 都已
event_free(或至少不再引用该 base)。 - 如果 base 中还有活跃事件,
event_base_free不会自动释放它们——先清理 event,再释放 base。
bufferevent_socket_new — 创建带缓冲区的 socket 事件 链接到标题
#include <event2/bufferevent.h>
#include <event2/buffer.h>
struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd,
int options);
功能:在已有 socket fd 上创建一个 bufferevent。bufferevent 自带输入/输出缓冲区,自动处理 I/O(你只管从 buffer 读、往 buffer 写),无需手动 read/write。
options 常用取值(| 组合):
| option | 含义 |
|---|---|
BEV_OPT_CLOSE_ON_FREE |
bufferevent_free 时自动 close(fd) |
BEV_OPT_THREADSAFE |
为 bufferevent 加锁,允许多线程访问 |
BEV_OPT_DEFER_CALLBACKS |
将回调延迟到 event_base 的锁释放后执行(与 BEV_OPT_THREADSAFE 搭配) |
BEV_OPT_UNLOCK_CALLBACKS |
回调执行时不持有锁(默认回调执行时持有锁) |
设置回调:
#include <event2/bufferevent.h>
void bufferevent_setcb(struct bufferevent *bev,
bufferevent_data_cb readcb, // 数据可读时调用
bufferevent_data_cb writecb, // 写缓冲区清空时调用(可传 NULL)
bufferevent_event_cb eventcb, // 连接/错误/EOF 时调用
void *cbarg); // 用户参数
注意:
- bufferevent 内部用两个
evbuffer管理读写缓冲(通过bufferevent_get_input/bufferevent_get_output获取)。 eventcb的第二个参数是what(BEV_EVENT_READING/BEV_EVENT_WRITING/BEV_EVENT_EOF/BEV_EVENT_ERROR/BEV_EVENT_CONNECTED),用于区分事件类型。readcb中你用bufferevent_read从输入缓冲区拿数据;writecb意味着写缓冲区已清空(写完了),可以在此时继续写更多数据。- 与 event 不同,bufferevent 不需要手动
event_add,创建后自动处于"未激活但就绪"状态,用bufferevent_enable开启读写。
bufferevent_enable / bufferevent_disable — 控制读写 链接到标题
#include <event2/bufferevent.h>
int bufferevent_enable(struct bufferevent *bev, short events);
int bufferevent_disable(struct bufferevent *bev, short events);
功能:开启/关闭 bufferevent 的读或写。events 为 EV_READ | EV_WRITE。
注意:创建 bufferevent 后,默认读写都是禁用的——必须 bufferevent_enable(bev, EV_READ | EV_WRITE),否则回调不会被调用。
bufferevent_setcb(bev, on_read, NULL, on_event, NULL);
bufferevent_enable(bev, EV_READ | EV_WRITE); // 关键!
bufferevent_read / bufferevent_write / bufferevent_write_buffer 链接到标题
#include <event2/bufferevent.h>
#include <event2/buffer.h>
size_t bufferevent_read(struct bufferevent *bev, void *data, size_t size);
int bufferevent_write(struct bufferevent *bev, const void *data, size_t size);
int bufferevent_write_buffer(struct bufferevent *bev, struct evbuffer *buf);
功能:
bufferevent_read:从输入缓冲区读取数据,返回实际读取的字节数。输入缓冲区为空时不阻塞——依赖 libevent 的读回调来驱动。bufferevent_write:将数据写入输出缓冲区(不立即发送,由 libevent 在可写时自动发出),返回 0 成功,-1 失败。bufferevent_write_buffer:将整个 evbuffer 的内容全部追加到输出缓冲区(常用于转发/代理场景)。
注意:
- 不要在 readcb 中多次调用
bufferevent_read拿不满数据——每次 readcb 触发,输入缓冲区可能有任意长度的数据。循环读直到返回 0 或小于 size。 bufferevent_write返回 -1 仅表示"缓冲区满了或出错",正常情况下数据只是 pending,不会立即发送。- 输出缓冲区的数据由 libevent 在 socket 可写时自动发送,你不需要手动监控
EV_WRITE。
bufferevent_setwatermark — 设置水位线 链接到标题
#include <event2/bufferevent.h>
void bufferevent_setwatermark(struct bufferevent *bev, short events,
size_t lowmark, size_t highmark);
功能:
- 低水位 (lowmark):输入缓冲区累积到
lowmark字节才触发readcb;输出缓冲区降到lowmark以下才触发writecb。 - 高水位 (highmark):输入/输出缓冲区达到
highmark后停止读取/写入。
注意:events 为 EV_READ(读水位)、EV_WRITE(写水位),可分别设置。读高水位到达后,eventcb 收到 BEV_EVENT_READING 错误。
bufferevent_free — 释放 bufferevent 链接到标题
#include <event2/bufferevent.h>
void bufferevent_free(struct bufferevent *bev);
功能:释放 bufferevent 及其内部缓冲区。如果设置了 BEV_OPT_CLOSE_ON_FREE,还会自动 close(fd)。
注意:调用后 bev 不再可用。确保 bufferevent 已被 disable 或不在事件循环中(通常 bufferevent_free 内部会处理,但最好先 bufferevent_disable)。
evconnlistener_new_bind — 监听并接受连接 链接到标题
#include <event2/listener.h>
struct evconnlistener *evconnlistener_new_bind(
struct event_base *base,
evconnlistener_cb cb, // 接受新连接时调用
void *ptr, // 用户参数
unsigned flags, // LEV_OPT_*
int backlog, // listen 队列长度
const struct sockaddr *sa,
int socklen);
功能:一站式创建监听 socket,bind + listen + accept 全自动完成。新连接到达时调用 cb,在 cb 中拿到 fd 和地址。
常用 flags:
| flag | 含义 |
|---|---|
LEV_OPT_CLOSE_ON_FREE |
释放 listener 时 close 监听 fd |
LEV_OPT_REUSEABLE |
设置 SO_REUSEADDR(等同手动 setsockopt) |
回调原型:
typedef void (*evconnlistener_cb)(struct evconnlistener *listener,
evutil_socket_t fd,
struct sockaddr *addr, int socklen,
void *ctx);
注意:在回调中一般创建 bufferevent 来处理这个 fd——bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE),然后 bufferevent_setcb + bufferevent_enable。
完整示例:TCP Echo 服务端 链接到标题
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <arpa/inet.h>
struct event_base *base = NULL;
static void on_read(struct bufferevent *bev, void *ctx) {
struct evbuffer *input = bufferevent_get_input(bev);
struct evbuffer *output = bufferevent_get_output(bev);
evbuffer_add_buffer(output, input); // ① 输入 → 输出 = echo
}
static void on_event(struct bufferevent *bev, short what, void *ctx) {
if (what & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
bufferevent_free(bev); // ② 对端关闭或出错,释放 bufferevent
}
}
static void on_accept(struct evconnlistener *listener,
evutil_socket_t fd,
struct sockaddr *addr, int socklen,
void *ctx) {
// ③ 每个新连接创建一个 bufferevent
struct bufferevent *bev =
bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, on_read, NULL, on_event, NULL);
bufferevent_enable(bev, EV_READ | EV_WRITE);
}
static void on_signal(evutil_socket_t sig, short events, void *ctx) {
printf("Shutting down...\n");
event_base_loopbreak(base); // ④ Ctrl-C 停止循环
}
int main() {
base = event_base_new();
// 信号处理,Ctrl-C 优雅退出
struct event *sigev = evsignal_new(base, SIGINT, on_signal, NULL);
event_add(sigev, NULL);
// 监听
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(8080);
sin.sin_addr.s_addr = htonl(INADDR_ANY);
struct evconnlistener *listener =
evconnlistener_new_bind(base, on_accept, NULL,
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
-1, // ⑤ backlog = -1,由 libevent 自动选合理值
(struct sockaddr *)&sin, sizeof(sin));
printf("Echo server on port 8080\n");
event_base_dispatch(base); // ⑥ 开始循环
// 清理
evconnlistener_free(listener);
event_free(sigev);
event_base_free(base);
return 0;
}
要点回顾:① bufferevent 用
evbuffer_add_buffer高效转发(零拷贝);②BEV_OPT_CLOSE_ON_FREE让 socket 生命周期与 bufferevent 绑定,无需手动close;③event_base_loopbreak在信号回调中安全停止循环;④event_base_dispatch返回后才能event_base_free。
evbuffer 操作 链接到标题
bufferevent 底层使用 evbuffer(可自动增长的字节缓冲区)。
常用 evbuffer 函数 链接到标题
#include <event2/buffer.h>
struct evbuffer *evbuffer_new(void); // 创建空缓冲区
void evbuffer_free(struct evbuffer *buf); // 释放
int evbuffer_add(struct evbuffer *buf, const void *data, size_t size); // 追加数据
int evbuffer_add_buffer(struct evbuffer *dst, struct evbuffer *src); // 移动整个 src 到 dst(零拷贝)
int evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ...); // 格式化追加
size_t evbuffer_get_length(const struct evbuffer *buf); // 缓冲区中数据长度
size_t evbuffer_copyout(struct evbuffer *buf, void *data, size_t size); // 拷出数据(不消费)
int evbuffer_remove(struct evbuffer *buf, void *data, size_t size); // 消费(读出并移除)
char *evbuffer_readln(struct evbuffer *buf, size_t *n_read,
enum evbuffer_eol_style eol_style); // 读一行
struct evbuffer_ptr evbuffer_search(struct evbuffer *buf,
const char *what, size_t len,
const struct evbuffer_ptr *start); // 搜索子串
行尾风格:EVBUFFER_EOL_LF(\n)、EVBUFFER_EOL_CRLF(\r\n)、EVBUFFER_EOL_ANY 等。
常见模式 链接到标题
超时控制 链接到标题
// 带读取超时的 bufferevent
bufferevent_setcb(bev, on_read, NULL, on_event, NULL);
bufferevent_enable(bev, EV_READ | EV_WRITE);
// 设置 10 秒超时
struct timeval tv = {10, 0};
bufferevent_set_timeouts(bev, &tv, NULL); // 读超时 10s,写无超时
定时任务(一次性或周期性) 链接到标题
// 一次性:3 秒后执行
struct event *timer = event_new(base, -1, 0, on_timer, NULL);
struct timeval tv = {3, 0};
event_add(timer, &tv);
// 周期性:每 2 秒执行一次
struct event *persist_timer = event_new(base, -1, EV_PERSIST, on_timer, NULL);
struct timeval tv2 = {2, 0};
event_add(persist_timer, &tv2);
注意:定时器用
fd = -1表示与任何 socket 无关。
非阻塞 connect(配合 bufferevent) 链接到标题
struct bufferevent *bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, on_read, NULL, on_event, NULL);
// 异步连接,连接成功时 eventcb 收到 BEV_EVENT_CONNECTED
struct sockaddr_in sin = { ... };
bufferevent_socket_connect(bev, (struct sockaddr *)&sin, sizeof(sin));
注意事项 链接到标题
- 回调中不要阻塞:libevent 是单线程事件循环模型(默认),回调执行期间其他事件无法被处理。耗时操作应放到线程池或异步处理。
- 多线程:每个线程一个
event_base,用evthread_use_pthreads()启用线程支持。bufferevent 的BEV_OPT_THREADSAFE允许多线程安全访问。 - 内存管理:bufferevent 的输入/输出缓冲区默认无上限——高水位到达后停止接收,避免内存爆炸。用
bufferevent_setwatermark设置。 - bufferevent 与裸 event 的选择:bufferevent 封装了缓冲区和自动 I/O,适合大多数 TCP 场景;裸
event适合自定义 I/O(如event_new(fd, EV_READ|EV_PERSIST, cb, arg)然后你手动read/write)。 BEV_OPT_CLOSE_ON_FREE:强烈推荐——避免 fd 泄露。bufferevent 生命周期与 fd 绑定。- event_base_loopbreak vs event_base_loopexit:
loopbreak立即退出,loopexit等到下一个 event 处理完毕。清理阶段用loopbreak。 - 引用计数:libevent 2.x 中
event_base有内部引用计数,event_free/bufferevent_free会自动减少计数,不会在使用中的对象上崩溃。但仍建议按正确顺序释放。
头文件汇总 链接到标题
/* ---------- libevent 核心 ---------- */
#include <event2/event.h> // event_base_new, event_new, event_add, event_del,
// event_free, event_base_free, event_base_dispatch,
// event_base_loopbreak, struct event
#include <event2/buffer.h> // evbuffer_new, evbuffer_add, evbuffer_add_buffer,
// evbuffer_remove, evbuffer_readln, evbuffer_get_length
#include <event2/bufferevent.h> // bufferevent_socket_new, bufferevent_setcb,
// bufferevent_enable, bufferevent_read,
// bufferevent_write, bufferevent_free,
// bufferevent_set_timeouts, bufferevent_setwatermark,
// BEV_OPT_CLOSE_ON_FREE, struct bufferevent
#include <event2/listener.h> // evconnlistener_new_bind, evconnlistener_free,
// LEV_OPT_CLOSE_ON_FREE, LEV_OPT_REUSEABLE
#include <event2/util.h> // evutil_socket_t, evutil_make_socket_nonblocking
/* ---------- 线程支持(可选) ---------- */
#include <event2/thread.h> // evthread_use_pthreads, evthread_enable_lock_debugging