C++26 反射落地实战:双路线条件编译实现自动路由注册、JSON 序列化与 OpenAPI 文档生成

C++26 反射落地实战:双路线条件编译实现自动路由注册、JSON 序列化与 OpenAPI 文档生成 本文以 Hical 框架(v2.5)为例,展示如何在 C++26 反射尚未被主流编译器完全支持的现阶段,用"C++26 反射 + C++20 宏回退"的双路线策略,让用户享受相同的 API——从 JSON 序列化、路由注册到 OpenAPI 3.0 文档自动生成。 问题:Web 框架中的重复样板代码 每个 Web 框架都有三大类重复劳动: 1. 路由注册——每个处理函数都要手写一行注册: 1 2 3 4 5 6 router.get("/api/users", listUsers); router.get("/api/users/{id}", getUser); router.post("/api/users", createUser); router.put("/api/users/{id}", updateUser); router.del("/api/users/{id}", deleteUser); // ... 50 个路由 = 50 行手写注册 2. JSON 序列化——每个 DTO 都要手写字段映射: 1 2 3 4 json["name"] = user.name; json["age"] = user.age; json["email"] = user.email; // ... 10 个字段 = 10 行手写映射 3. API 文档——每个接口都要手写 OpenAPI 描述,且与代码脱节: ...

April 12, 2026 · 8 min · 1516 words

为 C++ Web 框架设计三层 PMR 内存池:从原理到实战

为 C++ Web 框架设计三层 PMR 内存池:从原理到实战 本文以 Hical 框架为例,深入讲解如何利用 C++17 PMR(Polymorphic Memory Resource)为高并发 Web 服务器构建三层内存池架构。 为什么 Web 服务器需要自定义内存管理? 一个 HTTP 请求的生命周期中,框架需要分配大量临时对象:解析缓冲区、路径字符串、JSON 值、响应体。在高并发场景下(如 50K QPS),new/delete 的全局锁竞争会成为显著瓶颈: 1 2 3 50,000 请求/秒 × 每请求 ~20 次分配 = 1,000,000 次/秒 new/delete ↓ 全局堆锁竞争 → CPU 空转 传统方案是自研内存池,但 C++17 提供了标准化的解决方案 —— PMR。 PMR 速览 PMR 的核心思想:把内存分配策略从容器类型中解耦。 1 2 3 4 5 // 传统方式:分配器绑定在类型中 std::vector<int> vec; // 永远用 std::allocator // PMR 方式:运行时切换分配策略 std::pmr::vector<int> vec(&myPool); // 用自定义内存池 标准库提供了三种现成的内存资源: ...

April 12, 2026 · 3 min · 438 words

从零构建现代C++ Web服务器(一):设计理念与架构总览

从零构建现代C++ Web服务器(一):设计理念与架构总览 系列导航:第一篇:设计理念(本文) | 第二篇:协程与内存池 | 第三篇:路由、中间件与SSL | 第四篇:实战与性能 | 第五篇:Cookie、Session与文件服务 | 第六篇:数据库中间件 前置知识 熟悉 C++17、C++20 基础语法(模板、智能指针、lambda、协程、Concepts) 了解 TCP/IP 和 HTTP 协议基本概念 对异步编程模型有初步认知 目录 1. 为什么在 2026 年用 C++ 写 Web 框架? 2. 现有方案分析 3. hical 的设计目标 4. 两层架构设计 5. C++20 Concepts 做后端抽象 6. 线程模型:1 Thread : 1 io_context 7. 全文总结 1. 为什么在 2026 年用 C++ 写 Web 框架? 当大多数团队选择 Go、Rust 或 Node.js 构建 Web 服务时,用 C++ 写 Web 框架似乎是"逆潮流而行"。但事实是,在特定场景下 C++ 仍然不可替代: 极致性能需求:游戏服务器、实时通信、高频交易等场景对延迟敏感到微秒级别 与现有 C++ 生态集成:当你的业务逻辑、数据处理库本身就是 C++ 时,跨语言调用引入的开销和复杂度不可忽视 内存可控:C++ 没有 GC 暂停,配合内存池可以实现完全可预测的内存行为 更重要的是,C++20/26 带来了一系列改变游戏规则的特性: ...

April 12, 2026 · 9 min · 1789 words

从零构建现代C++ Web服务器(三):路由、中间件与 SSL

从零构建现代C++ Web服务器(三):路由、中间件与 SSL 系列导航:第一篇:设计理念 | 第二篇:协程与内存池 | 第三篇:路由、中间件与SSL(本文) | 第四篇:实战与性能 | 第五篇:Cookie、Session与文件服务 | 第六篇:数据库中间件 前置知识 阅读过本系列前两篇(架构分层、协程基础、PMR 内存池) 了解 HTTP 请求/响应基本结构 了解中间件(Middleware)的概念(如 Express/Koa 的洋葱模型) 目录 1. 路由系统设计 2. 洋葱模型中间件管道 3. 模板化 SSL:编译期零开销 4. WebSocket 集成 5. 组装:HttpServer 门面 6. 总结 1. 路由系统设计 1.1 双策略路由:快速与灵活的平衡 Web 框架的路由系统需要处理两类路径: 静态路由:/api/status、/api/users — 路径固定,可以精确匹配 参数路由:/users/{id}、/posts/{pid}/comments/{cid} — 路径含变量,需要模式匹配 hical 采用双策略设计: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 请求到达: GET /api/users/42 │ ▼ ┌─────────────────────────────┐ │ 1. 静态路由查找 (O(1)) │ │ unordered_map 哈希表 │ │ 查找 {GET, "/api/users/42"} │ │ → 未命中 │ └───────────┬─────────────────┘ │ ▼ ┌─────────────────────────────┐ │ 2. 参数路由匹配 (线性扫描) │ │ 遍历 paramRoutes_: │ │ {GET, "/users/{id}"} │ │ → 匹配!提取 id = "42" │ └───────────┬─────────────────┘ │ ▼ 执行 handler 为什么不统一用 Trie 树?因为绝大多数实际应用中,静态路由占比远大于参数路由。用哈希表处理静态路由是 O(1),比 Trie 的 O(path_length) 更快。参数路由数量通常很少(几十个),线性扫描完全可以接受。 ...

April 12, 2026 · 13 min · 2702 words

从零构建现代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