libevent 常用函数速查 链接到标题

libevent 是一个轻量级、高性能的事件通知库,封装了 select/poll/epoll/kqueue 等底层多路复用机制,提供统一的事件驱动 API。


安装 链接到标题

Ubuntu / Debian 链接到标题

sudo apt install libevent-dev

安装后头文件在 /usr/include/event2/,库文件为 libevent_core.solibevent_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 结尾的字符串数组,列出当前平台所有可用的多路复用后端(如 epollpollselectkqueuedevpoll 等)。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_reinitfork 后应直接在子进程中 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_addtimeval 参数)
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 等),argevent_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_delevent_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_freeevent_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_delevent_free 顺序调用。即使 evEV_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 的第二个参数是 whatBEV_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 的读或写。eventsEV_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 后停止读取/写入。

注意eventsEV_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));

注意事项 链接到标题

  1. 回调中不要阻塞:libevent 是单线程事件循环模型(默认),回调执行期间其他事件无法被处理。耗时操作应放到线程池或异步处理。
  2. 多线程:每个线程一个 event_base,用 evthread_use_pthreads() 启用线程支持。bufferevent 的 BEV_OPT_THREADSAFE 允许多线程安全访问。
  3. 内存管理:bufferevent 的输入/输出缓冲区默认无上限——高水位到达后停止接收,避免内存爆炸。用 bufferevent_setwatermark 设置。
  4. bufferevent 与裸 event 的选择:bufferevent 封装了缓冲区和自动 I/O,适合大多数 TCP 场景;裸 event 适合自定义 I/O(如 event_new(fd, EV_READ|EV_PERSIST, cb, arg) 然后你手动 read/write)。
  5. BEV_OPT_CLOSE_ON_FREE:强烈推荐——避免 fd 泄露。bufferevent 生命周期与 fd 绑定。
  6. event_base_loopbreak vs event_base_loopexitloopbreak 立即退出,loopexit 等到下一个 event 处理完毕。清理阶段用 loopbreak
  7. 引用计数: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