引言
在前面的文章中,我们分别学习了 select、poll 和 epoll 三种 I/O 多路复用机制。虽然 epoll 性能卓越,但直接使用这些系统调用编写服务器存在以下痛点:
-
代码冗长:每次都要手动管理描述符集合、事件注册、循环检测
-
跨平台困难:Linux 用 epoll,macOS 用 kqueue,Windows 用 IOCP
-
缺乏扩展:定时器、信号处理等功能需要自己实现
Libevent 正是为了解决这些问题而生的。它是一个轻量级、高性能的 C 语言网络库,对底层 I/O 复用函数进行了统一封装,让开发者只需关注业务逻辑即可。

第一部分:Libevent 核心概念
一、三大核心组件

二、事件类型
| 事件类型 | 含义 | 使用场景 |
|---|---|---|
EV_READ |
读就绪 | 接收客户端数据、accept 新连接 |
EV_WRITE |
写就绪 | 发送数据给客户端 |
EV_TIMEOUT |
超时事件 | 定时任务、心跳检测 |
EV_SIGNAL |
信号事件 | 捕获 SIGINT、SIGTERM 等 |
EV_PERSIST |
永久事件 | 触发后保持注册状态,不用重新添加 |
EV_ET |
边缘触发 | 配合 epoll ET 模式(需系统支持) |
三、Reactor 模式
Libevent 采用经典的 Reactor(反应器)模式:

第二部分:Libevent 安装
Ubuntu/Debian 安装开发库
sudo apt install libevent-dev
CentOS/RHEL 安装
sudo yum install libevent-devel
编译时链接
gcc program.c -o program -levent
查看安装的头文件位置
ls /usr/include/event2/
第三部分:基本使用流程
一、最小化示例
cpp
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <event2/event.h>
/* 信号回调函数 */
void signal_cb(evutil_socket_t fd, short events, void *arg) {
printf("收到信号: %d\n", fd);
}
/* 定时回调函数 */
void timeout_cb(evutil_socket_t fd, short events, void *arg) {
printf("定时器触发!\n");
}
int main() {
// ===== 步骤1:创建事件循环引擎 =====
struct event_base *base = event_base_new();
if (!base) {
fprintf(stderr, "创建 event_base 失败\n");
return -1;
}
// ===== 步骤2:注册信号事件 =====
struct event *sig_ev = evsignal_new(base, SIGINT, signal_cb, NULL);
if (!sig_ev) {
fprintf(stderr, "创建信号事件失败\n");
return -1;
}
event_add(sig_ev, NULL); // 注册到事件循环
// ===== 步骤3:注册定时事件 =====
struct timeval tv = {5, 0}; // 5 秒后触发
struct event *time_ev = evtimer_new(base, timeout_cb, NULL);
if (!time_ev) {
fprintf(stderr, "创建定时事件失败\n");
return -1;
}
event_add(time_ev, &tv); // 注册并设置超时时间
// ===== 步骤4:启动事件循环(阻塞) =====
printf("事件循环启动...\n");
event_base_dispatch(base);
// ===== 步骤5:清理资源 =====
event_free(sig_ev);
event_free(time_ev);
event_base_free(base);
return 0;
}
编译运行:
gcc demo.c -o demo -levent
./demo
输出:事件循环启动...
5秒后:定时器触发!
Ctrl+C:收到信号: 2
二、使用流程总结

