土法炼钢兴趣小组的算法知识备份

环境搭建与 Hello World

目录

在上一篇 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/event2

2. 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_server

2.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 开发环境,并编写了一个基于 buffereventevconnlistener 的 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 - 多构建工具链集成

返回 Libevent 专题索引


By .