一个基于 Boost.Asio 构建的轻量级、高性能服务器框架,旨在提供易于扩展和定制的能力。
- 高性能: 利用 Boost.Asio 的异步非阻塞 I/O 操作,高效处理并发连接。 异步 I/O 允许服务器在等待 I/O 操作完成时处理其他任务,从而提高了吞吐量和响应速度。
- 模块化设计: 可轻松使用自定义协议和请求处理程序进行扩展。 通过实现
ProtocolHandler和RequestHandler接口,您可以轻松添加对新协议和请求类型的支持。 - 多线程: 使用线程池管理传入连接和请求处理,最大限度地提高吞吐量。 线程池减少了创建和销毁线程的开销,从而提高了性能。
- HTTP 服务器示例: 包含一个基本的 HTTP 服务器实现,开箱即用地演示框架的功能。 HTTP 服务器示例演示了如何处理 HTTP 请求、提供静态文件和处理表单提交。
- 跨平台: 设计为跨平台兼容,支持 Windows 和 Linux (需要适当的构建配置)。 该框架可以使用 CMake 构建,CMake 是一个跨平台构建系统。
- 可配置的线程池: 可以根据硬件资源和预期工作负载调整线程池大小。 线程池的大小应该根据 CPU 核心数和预期的并发连接数进行调整。
- 错误处理和日志记录: 在整个框架中提供一致的错误处理和日志记录机制。 框架使用 Boost.Asio 的
boost::system::error_code进行错误处理,并使用std::cout和std::cerr进行日志记录。 - 拓展: 提供 wasm 模块进行拓展功能 (例如: gray_scale.cpp),提升性能。 WASM 模块允许您使用其他编程语言(如 C++)编写高性能代码,并在 Web 浏览器中运行它。
框架的核心组件包括:
- Server (服务器): 监听指定的端口,接受新的连接,并将它们分派给工作线程。 服务器使用
boost::asio::acceptor监听连接请求。 - HttpSession (连接/会话): 在您提供的代码中,框架使用
HttpSession类来处理客户端连接,它负责读取数据、写入数据,以及处理 HTTP 协议特定的细节。 传统上,这些职责通常由一个单独的Connection类承担。HttpSession类管理连接的生命周期,并处理部分读取和写入。 - IOContext: Boost.Asio 的核心组件,管理异步 I/O 操作。 可以将其视为驱动所有异步任务的引擎。
io_context管理事件循环和完成处理程序。 - ThreadPool (线程池): 用于执行连接处理逻辑的线程池,提高并发性和响应能力。 线程被重用于不同的连接,从而减少了创建和销毁线程的开销。
ThreadPool减少了创建和销毁线程的开销,从而提高了性能。 - RequestHandler (请求处理程序): 一个抽象类(接口),定义了如何处理客户端请求。 您需要实现具体的
RequestHandler类来处理特定的请求类型或业务逻辑。RequestHandler接口定义了handle_request()方法,该方法用于处理客户端请求并返回 HTTP 响应。 - ProtocolHandler (协议处理程序): 处理特定协议的细节(例如,HTTP)。 负责根据协议规则解析传入数据和格式化传出数据。
ProtocolHandler接口定义了parseRequest()和createResponse()方法,分别用于解析请求和格式化响应。
这些组件协同工作,提供了一个高效且可扩展的服务器框架。 Server 接受传入的连接,为每个连接创建一个 HttpSession 对象,然后使用 ThreadPool 分配一个线程来处理连接的处理。 HttpSession 对象使用 ProtocolHandler 来管理协议相关的细节(如 HTTP 头部),并将处理后的请求传递给 RequestHandler 以执行业务逻辑。
- Boost 库: 异步 I/O 和多线程所需。 使用系统的包管理器或 Vcpkg 安装 Boost (推荐用于 Windows)。 有关详细说明,请参阅 安装 部分。
- CMake: 构建项目所需。 从 https://cmake.org/ 下载并安装 CMake。
- MinGW (可选, Windows): 推荐在 Windows 上使用 MinGW 工具链进行构建。
Vcpkg 是 Windows 上 C++ 库的包管理器,简化了安装过程。
-
克隆 Vcpkg 仓库:
git clone https://github.com/microsoft/vcpkg.git
-
引导 Vcpkg: 这将准备 Vcpkg 以在您的系统上使用。
cd vcpkg ./bootstrap-vcpkg.bat # PowerShell ./bootstrap-vcpkg.sh # Git Bash
-
将 Vcpkg 与您的系统集成: 这设置 Vcpkg 以供 CMake 使用。
./vcpkg integrate install
-
安装 Boost: 这将下载并构建 Boost 库。 选择正确的架构(x64 用于 64 位构建,x86 用于 32 位)。
vcpkg install boost:x64-windows # 64 位构建 vcpkg install boost:x86-windows # 32 位构建
sudo apt update
sudo apt install libboost-dev libboost-system-dev libboost-thread-devbrew install boost-
克隆仓库:
git clone https://github.com/424635328/cpp-asio-server cd cpp-asio-server -
创建一个构建目录: 最好在单独的目录中进行构建,以保持源代码的整洁。
重要: 在生成构建文件之前,您 必须 告诉 CMake 在哪里可以找到 Vcpkg。 修改
CMakeLists.txt文件以指定 Vcpkg 工具链文件路径:set(CMAKE_TOOLCHAIN_FILE "<vcpkg安装路径>/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Vcpkg Toolchain File" FORCE)
将
<vcpkg安装路径>替换为 Vcpkg 安装的实际路径。 -
使用 CMake 生成构建文件: 使用的命令取决于您的构建环境。
-
使用 MinGW (Windows):
cmake .. -G "MinGW Makefiles" -
使用 Visual Studio (Windows):
cmake -B build -S . -A x64 -DCMAKE_BUILD_TYPE=Release # 明确指定架构和构建类型(在项目根目录下运行)
-
使用 Make (Linux/macOS):
cmake .. -DCMAKE_BUILD_TYPE=Release # 明确指定构建类型
-
-
构建项目:
-
使用 MinGW:
mingw32-make
-
使用 Visual Studio:
cmake --build build --config Release # 在项目根目录下运行 -
使用 Make:
make
-
-
导航到构建目录 (通常为
build/或build/Release/,具体取决于您的构建系统)。 -
运行可执行文件:
./my_server_framework # Linux/macOS ./my_server_framework.exe # Windows
-
打开您的 Web 浏览器并访问
http://127.0.0.1:8765。您应该会看到预期的响应 (例如,"Hello, World!" 或联系表单)。如果出现端口被占用的情况: 您可以尝试修改
main.cpp文件中的端口号,或者关闭占用该端口的进程。 您可以使用netstat -ano | findstr :8765(Windows) 或netstat -tulnp | grep :8765(Linux/macOS) 命令来查找占用该端口的进程。 -
验证服务器是否正在运行:
curl http://127.0.0.1:8765 # Linux/macOS curl http://127.0.0.1:8765 -o nul # Windows
或者,使用
netstat:netstat -ano | findstr :8765 # Windows netstat -tulnp | grep :8765 # Linux/macOS
该框架包含一个简单的 HTTP 服务器示例,演示了如何处理 HTTP 请求。
关键组件:
MyHttpServer: 服务器类,负责监听连接、接受连接以及管理最大并发连接数。MyHttpServer继承自Server类,并重写了create_session()方法,用于创建HttpSession对象。HttpRequestHandler: 处理逻辑以处理 HTTP 请求并生成适当响应。 这是定义服务器端点和行为的地方。HttpRequestHandler类实现了handle_request()方法,该方法用于处理客户端请求并返回 HTTP 响应。handle_request()方法可以处理静态文件请求、表单提交和 API 请求。HttpSession: 管理单个客户端会话,处理读取请求、发送响应以及潜在地保持连接活动(HTTP 持久连接)。HttpSession类负责读取 HTTP 请求、调用HttpRequestHandler处理请求、发送 HTTP 响应,以及关闭连接。
代码示例:
#include "server.h"
#include "http_request_handler.h"
#include "asio_context.h"
int main() {
// 确定可用的硬件线程数。
size_t num_threads = std::thread::hardware_concurrency();
// 创建一个具有确定大小的线程池的 AsioContext。 这管理着异步任务的执行。
AsioContext io_context(num_threads);
// 创建 HTTP 服务器,指定 io_context、要侦听的端口 (8765) 和最大连接数 (1000)。
MyHttpServer server(io_context, 8765, 1000);
// 启动 io_context,开始处理异步任务。
io_context.run();
return 0;
}请求处理:
HttpRequestHandler 负责解析 HTTP 请求并根据其内容生成响应。 这包括处理静态文件请求(提供 HTML、CSS、JavaScript、图像)以及处理示例 /contact 表单提交。
扩展 HTTP 服务器:
您可以通过修改 HttpRequestHandler 以添加新路由和处理逻辑来轻松扩展 HTTP 服务器。 请参阅 HttpRequestHandler::handle_request 函数,以获取有关如何添加新端点并定义其行为的示例。
示例:添加新的路由
HttpResponse HttpRequestHandler::handle_request(const HttpRequest& request) {
cout << "收到请求: " << request.method << " " << request.uri << endl;
if (request.uri == "/api/data" && request.method == "GET") {
// 处理 /api/data 请求
HttpResponse response;
response.status_code = 200;
response.headers["Content-Type"] = "application/json";
response.body = "{\"message\": \"Hello from API\"}";
return response;
} else if (request.uri == "/contact" && request.method == "POST") {
// ... (处理联系表单提交) ...
} else {
return handle_static_file_request(request);
}
}关键代码解释:
- 线程池的创建和管理:
AsioContext类负责创建和管理线程池。 线程池的大小应该根据 CPU 核心数和预期的并发连接数进行调整。 过多的线程会导致上下文切换开销,而过少的线程会导致吞吐量降低。 一般来说,线程池的大小设置为 CPU 核心数的 2 倍是一个不错的选择。 - 异步 I/O 的使用:
HttpSession类使用 Boost.Asio 的异步 I/O 操作来读取和写入数据。 异步 I/O 允许服务器在等待 I/O 操作完成时处理其他任务,从而提高了吞吐量和响应速度。 - 请求处理器的实现:
HttpRequestHandler类实现了handle_request()方法,该方法用于处理客户端请求并返回 HTTP 响应。handle_request()方法可以处理静态文件请求、表单提交和 API 请求。 - HTTP 状态码的使用:
HttpRequestHandler类使用 HTTP 状态码来指示请求的结果。 例如,200 OK表示请求成功,404 Not Found表示请求的资源不存在。
可以通过命令行选项或修改 main.cpp 文件来配置服务器。 您可以更改侦听端口、最大连接数以及其他参数。
./my_server_framework --port 9000 --max_connections 2000// main.cpp(示例)
#include <boost/program_options.hpp> // 引入 boost program_options
namespace po = boost::program_options;
int main(int argc, char* argv[]) {
try {
// 定义命令行选项
po::options_description desc("Allowed options");
desc.add_options()
("help", "produce help message") // 帮助信息
("port", po::value<short>()->default_value(8765), "set listening port") // 设置监听端口
("max_connections", po::value<size_t>()->default_value(std::numeric_limits<size_t>::max()), "set max connections"); // 设置最大连接数
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm); // 解析命令行参数
po::notify(vm);
if (vm.count("help")) {
cout << desc << endl;
return 0;
}
short port = vm["port"].as<short>(); // 获取端口号
size_t max_connections = vm["max_connections"].as<size_t>(); // 获取最大连接数
// ... (创建 AsioContext 和 MyHttpServer) ...
} catch (exception& e) {
cerr << "异常: " << e.what() << endl;
return 1;
}
return 0;
}Boost.Program_options 使用说明:
- 定义选项: 使用
po::options_description类定义命令行选项。 每个选项都有一个名称、一个描述和一个类型。 - 解析命令行: 使用
po::store()函数解析命令行参数,并将结果存储在po::variables_map对象中。 - 访问选项: 使用
vm["option_name"].as<type>()方法访问选项的值。
线程池配置:
线程池的大小在 AsioContext 的构造函数中配置。 通常,将其设置为等于可用的硬件线程数,以获得最佳性能。
size_t num_threads = std::thread::hardware_concurrency();
AsioContext io_context(num_threads);线程池大小的权衡:
- 过多的线程: 会导致上下文切换开销增加,从而降低性能。
- 过少的线程: 会导致吞吐量降低,因为服务器无法充分利用 CPU 资源。
建议的线程池大小计算方法:
线程数 = CPU 核心数 * 2
错误处理和日志记录:
该框架利用 Boost.Asio 的 boost::system::error_code 在异步操作中实现一致的错误处理。 在每次异步操作之后,检查 error_code 对象以确定操作是否成功。 使用 std::cout 和 std::cerr 提供基本日志记录,所有控制台输出都受到互斥锁的保护,以确保线程安全。
注意: 确保所有控制台输出(包括日志消息)均为英文,以最大程度地减少 CMake 执行期间的潜在警告和错误,并保持一致性。 AsioContext::cout_mutex 用于保护 std::cout 和 std::cerr 的访问,以避免多线程环境下的竞争条件。 不正确使用互斥锁可能会导致死锁,请确保始终以相同的顺序获取互斥锁。
示例:错误处理
boost::asio::async_read(socket_, buffer_, boost::asio::transfer_at_least(1),
[this](boost::system::error_code ec, size_t bytes_transferred) {
if (ec) {
std::cerr << "Error reading from socket: " << ec.message() << std::endl;
stop();
return;
}
// ... (处理读取的数据) ...
});该框架允许您通过实现 ProtocolHandler 接口来添加对自定义协议的支持。 请注意,在当前的代码结构中,协议处理的逻辑主要集中在 HttpSession 类中,而不是一个独立的 Connection 类。
-
定义一个协议处理程序类: 创建一个继承自
ProtocolHandler的类。 此类将包含用于解析和生成协议特定数据的逻辑。class MyProtocolHandler : public ProtocolHandler { public: // 实现协议解析逻辑。 从缓冲区中提取请求,返回请求字符串和已消耗的字节数。 std::pair<std::string, size_t> parseRequest(boost::asio::streambuf& buffer) override { // ... (协议解析逻辑) ... } // 将响应字符串转换为可以在套接字上发送的一系列缓冲区。 std::vector<boost::asio::const_buffer> createResponse(const std::string& response) override { // ... (响应格式化逻辑) ... } };
-
修改
HttpSession类以支持新的协议:- 协议识别: 在
HttpSession::start()方法中,添加逻辑以识别当前连接使用的协议。 这可能涉及读取连接的初始几个字节,并根据这些字节判断协议类型。 - 协议处理程序选择: 根据识别出的协议,创建相应的
ProtocolHandler对象。 - 请求处理: 修改
HttpSession::do_read()和HttpSession::do_write()方法,使用选定的ProtocolHandler来解析请求和格式化响应。
示例:修改
HttpSession::start()void HttpSession::start() { { std::lock_guard<std::mutex> lock(AsioContext::cout_mutex); cout << "HTTP Session started." << endl; } try { // 确定协议 (示例:检查前几个字节) boost::asio::async_read(socket_, boost::asio::buffer(read_buffer_, 4), // 读取 4 个字节 [this](boost::system::error_code ec, std::size_t bytes_transferred) { if (!ec) { std::string protocol_identifier(read_buffer_, bytes_transferred); if (protocol_identifier == "MYPR") { // 自定义协议标识 protocol_handler_ = std::make_shared<MyProtocolHandler>(); } else { protocol_handler_ = std::make_shared<HttpProtocolHandler>(); } do_read(); // 开始读取数据 (使用正确的协议处理程序) } else { std::lock_guard<std::mutex> lock(AsioContext::cout_mutex); cerr << "Error reading protocol identifier: " << ec.message() << endl; stop(); } }); } catch (std::exception& e) { std::lock_guard<std::mutex> lock(AsioContext::cout_mutex); cerr << "Exception in HttpSession::start: " << e.what() << endl; stop(); // 发生异常,停止会话 } }
注意: 您还需要相应地修改
HttpSession::do_read()和HttpSession::do_write()方法,以便使用protocol_handler_来解析请求和格式化响应。 - 协议识别: 在
-
简单的回显服务器:
class EchoRequestHandler : public RequestHandler { public: HttpResponse handle_request(const HttpRequest& request) override { HttpResponse response; response.status_code = 200; response.body = request.body; // 将请求正文回显到客户端。 无论客户端发送什么,服务器都会发回什么。 response.headers["Content-Type"] = "text/plain"; return response; } };
-
静态文件服务器:
该框架的
HttpRequestHandler已经包含静态文件服务功能。 将您的文件放在web/目录中,它们将可以通过 HTTP 访问。 默认情况下,访问根路径 (/) 将提供web/index.html文件。目录结构:
cpp-asio-server/ ├── web/ │ ├── index.html │ ├── style.css │ └── script.js ├── src/ │ ├── ... ├── ...
为了保持代码库中的一致性和可读性,请遵循以下代码风格约定:
- 使用 4 个空格进行缩进。
- 对于类和函数名称,使用 PascalCase(也称为大驼峰式命名)(例如,
MyClass,CalculateValue)。 - 对于变量名,使用 snake_case (例如,
my_variable,user_name)。 - 添加适当的注释来解释代码的目的和功能。 专注于代码 为什么 要执行它所执行的操作,而不仅仅是代码 做什么。
- 用英语编写注释和文档。
- 使用代码格式化工具(例如,clang-format)来自动格式化代码,以确保代码库中的一致性。
欢迎贡献! 请遵循以下准则:
-
Fork 该仓库。
-
为您的功能或错误修复创建一个新分支。 为分支提供一个描述性名称。
git checkout -b feature/my-new-feature
-
实现您的更改并提交它们,并使用清晰、简洁和信息丰富的提交消息。
- 提交消息应概括提交中所做的更改。
- 如果您的更改修复了一个错误,请在提交消息中包含错误编号。
- 如果您的更改添加了一个新功能,请提供一个简短的代码示例,演示该功能的用法。
- 遵循 Conventional Commits 规范,以使提交历史更加清晰和一致。
git commit -m "feat: 添加我的新功能:实现了 X、Y 和 Z" -
在提交拉取请求之前,请确保您的代码符合代码风格约定。
-
运行所有单元测试(如果适用),以验证您的更改是否没有引入回归。
-
提交拉取请求。
- 在拉取请求说明中,提供您所做更改的详细说明。
- 如果您的更改修复了一个错误,请在拉取请求说明中包含错误编号。
- 如果您的更改添加了一个新功能,请提供一个简短的代码示例,演示在拉取请求说明中该功能的用法。
-
代码审查:其他贡献者将审查您的代码,提供反馈并提出改进建议。 准备好处理收到的任何反馈。
-
测试:您的代码将被自动测试,以确保它不会破坏现有功能。
-
合并:一旦您的代码通过代码审查和测试,它将被合并到主分支中。
为了评估框架的性能,我们进行了一系列基准测试。
本节展示了对服务器进行的初步性能基准测试结果。请注意,这些结果仅供参考,实际性能可能因环境而异。
测试环境:
- CPU: Intel Core r7-5800H
- 内存: 16GB DDR4
- 操作系统: Ubuntu 20.04
测试工具:
wrk(https://github.com/wg/wrk) - HTTP 基准测试工具
测试用例:
- Hello World: 服务器返回一个简单的 "Hello, World!" 字符串。
- 静态网页: 服务器返回已加载 WASM 模块的
index.html页面 (更改日期:2025.03.23)。
测试结果 (持续更新):
| 测试用例 | 并发连接数 | 每秒请求数 (RPS) | 平均延迟 (毫秒) |
|---|---|---|---|
| Hello World | 100 | 10000 | TBD |
| Hello World | 1000 | 80000 | TBD |
| 静态网页 | 100 | - | - |
| 静态网页 | 1000 | - | - |
TBD: 待确定 (To Be Determined)
测试命令:
wrk -t12 -c100 -d10s http://127.0.0.1:8765参数说明:
-t12: 使用 12 个线程。-c100: 保持 100 个并发连接。-d10s: 测试持续 10 秒。
免责声明:
上述测试结果是在特定硬件和软件配置下获得的。 实际性能可能因硬件、网络条件、服务器配置以及其他因素而有所不同。我们强烈建议您在自己的环境中进行基准测试,以获得更准确的结果。 我们会定期更新这些测试结果,并添加更多测试用例。
该项目已获得 MIT 许可证 的许可。