第四部分:事件类型详解
一、信号事件
cpp
void signal_cb(evutil_socket_t fd, short events, void *arg) {
printf("捕获信号 %d\n", fd);
}
// 注册 SIGINT 信号
struct event *sig_ev = evsignal_new(base, SIGINT, signal_cb, NULL);
event_add(sig_ev, NULL);
要点:
-
信号事件使用
evsignal_new创建 -
fd参数实际上是信号编号 -
信号没有描述符,直接传
base和信号值
二、定时事件
cpp
void timeout_cb(evutil_socket_t fd, short events, void *arg) {
printf("定时器触发!\n");
}
// 5 秒后触发一次
struct timeval tv = {5, 0}; // 秒, 微秒
struct event *time_ev = evtimer_new(base, timeout_cb, NULL);
event_add(time_ev, &tv);
持久定时器 :加上 EV_PERSIST 标志
cpp
// 每 2 秒触发一次
struct event *persist_ev = event_new(base, -1, EV_TIMEOUT | EV_PERSIST,
timeout_cb, NULL);
struct timeval tv = {2, 0};
event_add(persist_ev, &tv);
三、IO 事件
cpp
void read_cb(evutil_socket_t fd, short events, void *arg) {
char buf[128];
int n = recv(fd, buf, sizeof(buf) - 1, 0);
if (n > 0) {
buf[n] = '\0';
printf("收到: %s\n", buf);
send(fd, "OK", 2, 0);
} else {
// 客户端关闭
close(fd);
}
}
// 注册读事件(持久模式)
struct event *io_ev = event_new(base, client_fd,
EV_READ | EV_PERSIST, read_cb, NULL);
event_add(io_ev, NULL);
四、持久事件 EV_PERSIST
| 场景 | 使用标志 | 说明 |
|---|---|---|
| 一次性定时器 | EV_TIMEOUT |
触发后自动删除 |
| 周期性定时器 | `EV_TIMEOUT | EV_PERSIST` |
| 信号处理 | `EV_SIGNAL | EV_PERSIST` |
| IO 监听 | `EV_READ | EV_PERSIST` |
第五部分:回显服务器完整实现
一、数据结构设计
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <event2/event.h>
#define PORT 6000
#define BUFFER_SIZE 128
/* 客户端消息结构体 */
typedef struct {
int fd; // 文件描述符
struct event *ev; // 关联的事件指针
char buffer[BUFFER_SIZE]; // 数据缓冲区
} ClientData;
二、回调函数
cpp
/* 接收客户端数据 */
void recv_cb(evutil_socket_t fd, short events, void *arg) {
ClientData *data = (ClientData *)arg;
char buf[BUFFER_SIZE];
int n = recv(fd, buf, sizeof(buf) - 1, 0);
if (n <= 0) {
// 客户端断开
printf("客户端断开: fd=%d\n", fd);
event_del(data->ev); // 从事件循环中移除
event_free(data->ev); // 释放事件
close(fd); // 关闭描述符
free(data); // 释放自定义结构
} else {
buf[n] = '\0';
printf("收到 fd=%d: %s\n", fd, buf);
send(fd, "OK\n", 3, 0);
}
}
/* 接受客户端连接 */
void accept_cb(evutil_socket_t listen_fd, short events, void *arg) {
struct event_base *base = (struct event_base *)arg;
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
int client_fd = accept(listen_fd,
(struct sockaddr *)&client_addr, &len);
if (client_fd == -1) {
perror("accept");
return;
}
printf("新连接: fd=%d, IP=%s\n",
client_fd, inet_ntoa(client_addr.sin_addr));
// 为客户端分配数据结构
ClientData *data = (ClientData *)malloc(sizeof(ClientData));
if (!data) {
close(client_fd);
return;
}
memset(data, 0, sizeof(ClientData));
data->fd = client_fd;
// 注册客户端读事件
struct event *ev = event_new(base, client_fd,
EV_READ | EV_PERSIST,
recv_cb, data);
if (!ev) {
free(data);
close(client_fd);
return;
}
data->ev = ev;
event_add(ev, NULL);
}
三、主函数
cpp
int main() {
// 1. 创建监听套接字
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) { perror("socket"); return -1; }
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("bind"); close(listen_fd); return -1;
}
if (listen(listen_fd, 5) == -1) {
perror("listen"); close(listen_fd); return -1;
}
// 2. 创建 Libevent 实例
struct event_base *base = event_base_new();
if (!base) {
fprintf(stderr, "event_base_new 失败\n");
close(listen_fd);
return -1;
}
// 3. 注册监听套接字事件
struct event *listen_ev = event_new(base, listen_fd,
EV_READ | EV_PERSIST,
accept_cb, base);
if (!listen_ev) {
fprintf(stderr, "event_new 失败\n");
close(listen_fd);
event_base_free(base);
return -1;
}
event_add(listen_ev, NULL);
printf("Libevent 回显服务器启动,端口: %d\n", PORT);
// 4. 启动事件循环
event_base_dispatch(base);
// 5. 清理
event_free(listen_ev);
event_base_free(base);
close(listen_fd);
return 0;
}
四、编译测试
gcc echo_server.c -o echo_server -levent
./echo_server
另开终端测试
nc 127.0.0.1 6000
输入任意内容,应收到 "OK" 回复
第六部分:核心 API 速查表
一、event_base 相关
| API | 作用 |
|---|---|
event_base_new() |
创建事件循环引擎 |
event_base_dispatch(base) |
启动事件循环(阻塞) |
event_base_loopbreak(base) |
退出事件循环 |
event_base_free(base) |
释放引擎资源 |
二、event 相关
| API | 作用 |
|---|---|
event_new(base, fd, events, cb, arg) |
创建普通事件 |
evsignal_new(base, sig, cb, arg) |
创建信号事件 |
evtimer_new(base, cb, arg) |
创建定时事件 |
event_add(ev, timeout) |
注册事件到循环 |
event_del(ev) |
从循环中移除事件 |
event_free(ev) |
释放事件资源 |
三、回调函数格式
cpp
void callback(evutil_socket_t fd, short events, void *arg);
// fd: 触发事件的文件描述符(信号事件为信号编号)
// events:触发的事件类型(EV_READ、EV_TIMEOUT 等)
// arg: 用户自定义参数
第七部分:与原生 epoll 的对比
| 对比项 | 原生 epoll | Libevent |
|---|---|---|
| 代码量 | 需要手动管理一切 | 只需回调函数 |
| 跨平台 | 仅 Linux | Linux/macOS/Windows |
| 信号处理 | 需结合 signal() | 内置支持 |
| 定时器 | 需自己实现 | 内置支持 |
| 持久事件 | 需手动重新注册 | EV_PERSIST 一行搞定 |
| 学习成本 | 高 | 低 |
| 性能 | 最优 | 接近原生(封装开销极小) |
总结
一、Libevent 使用流程
① event_base_new() → 创建引擎
② event_new() → 创建事件(指定 fd、类型、回调)
③ event_add() → 注册事件
④ event_base_dispatch() → 启动循环
⑤ event_free() + event_base_free() → 清理
二、事件类型速记
| 宏 | 用途 |
|---|---|
EV_READ |
可读 |
EV_WRITE |
可写 |
EV_TIMEOUT |
超时 |
EV_SIGNAL |
信号 |
EV_PERSIST |
持久(触发后保持) |
EV_ET |
边缘触发 |
三、回调函数签名
cpp
void callback(evutil_socket_t fd, short events, void *arg);
四、一句话记忆
Libevent 封装了 epoll/select/poll,让你只需要写回调函数、注册事件、启动循环三步就能构建高性能网络服务器,同时内置了定时器和信号处理。