从零构建现代C++ Web服务器(二):协程异步与 PMR 内存池

从零构建现代C++ Web服务器(二):协程异步与 PMR 内存池 系列导航:第一篇:设计理念 | 第二篇:协程与内存池(本文) | 第三篇:路由、中间件与SSL | 第四篇:实战与性能 | 第五篇:Cookie、Session与文件服务 | 第六篇:数据库中间件 前置知识 了解协程关键字(co_await、co_return、co_yield)的基本含义 理解智能指针和 RAII 模式 了解 std::pmr(Polymorphic Memory Resource)的基本概念 目录 从零构建现代C++ Web服务器(二):协程异步与 PMR 内存池 前置知识 目录 1. 从回调地狱到 co_await 1.1 传统回调方式 1.2 协程方式 1.3 协程的工作原理(简化) 2. Awaitable:hical 的协程基石 工具函数 3. 协程在框架中的实际应用 3.1 Accept 循环 3.2 HTTP 会话处理 3.3 协程执行全景 4. PMR 内存池:为什么默认 allocator 不够好 4.1 问题分析 4.2 Benchmark 对比 5. 三层内存架构深度剖析 5.1 教学代码:从零实现简化版三层 PMR 5.2 教学代码:三层管理器 5.3 generation 计数器的妙用 6. PmrBuffer:零拷贝缓冲区 6.1 设计思路 6.2 教学代码:简化版 PmrBuffer 6.3 makeSpace 策略图解 7. 协程 + PMR 协同 8. 总结 核心要点 下篇预告 1. 从回调地狱到 co_await 1.1 传统回调方式 假设我们要写一个 TCP Echo Server。传统的异步回调方式是这样的: ...

April 12, 2026 · 12 min · 2520 words

从零构建现代C++ Web服务器(五):Cookie、Session、静态文件与文件上传

从零构建现代C++ Web服务器(五):Cookie、Session、静态文件与文件上传 系列导航:第一篇:设计理念 | 第二篇:协程与内存池 | 第三篇:路由、中间件与SSL | 第四篇:实战与性能 | 第五篇:Cookie、Session与文件服务(本文) | 第六篇:数据库中间件 前置知识 阅读过本系列前四篇(特别是第三篇的中间件洋葱模型) 了解 HTTP Cookie 与 Session 基本概念 了解 multipart/form-data 编码格式基本原理 目录 1. Web 应用的"最后一公里" 2. Cookie:无状态协议的状态记忆 3. Session:从 Cookie 到有状态会话 4. 静态文件服务:安全地托管资源 5. Multipart 文件上传:解析 RFC 7578 6. 综合实战:带登录的文件管理服务 7. 全系列总结 1. Web 应用的"最后一公里" 经过前四篇的铺垫,hical 已经具备了完整的 HTTP 服务器骨架——协程驱动的异步 I/O、PMR 内存池、双策略路由、洋葱模型中间件、SSL/WebSocket 支持,以及反射宏系统。 但如果你真正尝试用它搭建一个 Web 应用,很快就会发现少了几样东西:用户登录后刷新页面状态丢失、无法提供前端静态资源、用户没法上传头像文件。这些功能看似"基础",却是 Web 应用从"能跑通"到"能用"的最后一公里。 hical v1.0.0 补齐了这四块拼图: 模块 解决的问题 集成方式 核心文件 Cookie HTTP 无状态协议下的客户端状态存储 req.cookie() / res.setCookie() Cookie.h HttpRequest.cpp HttpResponse.cpp Session 服务端有状态会话管理 makeSessionMiddleware() 中间件 Session.h Session.cpp StaticFiles 安全地托管前端/资源文件 serveStatic() 工厂函数 StaticFiles.h Multipart 文件上传(RFC 7578) MultipartParser::parse() 静态方法 Multipart.h Multipart.cpp 它们在 hical 整体架构中的位置: ...

April 12, 2026 · 20 min · 4181 words

从零构建现代C++ Web服务器(四):实战案例与性能调优

