在上一篇 Libevent 概览与 Reactor 模式 中,我们了解了 Libevent 的核心设计哲学。本篇我们将动手实践,完成开发环境的搭建,并编写第一个基于 Libevent 的 Echo Server。
1. 环境搭建
Libevent 支持 Linux, macOS, Windows 等多种平台。为了方便学习,推荐使用 Linux 环境(如 Ubuntu/Debian 或 CentOS)。
1.1. 源码编译安装 (推荐)
虽然可以通过包管理器(如
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 时需要开启。
1.2. 验证安装
检查 /usr/local/lib 下是否有
libevent.so,以及
/usr/local/include 下是否有 event2
目录。
ls -l /usr/local/lib/libevent*
ls -d /usr/local/include/event22. Hello World: Echo Server
我们将编写一个简单的 Echo Server:它监听 9999 端口,接收客户端发送的数据,并原样返回。
2.1. 代码实现
(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
2.2. 编译与运行
使用 gcc 编译,注意链接
libevent 库:
gcc -o echo_server echo_server.c -levent运行服务器:
./echo_server2.3. 测试
打开另一个终端,使用 nc (netcat) 或
telnet 连接:
nc 127.0.0.1 9999输入任意字符,你应该能看到服务器立即返回相同的内容。
3. 调试环境配置
在开发过程中,学会调试是必不可少的。
3.1. GDB 调试
为了让 GDB 能显示源码,编译时需要加上 -g
选项:
gcc -g -o echo_server echo_server.c -levent启动调试:
gdb ./echo_server常用命令: * b conn_readcb:
在读回调函数处打断点。 * r: 运行程序。 *
bt: 查看函数调用栈
(Backtrace)。你会发现栈底通常是
event_base_loop。
3.2. 查看 Libevent 日志
Libevent 内部有自己的日志机制。在 main
函数开头开启调试日志,可以帮助排查问题:
// 在 event_base_new() 之前调用
event_enable_debug_mode();这会开启额外的检查(如事件是否未初始化就使用),但会带来一定的性能开销,生产环境慎用。
4. 总结
通过本篇,我们成功搭建了 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 - 多构建工具链集成