NeFut Logo NeFut
EN 管理员登录

[C++黑魔法] 单线程下的聊天服务器:性能与局限的深度探索

发布于:2026-05-30 07:51 最后更新:2026-06-06 13:04
#C++ #Asynchronous #Boost

Rubén Pérez,Boost.MySQL 的作者和 Boost.Redis 的共同维护者,构建了一个群聊服务器,以展示 Boost 库在实际应用中的协同工作。这个项目名为 BoostServerTech Chat,运行在单个 C++ 进程上,处理 HTTP、WebSocket、Redis 和 MySQL 连接,且全在一个线程中。本文将涵盖该设计的可行性、实际表现以及可能的不足之处。

技术栈

服务器位于一个 React/Next.js 前端后面,并与两个后端存储进行通信:Redis 用于聊天消息和会话(存储为流),MySQL 用于用户账户。C++ 进程负责其他所有操作:服务静态前端文件,暴露 REST API 以供登录和账户创建,并将 HTTP 连接升级为 WebSocket 以实现实时消息传递。HTTP 处理请求的延迟要求较低,例如账户创建和身份验证,而消息则通过 WebSocket 发送以保持低延迟。当用户输入消息时,前端通过 WebSocket 将其发送到服务器,服务器将其持久化到 Redis 流并广播给其他连接的客户端。

协程的应用

该服务器是完全异步的,使用 C++20 协程和 Boost.Asio。协程的使用使得异步代码的编写与同步代码类似,避免了回调地狱。以下是 HTTP 会话处理器中的代码片段:

// 处理常规 HTTP 请求,查询后端数据库
http::message_generator msg = co_await handle_http_request( parser.release(), *state );
// 确定是否应关闭连接
bool keep_alive = msg.keep_alive();
// 发送响应
co_await beast::async_write( stream, std::move(msg), asio::redirect_error(ec) );

这里的关键点在于,当执行到 co_await handle_http_request(...) 时,服务器向 Redis 或 MySQL 发送查询,协程会挂起直到数据库响应。与此同时,其他工作在同一线程上继续进行。当响应到达时,协程会从挂起的地方恢复执行。

单线程与无锁

以下是 main.cpp 中事件循环的设置:

// 服务器是单线程的,因此我们将
// 并发提示设置为 1
asio::io_context ctx(1);

一个 io_context,一个线程调用 ctx.run()。每个连接、每个数据库调用、每个 WebSocket 帧都经过同一个事件循环。这种设计的好处在于,共享可变状态不需要任何同步。服务器维护一个内存结构来跟踪哪些客户端订阅了哪些聊天室。在多线程服务器中,每次访问该结构都需要一个 strand,而正确处理多线程的 Asio 代码并不简单。在这里,它只需作为一个容器,无锁、无竞争、无负载下的排序错误。

服务的组合

所有服务都存在于传递给每个会话的 shared_state 对象中:

class shared_state {
    struct {
        std::string doc_root_;
        std::unique_ptr<redis_client> redis_;
        std::unique_ptr<mysql_client> mysql_;
        std::unique_ptr<cookie_auth_service> cookie_auth_;
        std::unique_ptr<pubsub_service> pubsub_;
    } impl_;
};

每个服务都是一个接口,后面有异步实现,以保持编译速度。Redis 客户端保持一个持久连接,而 MySQL 客户端使用连接池。发布/订阅服务是基于 Boost.MultiIndex 的内存容器。所有服务共享同一个 io_context,在一个线程上协作,无需显式协调。

局限性

显而易见的局限性是仅使用一个 CPU 核心。对于聊天服务器来说,这没问题,因为线程几乎一直在等待网络 I/O。但如果请求涉及 CPU 密集型工作(如图像处理、压缩、重度序列化),则会阻塞其他连接。更微妙的限制是横向扩展。发布/订阅状态存储在内存中,因此不能期望在负载均衡器后运行两个服务器实例时消息能到达所有客户端。Rubén 跟踪这个问题,计划用 Redis 通道或 XREAD 组替代内存中的发布/订阅,以便多个实例可以共享广播状态。

全貌

整个服务器约有 3000 行 C++ 代码。它组合了关键的 Boost 库(Asio、Beast、Redis、MySQL、JSON、Describe、MultiIndex、URL 和 Test),构成一个可以分叉、使用 CMake 构建并在 Docker 中部署的应用。没有框架,也没有隐藏细节的抽象层。每一层都在源代码中。BoostServerTech Chat 仓库包含完整的代码、构建说明和架构文档。

博主点评: 该项目展示了单线程异步服务器设计的潜力,尤其适合 I/O 密集型应用。然而,面对 CPU 密集型任务时的局限性也提醒我们,设计时需权衡性能与复杂性之间的关系。未来的扩展方向值得关注。

原文链接: https://www.reddit.com/r/cpp/comments/1to5dg2/what_happens_when_you_build_a_chat_server_on_one/

[h] 返回首页