从零构建现代C++ Web服务器(四):实战案例与性能调优 系列导航:第一篇:设计理念 | 第二篇:协程与内存池 | 第三篇:路由、中间件与SSL | 第四篇:实战与性能(本文) | 第五篇:Cookie、Session与文件服务 | 第六篇:数据库中间件 前置知识 阅读过本系列前三篇 了解 RESTful API 基本概念 了解 JSON 序列化/反序列化 目录 1. 完整案例:RESTful API 服务 2. 完整案例:WebSocket 实时通信 3. 反射宏系统 4. 性能调优实战 5. 安全加固清单 6. 错误处理体系 7. 系列总结 1. 完整案例:RESTful API 服务 1.1 从零构建用户管理 API 让我们用 hical 构建一个完整的用户管理 REST API,包含路由注册、中间件、JSON 请求/响应和路径参数。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 #include "core/HttpServer.h" #include <boost/json.hpp> #include <iostream> using namespace hical; namespace json = boost::json; int main() { HttpServer server(8080); // ============ 中间件 ============ // 日志中间件:记录请求方法、路径和响应状态码 server.use([](HttpRequest& req, MiddlewareNext next) -> Awaitable<HttpResponse> { auto start = std::chrono::steady_clock::now(); std::cout << httpMethodToString(req.method()) << " " << req.path() << std::endl; auto res = co_await next(req); auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>( std::chrono::steady_clock::now() - start).count(); std::cout << " -> " << static_cast<int>(res.statusCode()) << " (" << elapsed << "us)" << std::endl; co_return res; }); // 认证中间件(简化版) server.use([](HttpRequest& req, MiddlewareNext next) -> Awaitable<HttpResponse> { // 公开路由不需要认证 if (req.path() == "/" || req.path() == "/api/status") { co_return co_await next(req); } auto authHeader = req.header("Authorization"); if (authHeader.empty()) { co_return HttpResponse::badRequest("Missing Authorization header"); } co_return co_await next(req); }); // ============ 路由 ============ // GET / — 首页 server.router().get("/", [](const HttpRequest&) -> HttpResponse { return HttpResponse::ok("User Management API v1.0"); }); // GET /api/status — 状态查询 server.router().get("/api/status", [](const HttpRequest&) -> HttpResponse { return HttpResponse::json({ {"status", "running"}, {"version", "0.2.0"}, {"framework", "hical"} }); }); // GET /api/users — 用户列表 server.router().get("/api/users", [](const HttpRequest& req) -> HttpResponse { // 查询参数示例 auto query = req.query(); json::array users; users.push_back(json::object{ {"id", 1}, {"name", "Alice"}, {"email", "alice@example.com"}}); users.push_back(json::object{ {"id", 2}, {"name", "Bob"}, {"email", "bob@example.com"}}); return HttpResponse::json({{"users", users}, {"total", 2}}); }); // GET /users/{id} — 查询单个用户(路径参数) server.router().get("/users/{id}", [](const HttpRequest& req) -> HttpResponse { auto userId = req.param("id"); return HttpResponse::json({ {"id", userId}, {"name", "User " + userId}, {"email", userId + "@example.com"} }); }); // POST /api/users — 创建用户(JSON 请求体) server.router().post("/api/users", [](const HttpRequest& req) -> HttpResponse { try { auto body = req.jsonBody(); auto& obj = body.as_object(); // 提取字段 auto name = std::string(obj.at("name").as_string()); auto email = std::string(obj.at("email").as_string()); return HttpResponse::json({ {"message", "User created"}, {"name", name}, {"email", email} }); } catch (const std::exception& e) { return HttpResponse::badRequest( std::string("Invalid JSON: ") + e.what()); } }); // PUT /users/{id} — 更新用户 server.router().put("/users/{id}", [](const HttpRequest& req) -> HttpResponse { auto userId = req.param("id"); auto body = req.jsonBody(); return HttpResponse::json({ {"message", "User " + userId + " updated"}, {"data", body} }); }); // DELETE /users/{id} — 删除用户 server.router().del("/users/{id}", [](const HttpRequest& req) -> HttpResponse { auto userId = req.param("id"); return HttpResponse::json({ {"message", "User " + userId + " deleted"} }); }); // ============ 启动 ============ std::cout << "hical User API Server listening on :8080" << std::endl; server.start(); } 1.2 测试 API 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 状态查询 curl http://localhost:8080/api/status # 用户列表 curl -H "Authorization: Bearer token" http://localhost:8080/api/users # 查询单个用户 curl -H "Authorization: Bearer token" http://localhost:8080/users/42 # 创建用户 curl -X POST http://localhost:8080/api/users \ -H "Authorization: Bearer token" \ -H "Content-Type: application/json" \ -d '{"name":"Charlie","email":"charlie@example.com"}' # 删除用户 curl -X DELETE -H "Authorization: Bearer token" http://localhost:8080/users/42 2. 完整案例:WebSocket 实时通信 2.1 WebSocket Echo + 广播 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 #include "core/HttpServer.h" #include "core/WebSocket.h" #include <iostream> #include <mutex> #include <set> using namespace hical; // 简单的连接管理器 struct ConnectionManager { std::mutex mutex; std::set<WebSocketSession*> sessions; void add(WebSocketSession* s) { std::lock_guard lock(mutex); sessions.insert(s); } void remove(WebSocketSession* s) { std::lock_guard lock(mutex); sessions.erase(s); } }; int main() { HttpServer server(8080); ConnectionManager conns; // HTTP 路由 server.router().get("/", [](const HttpRequest&) -> HttpResponse { return HttpResponse::ok("WebSocket Server - connect to /ws/echo or /ws/chat"); }); // WebSocket Echo server.router().ws("/ws/echo", [](const std::string& msg, WebSocketSession& ws) -> Awaitable<void> { co_await ws.send("Echo: " + msg); }, [](WebSocketSession& ws) -> Awaitable<void> { co_await ws.send("Connected to echo service!"); }); // WebSocket Chat(广播) server.router().ws("/ws/chat", // 消息回调:广播给所有连接 [&conns](const std::string& msg, WebSocketSession& ws) -> Awaitable<void> { std::lock_guard lock(conns.mutex); for (auto* session : conns.sessions) { if (session != &ws && session->isOpen()) { co_await session->send(msg); } } }, // 连接回调:注册到管理器 [&conns](WebSocketSession& ws) -> Awaitable<void> { conns.add(&ws); co_await ws.send("Welcome to the chat room!"); }); std::cout << "WebSocket Server on :8080" << std::endl; server.start(); } 3. 反射宏系统 3.1 HICAL_JSON:自动 DTO 序列化 手动写 JSON 序列化代码很繁琐。hical 提供了 HICAL_JSON 宏,一行代码实现结构体到 JSON 的自动转换: ...

