2025-11-27 · libevent
【Libevent 深度剖析与实战指南】多构建工具链集成 (Build Systems)
告别手写 gcc 命令,详解如何在 Makefile、CMake 和 Bazel 项目中优雅地集成 Libevent。
在上一篇 Libevent 概览与 Reactor 模式 中,我们了解了 Libevent 的核心设计哲学。本篇我们将动手实践,完成开发环境的搭建,并编写第一个基于 Libevent 的 Echo Server。
Libevent 支持 Linux, macOS, Windows 等多种平台。为了方便学习,推荐使用 Linux 环境(如 Ubuntu/Debian 或 CentOS)。
虽然可以通过包管理器(如
apt-get install libevent-dev)安装,但为了深入源码分析,建议手动编译安装最新稳定版(目前为
2.1.12-stable)。
# 1. 下载源码
wget https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz
tar -xzvf libevent-2.1.12-stable.tar.gz
cd libevent-2.1.12-stable
# 2. 配置与编译
./configure --prefix=/usr/local --disable-openssl
make -j4
# 3. 安装
sudo make install
# 4. 更新动态库缓存
sudo ldconfig注:
--disable-openssl仅为了简化初次编译,后续章节涉及 SSL 时需要开启。
检查 /usr/local/lib 下是否有
libevent.so,以及
/usr/local/include 下是否有 event2
目录。
ls -l /usr/local/lib/libevent*
ls -d /usr/local/include/event2我们将编写一个简单的 Echo Server:它监听 9999 端口,接收客户端发送的数据,并原样返回。
echo_server.c)#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/listener.h>
#define PORT 9999
// 读回调:当 socket 有数据可读时被调用
static void conn_readcb(struct bufferevent *bev, void *user_data) {
struct evbuffer *input = bufferevent_get_input(bev);
struct evbuffer *output = bufferevent_get_output(bev);
// 将输入缓冲区的数据全部拷贝到输出缓冲区(实现 Echo)
evbuffer_add_buffer(output, input);
}
// 事件回调:处理连接建立、断开或错误
static void conn_eventcb(struct bufferevent *bev, short events, void *user_data) {
if (events & BEV_EVENT_EOF) {
printf("Connection closed.\n");
} else if (events & BEV_EVENT_ERROR) {
printf("Got an error on the connection: %s\n",
strerror(errno));
}
// 发生错误或连接断开时,释放 bufferevent
bufferevent_free(bev);
}
// 监听回调:当有新连接到来时被调用
static void listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
struct sockaddr *sa, int socklen, void *user_data) {
struct event_base *base = user_data;
struct bufferevent *bev;
printf("New connection received.\n");
// 为新连接创建一个 bufferevent
// BEV_OPT_CLOSE_ON_FREE: 释放 bev 时自动关闭底层 socket
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if (!bev) {
fprintf(stderr, "Error constructing bufferevent!\n");
event_base_loopbreak(base);
return;
}
// 设置回调函数:读回调、写回调(NULL)、事件回调
bufferevent_setcb(bev, conn_readcb, NULL, conn_eventcb, NULL);
// 启用读写事件
bufferevent_enable(bev, EV_READ | EV_WRITE);
}
int main(int argc, char **argv) {
struct event_base *base;
struct evconnlistener *listener;
struct sockaddr_in sin;
// 1. 创建 event_base (Reactor 实例)
base = event_base_new();
if (!base) {
fprintf(stderr, "Could not initialize libevent!\n");
return 1;
}
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
// 2. 创建监听器
// LEV_OPT_REUSEABLE: 设置 SO_REUSEADDR
// LEV_OPT_CLOSE_ON_FREE: 释放 listener 时关闭 socket
listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1,
(struct sockaddr*)&sin,
sizeof(sin));
if (!listener) {
fprintf(stderr, "Could not create a listener!\n");
return 1;
}
printf("Echo Server listening on port %d...\n", PORT);
// 3. 进入事件循环
event_base_dispatch(base);
// 4. 清理资源
evconnlistener_free(listener);
event_base_free(base);
printf("Done.\n");
return 0;
}完整代码: 01-echo-server.c
使用 gcc 编译,注意链接
libevent 库:
gcc -o echo_server echo_server.c -levent运行服务器:
./echo_server打开另一个终端,使用 nc (netcat) 或
telnet 连接:
nc 127.0.0.1 9999输入任意字符,你应该能看到服务器立即返回相同的内容。
在开发过程中,学会调试是必不可少的。
为了让 GDB 能显示源码,编译时需要加上 -g
选项:
gcc -g -o echo_server echo_server.c -levent启动调试:
gdb ./echo_server常用命令: * b conn_readcb:
在读回调函数处打断点。 * r: 运行程序。 *
bt: 查看函数调用栈
(Backtrace)。你会发现栈底通常是
event_base_loop。
Libevent 内部有自己的日志机制。在 main
函数开头开启调试日志,可以帮助排查问题:
// 在 event_base_new() 之前调用
event_enable_debug_mode();这会开启额外的检查(如事件是否未初始化就使用),但会带来一定的性能开销,生产环境慎用。
通过本篇,我们成功搭建了 Libevent
开发环境,并编写了一个基于 bufferevent 和
evconnlistener 的 Echo Server。
这短短几十行代码,其实已经包含了一个高性能网络服务器的雏形:
1. Reactor 初始化
(event_base_new) 2. 监听端口
(evconnlistener_new_bind) 3.
连接处理
(bufferevent_socket_new) 4.
事件循环
(event_base_dispatch)
在下一篇中,我们将探讨如何将 Libevent
集成到现代构建系统中,告别手写 gcc 命令。
上一篇: 00-intro/reactor-pattern.md - Libevent 概览与 Reactor 模式 下一篇: 00-intro/build-systems.md - 多构建工具链集成
把当前热点继续串成多页阅读,而不是停在单篇消费。
2025-11-27 · libevent
告别手写 gcc 命令,详解如何在 Makefile、CMake 和 Bazel 项目中优雅地集成 Libevent。
2025-11-27 · libevent
深入理解 Libevent 的核心设计哲学:Reactor 模式、异步 I/O 模型以及其整体架构解析。
2025-11-27 · libevent
解密 Libevent 如何封装 epoll、kqueue 等底层机制,实现跨平台的统一事件接口。
2025-11-27 · libevent
对比 Linux epoll、BSD kqueue 和 Windows IOCP 的异同,以及 Libevent 在不同平台上的实现差异与避坑指南。
在上一篇 Libevent 概览与 Reactor 模式 中,我们了解了 Libevent 的核心设计哲学。本篇我们将动手实践,完成开发环境的搭建,并编写第一个基于 Libevent 的 Echo Server。
Libevent 支持 Linux, macOS, Windows 等多种平台。为了方便学习,推荐使用 Linux 环境(如 Ubuntu/Debian 或 CentOS)。
虽然可以通过包管理器(如
apt-get install libevent-dev)安装,但为了深入源码分析,建议手动编译安装最新稳定版(目前为
2.1.12-stable)。
# 1. 下载源码
wget https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz
tar -xzvf libevent-2.1.12-stable.tar.gz
cd libevent-2.1.12-stable
# 2. 配置与编译
./configure --prefix=/usr/local --disable-openssl
make -j4
# 3. 安装
sudo make install
# 4. 更新动态库缓存
sudo ldconfig注:
--disable-openssl仅为了简化初次编译,后续章节涉及 SSL 时需要开启。
检查 /usr/local/lib 下是否有
libevent.so,以及
/usr/local/include 下是否有 event2
目录。
ls -l /usr/local/lib/libevent*
ls -d /usr/local/include/event2我们将编写一个简单的 Echo Server:它监听 9999 端口,接收客户端发送的数据,并原样返回。
echo_server.c)#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/listener.h>
#define PORT 9999
// 读回调:当 socket 有数据可读时被调用
static void conn_readcb(struct bufferevent *bev, void *user_data) {
struct evbuffer *input = bufferevent_get_input(bev);
struct evbuffer *output = bufferevent_get_output(bev);
// 将输入缓冲区的数据全部拷贝到输出缓冲区(实现 Echo)
evbuffer_add_buffer(output, input);
}
// 事件回调:处理连接建立、断开或错误
static void conn_eventcb(struct bufferevent *bev, short events, void *user_data) {
if (events & BEV_EVENT_EOF) {
printf("Connection closed.\n");
} else if (events & BEV_EVENT_ERROR) {
printf("Got an error on the connection: %s\n",
strerror(errno));
}
// 发生错误或连接断开时,释放 bufferevent
bufferevent_free(bev);
}
// 监听回调:当有新连接到来时被调用
static void listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
struct sockaddr *sa, int socklen, void *user_data) {
struct event_base *base = user_data;
struct bufferevent *bev;
printf("New connection received.\n");
// 为新连接创建一个 bufferevent
// BEV_OPT_CLOSE_ON_FREE: 释放 bev 时自动关闭底层 socket
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if (!bev) {
fprintf(stderr, "Error constructing bufferevent!\n");
event_base_loopbreak(base);
return;
}
// 设置回调函数:读回调、写回调(NULL)、事件回调
bufferevent_setcb(bev, conn_readcb, NULL, conn_eventcb, NULL);
// 启用读写事件
bufferevent_enable(bev, EV_READ | EV_WRITE);
}
int main(int argc, char **argv) {
struct event_base *base;
struct evconnlistener *listener;
struct sockaddr_in sin;
// 1. 创建 event_base (Reactor 实例)
base = event_base_new();
if (!base) {
fprintf(stderr, "Could not initialize libevent!\n");
return 1;
}
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
// 2. 创建监听器
// LEV_OPT_REUSEABLE: 设置 SO_REUSEADDR
// LEV_OPT_CLOSE_ON_FREE: 释放 listener 时关闭 socket
listener = evconnlistener_new_bind(base, listener_cb, (void *)base,
LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1,
(struct sockaddr*)&sin,
sizeof(sin));
if (!listener) {
fprintf(stderr, "Could not create a listener!\n");
return 1;
}
printf("Echo Server listening on port %d...\n", PORT);
// 3. 进入事件循环
event_base_dispatch(base);
// 4. 清理资源
evconnlistener_free(listener);
event_base_free(base);
printf("Done.\n");
return 0;
}完整代码: 01-echo-server.c
使用 gcc 编译,注意链接
libevent 库:
gcc -o echo_server echo_server.c -levent运行服务器:
./echo_server打开另一个终端,使用 nc (netcat) 或
telnet 连接:
nc 127.0.0.1 9999输入任意字符,你应该能看到服务器立即返回相同的内容。
在开发过程中,学会调试是必不可少的。
为了让 GDB 能显示源码,编译时需要加上 -g
选项:
gcc -g -o echo_server echo_server.c -levent启动调试:
gdb ./echo_server常用命令: * b conn_readcb:
在读回调函数处打断点。 * r: 运行程序。 *
bt: 查看函数调用栈
(Backtrace)。你会发现栈底通常是
event_base_loop。
Libevent 内部有自己的日志机制。在 main
函数开头开启调试日志,可以帮助排查问题:
// 在 event_base_new() 之前调用
event_enable_debug_mode();这会开启额外的检查(如事件是否未初始化就使用),但会带来一定的性能开销,生产环境慎用。
通过本篇,我们成功搭建了 Libevent
开发环境,并编写了一个基于 bufferevent 和
evconnlistener 的 Echo Server。
这短短几十行代码,其实已经包含了一个高性能网络服务器的雏形:
1. Reactor 初始化
(event_base_new) 2. 监听端口
(evconnlistener_new_bind) 3.
连接处理
(bufferevent_socket_new) 4.
事件循环
(event_base_dispatch)
在下一篇中,我们将探讨如何将 Libevent
集成到现代构建系统中,告别手写 gcc 命令。
上一篇: 00-intro/reactor-pattern.md - Libevent 概览与 Reactor 模式 下一篇: 00-intro/build-systems.md - 多构建工具链集成
把当前热点继续串成多页阅读,而不是停在单篇消费。
2025-11-27 · libevent
告别手写 gcc 命令,详解如何在 Makefile、CMake 和 Bazel 项目中优雅地集成 Libevent。
2025-11-27 · libevent
深入理解 Libevent 的核心设计哲学:Reactor 模式、异步 I/O 模型以及其整体架构解析。
2025-11-27 · libevent
解密 Libevent 如何封装 epoll、kqueue 等底层机制,实现跨平台的统一事件接口。
2025-11-27 · libevent
对比 Linux epoll、BSD kqueue 和 Windows IOCP 的异同,以及 Libevent 在不同平台上的实现差异与避坑指南。