April 12, 2026 · 11 min · 2188 words

告别回调地狱:在 C++ Web 框架中全面拥抱协程

告别回调地狱:在 C++ Web 框架中全面拥抱协程 本文以 Hical 框架为例,展示如何用 C++20 协程 + Boost.Asio 构建一个全协程化的 HTTP 服务器,以及这样做的工程权衡。 回调有什么问题? 几乎所有 C++ 网络框架的 1.0 版本都是回调驱动的。一个简单的"读取请求 → 处理 → 发送响应"流程,回调版本长这样: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void onAccept(tcp::socket socket) { auto buf = std::make_shared<flat_buffer>(); auto req = std::make_shared<http::request<string_body>>(); http::async_read(socket, *buf, *req, [&socket, buf, req](error_code ec, size_t) { if (ec) return; auto res = std::make_shared<http::response<string_body>>(); // ... 处理请求,构建响应 ... http::async_write(socket, *res, [&socket, res](error_code ec, size_t) { if (ec) return; socket.shutdown(tcp::socket::shutdown_send); }); }); } 问题很明显: ...

April 12, 2026 · 3 min · 539 words

用 C++20 Concepts 设计可替换的网络后端:从 Boost.Asio 到未来的 io_uring

用 C++20 Concepts 设计可替换的网络后端:从 Boost.Asio 到未来的 io_uring 本文以 Hical 框架为例,展示如何用 C++20 Concepts 约束网络后端接口,实现编译期类型安全的后端抽象。 问题:网络后端绑定的困境 大多数 C++ 网络框架和底层网络库深度绑定。Drogon 绑定 Trantor,muduo 绑定自研的 EventLoop。一旦想换后端(比如从 epoll 切到 io_uring),基本等于重写。 原因是传统的抽象手段——虚函数继承——有两个问题: 运行时开销:每次调用都经过 vtable 接口松散:基类定义了接口,但"你的实现是否真的完整?“只能在链接期或运行时才知道 Concepts:编译期的接口约束 C++20 Concepts 提供了一种具名约束机制——在编译期验证类型是否满足一组要求: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 template <typename T> concept EventLoopLike = requires(T loop, std::function<void()> func, double delay) { { loop.run() } -> std::same_as<void>; { loop.stop() } -> std::same_as<void>; { loop.isRunning() } -> std::convertible_to<bool>; { loop.post(func) } -> std::same_as<void>; { loop.dispatch(func) } -> std::same_as<void>; { loop.runAfter(delay, func) } -> std::convertible_to<uint64_t>; { loop.runEvery(delay, func) } -> std::convertible_to<uint64_t>; { loop.cancelTimer(uint64_t{}) } -> std::same_as<void>; { loop.isInLoopThread() } -> std::convertible_to<bool>; { loop.index() } -> std::convertible_to<size_t>; { loop.allocator() } -> std::same_as<std::pmr::polymorphic_allocator<std::byte>>; }; 如果某个类型缺少 run() 方法或返回类型不对,编译器立即报错,而不是在链接时给出晦涩的"未定义引用”。 ...

April 12, 2026 · 3 min · 580 words

用 if constexpr + 模板在一份代码中同时处理 TCP 和 SSL 连接

用 if constexpr + 模板在一份代码中同时处理 TCP 和 SSL 连接 本文以 Hical 框架的 GenericConnection 为例,展示如何用 C++17 的 if constexpr + 类型萃取在一个模板类中统一 TCP 和 SSL 两种连接,实现编译期零开销分支。 问题:TCP 和 SSL 的代码高度相似 TCP 连接和 SSL 连接的区别只有三处: socket 类型不同:tcp::socket vs ssl::stream<tcp::socket> 连接建立多一步 TLS 握手 关闭多一步 ssl::stream::async_shutdown 其余 99% 的代码——读循环、写循环、缓冲区管理、回调触发、状态机——完全相同。 传统方案:继承 + 虚函数 1 2 3 4 5 6 7 8 9 class TcpConnection : public Connection { tcp::socket socket_; void doRead() override { ... } }; class SslConnection : public Connection { ssl::stream<tcp::socket> socket_; void doRead() override { ... } // 几乎一样的代码 }; 问题: 两个类 90% 的代码重复 每次调用 doRead/doWrite 都经过虚函数表间接调用 修改共同逻辑需要同步两处 Hical 方案:一个模板统一 1 2 3 4 5 6 7 8 9 template <typename SocketType> class GenericConnection : public TcpConnection { SocketType socket_; // 一份读循环、一份写循环、一份状态机 // 差异部分用 if constexpr 处理 }; using PlainConnection = GenericConnection<tcp::socket>; using SslConnection = GenericConnection<ssl::stream<tcp::socket>>; 核心技术:类型萃取 + if constexpr 类型萃取:编译期判断 socket 类型 1 2 3 4 5 6 7 8 template <typename T> struct IsSslStream : std::false_type {}; template <typename T> struct IsSslStream<boost::asio::ssl::stream<T>> : std::true_type {}; template <typename T> inline constexpr bool hIsSslStream = IsSslStream<T>::value; 这是一个经典的模板特化技巧: ...

April 12, 2026 · 3 min · 567 words

深入学习 C++26 静态反射(Static Reflection)

深入学习 C++26 静态反射(Static Reflection) 提案:P2996R9(Reflection for C++26) 头文件:<meta> 命名空间:std::meta 编译器支持:Clang(P2996 实验分支)、EDG(部分)/ GCC 和 MSVC 计划中 注意:C++26 标准预计 2026 年底定稿,本文语法基于当前最新提案,最终可能有微调 一、为什么需要静态反射? 1.1 C++ 元编程的历史痛点 C++ 一直以"零开销抽象"著称,但在类型自省这件事上,四十年来只能靠旁门左道: 方案 A:宏暴力展开 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 用宏定义可序列化结构体 #define DEFINE_FIELDS(TYPE, ...) \ static constexpr auto fields() { \ return std::make_tuple(__VA_ARGS__); \ } struct Player { uint64_t id; std::string name; int level; int64_t gold; DEFINE_FIELDS(Player, FIELD(id), FIELD(name), FIELD(level), FIELD(gold) // 手动列举每个字段 ) }; 方案 B:代码生成工具(protobuf / flatbuffers / 自研工具) ...

April 9, 2026 · 27 min · 5613 words

深入学习 C++20 协程(Coroutines)

深入学习 C++20 协程(Coroutines) 头文件:<coroutine> 命名空间:std 编译器要求:GCC 11+ / Clang 14+ / MSVC 19.28+(均需 -std=c++20 或以上) 注意:GCC 10 / Clang 8~13 可通过 -fcoroutines 和 <experimental/coroutine> 使用实验性支持 一、为什么需要协程? 1.1 异步编程的传统痛点 游戏服务器中充斥着异步操作——数据库查询、网络 I/O、定时器回调。传统方案各有各的痛: 方案 A:回调地狱(Callback Hell) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void HandleLogin(Connection* conn, const LoginPacket& pkt) { // 第1步:查询数据库验证账号 dbManager->QueryAsync("SELECT * FROM accounts WHERE name=?", pkt.name, [conn, pkt](const DBResult& result) { if (!result.ok) { conn->SendError("DB错误"); return; } // 第2步:查询角色列表 dbManager->QueryAsync("SELECT * FROM characters WHERE account_id=?", result.accountId, [conn](const DBResult& charResult) { if (!charResult.ok) { conn->SendError("DB错误"); return; } // 第3步:加载角色数据 dbManager->QueryAsync("SELECT * FROM inventory WHERE char_id=?", charResult.charId, [conn, charResult](const DBResult& invResult) { // 第4步:终于可以发送登录成功了... conn->SendLoginSuccess(charResult, invResult); }); }); }); } 方案 B:状态机(State Machine) ...

April 8, 2026 · 22 min · 4611 words

深入学习 C++17 PMR(Polymorphic Memory Resource)

深入学习 C++17 PMR(Polymorphic Memory Resource) 头文件:<memory_resource> 命名空间:std::pmr 编译器要求:GCC 9+ / Clang 9+ / MSVC 19.13+(均需 -std=c++17 或以上) 一、为什么需要 PMR? 1.1 传统 Allocator 模型的痛点 C++98 引入的 Allocator 是模板参数,这意味着: 1 2 3 4 std::vector<int, MyAlloc<int>> vec1; std::vector<int, std::allocator<int>> vec2; // vec1 和 vec2 是不同类型!无法互相赋值、放进同一个容器 核心问题: 痛点 说明 类型传染 Allocator 是模板参数,换一个 Allocator 就变了类型,所有接口签名都要跟着改 无法运行时切换 编译期绑定,测试时想换成 debug allocator?重新编译 难以组合 想让 vector 内部的 string 也用同一个 arena?极其繁琐 状态传播困难 有状态 allocator(如持有内存池指针)在容器拷贝/移动时语义复杂 1.2 PMR 的解法:运行时多态 PMR 用一个虚基类 std::pmr::memory_resource 取代模板参数,容器统一使用 std::pmr::polymorphic_allocator<T>: ...

April 6, 2026 · 12 min · 2482 words

DeerFlow 2.0 本地部署与排坑实战指南

🦌 DeerFlow 2.0 本地化部署与排坑实战指南 文档维护: Hical 适用环境: Windows + Docker Desktop (企业内网管控环境) 📌 项目简介 DeerFlow 是一个强大的多智能体(Multi-Agent)协作框架,专为长时间运行的复杂自主任务设计(如自动化编码、深度调研、排障分析)。底层基于 LangGraph,支持沙盒(Sandbox)隔离执行。 🛠️ 部署前置准备 由于公司终端管控策略(如 IP-Guard 等防泄密软件)可能会禁用系统 WSL (Windows Subsystem for Linux) 或拦截 C 盘挂载,建议 Docker Desktop 配置如下: 禁用 WSL:若启动 Docker 报错,修改 %APPDATA%\Docker\settings.json,将 "wslEngineEnabled" 改为 false,强制使用 Hyper-V 引擎。 准备代码: 1 2 3 git clone https://github.com/bytedance/deer-flow.git cd deer-flow ⚙️ 核心配置修改 (避坑指南) 为了防止在启动和编译过程中出现各种“水土不服”的报错,在执行启动命令前,请务必完成以下 5 步修改: 配置根目录 .env:复制 .env.example 重命名为 .env,并在末尾追加以下关键变量: 1 2 3 4 5 6 # 大模型 Token ANTHROPIC_API_KEY=XXXXXX # 必须配置 Auth 组件的 Base URL,否则前端 SSR 渲染会报 500 错误!(端口固定为 2026) BETTER_AUTH_BASE_URL=http://localhost:2026 BETTER_AUTH_SECRET=glacier_network_super_secret_key_2026 配置大模型 config.yaml:复制 config.example.yaml 重命名为 config.yaml,配置内网模型: models: name: claude-sonnet-4-6 display_name: Claude Sonnet 4.6 (Claude Code OAuth) use: langchain_anthropic:ChatAnthropic model: claude-sonnet-4-6 api_key: $ANTHROPIC_API_KEY max_tokens: 8192 记得把系统默认的模型指向刚才配置好的这个内网模型 default_model: claude-sonnet-4-6 ...

March 25, 2026 · 8 min · 1579 words