基于源码实测(
paozhu(https://github.com/hggq/paozhu),drogon(https://github.com/drogonframework/drogon),均取 2026-10 最新源码目录)。 源码目录分析:drogon-master 为 master 分支检出(2026-10 时点,版本头文件version.h.in定义 MAJOR/MINOR/PATCH,version.cc输出 DROGON_VERSION + Git commit)。其lib/与trantor/中不含 HTTP/2 服务器实现(无Http2Server/Http2Connection;lib/src/HttpServer.cc仅输出HTTP/1.1,无h2/ALPN 注册;trantor 只有 TLS 层的 ALPN 能力桩)。paozhu 则内置 HTTP/2(http2_parse.cpp等)。故下文对 drogon 的 HTTP/2 描述以「drogon-master 实际代码」为准——drogon 主线截至当前检出时点尚未内置 HTTP/2 服务器实现,paozhu 的 HTTP/2 是两者间的实际差异化能力。
io_context + Asio C++20 协程贯穿全栈(连 ORM 查询都是协程),HTTP/1.1 与 HTTP/2 均自研,偏「全异步协程 + 多租户」。| 维度 | Paozhu | Drogon(本仓) |
|---|---|---|
| 底层库 | standalone Asio(#include <asio.hpp>,asio::io_context 暴露到底层,兼容 Asio 庞大生态) |
trantor 自研网络库(muduo 分支,epoll_wait + timerfd + 最小堆,非 asio::io_context,Asio 生态库不可直接对接) |
| 事件循环拓扑 | 单一共享 io_context,N 个 worker 线程各跑 io_context.run() |
每线程一个 EventLoop(one loop per thread,muduo 风格),多 ioLoop 并行 |
| 连接分发 | 连接挂在 asio::strand(client_session.h:194)保证协议次序 |
TcpServer 用 nextLoopIdx_ 轮询把新连接分给某个 ioLoop(TcpServer.cc:65) |
| 跨线程通信 | 协程 co_await + strand 串行化 |
无锁 MPSC 队列 funcs_ + wakeupFd_(EventLoop.h:311-321) |
| 业务线程 | 独立业务线程池 clientrunpool(server.h:487,hardware*2+2,可动态扩缩),同步 handler 进池跑 |
handler 默认跑在所属 ioLoop 线程,可手动 offload / 协程切线程 |
| 线程配置 | cothreadnum、threadmax(conf/server.conf) |
numThreads / EventLoopThreadPool 大小 |
设计哲学差异:
clientrunpool),router::co_resolve 会逐步派单:协程步就地 co_await,同步步进业务池(server.cpp:2351 注释)。这是两种架构最直观的差异点:假设同时有 4 个耗时 handler(慢 SQL、大量计算、文件 IO 等阻塞操作),框架各自会怎样?
| 框架 | 线程模型 | 结果 |
|---|---|---|
| Drogon | handler 默认跑在所属 ioLoop 线程,4 个线程 = 4 个 ioLoop | 会堵。4 个 ioLoop 全被阻塞 handler 占满,epoll_wait 不会被调用,每个线程上其他连接的读写事件全部排队——4 个线程全堵 = 整个服务假死 |
| Paozhu | IO 线程池只管 accept/read/write,耗时任务丢业务线程池 clientrunpool |
不会堵。IO 线程不跑业务逻辑,即使业务线程池全满(队列堆积),IO 线程依然正常 accept 新连接、读请求、返回过载响应(如 503),服务不会假死 |
Drogon 的缓解手段(但根本没内置业务线程池):
Drogon 架构里没有业务线程池这个东西——handler 直接跑在所属 ioLoop 线程上。所以阻塞 handler 要想不堵死那个 ioLoop,只能靠协程覆盖或用户自建:
缓解方式 A:全程协程(能覆盖大部分场景,但有边界)
Drogon 的 handler 可以写成协程函数,只要所有耗时操作都有异步 API(数据库用 CoroMapper、HTTP 客户端用 co_await、文件 IO 用异步版本),co_await 挂起后 ioLoop 就空出来处理其他连接——这种情况下不会堵。
但协程救不了没有异步 API 的东西。比如用 Chromium 生成 PDF、用 ffmpeg 转码、用 OpenCV 做图片处理、AI 推理模型——这些全是纯 CPU 计算或只有同步接口的库,co_await 对它们没用,直接跑在 ioLoop 上就堵死。
// ✅ 协程能缓解:数据库查询(有异步 API)
METHOD_ADD(MyController::slowQuery, "/query", Get) {
auto result = co_await drogon::orm::CoroMapper(drogon::app().getDbClient()).filter(...).exec();
// co_await 挂起后 ioLoop 空出来处理其他连接
}
// ❌ 协程救不了:生成 PDF(纯 CPU 计算,Chromium 只有同步接口)
METHOD_ADD(MyController::genPdf, "/pdf", Get) {
auto pdf = chromium.generatePdf(html); // 同步阻塞,直接堵 ioLoop!
res->setBody(pdf);
res->send();
}
缓解方式 B:用户自建业务线程池
对于协程覆盖不了的场景(纯计算、同步第三方库),Drogon 架构里没有任何内置的业务线程池或 offload 机制,需要开发者自己设计和实现(自建 std::thread 池、任务队列、线程间通信等)。Drogon 生态里没有通用的 offload 方案可以直接拿来用。
缓解方式 C:多实例 + Nginx 负载均衡
从架构层面分散压力,一个实例卡了其他实例还能接。
但所有这些都是开发者主动加才生效的——默认 handler 就在 ioLoop 上直接跑。不手动 offload = 阻塞了就卡死这个 ioLoop 上所有连接的读写事件 = 线上事故。
Paozhu 的优势(架构兜一层,不需要开发者想这些):IO 和业务天生隔离,handler 跑在独立的 clientrunpool 业务线程池里。即使同步 handler 里写了阻塞代码,只影响业务线程池队列,IO 线程依然正常 accept 新连接、读请求、返回过载响应。协程 handler 更彻底——co_await 挂起而非阻塞线程,业务线程被占用的概率极低。
核心差异总结:
| Paozhu | Drogon | |
|---|---|---|
| 默认行为 | 阻塞 handler → 卡业务线程池 → IO 层不受影响 | 阻塞 handler → 卡 ioLoop → 该线程上所有连接假死 |
| offload 能力 | 架构天然分层,不需要手动 offload | 仅限 DB(CoroMapper)或用户自建线程池 |
| 开发者心智 | 写阻塞代码也不会炸服务 | 必须自觉 offload 才能安全 |
| 代价 | 架构复杂度高(双线程池、strand/协程调度) | 单线程 Reactor,模型简单好理解 |
最底层的设计哲学分歧:防 OOM vs 防 DDoS
上面说的"默认安全 vs 默认危险"只是表层。往深看,两个框架在架构设计上防的是完全不同的灾难:
| Paozhu | Drogon | |
|---|---|---|
| 防什么 | 防 OOM / 防服务假死(业务层失控) | 防 DDoS / 防内存线性增长(连接层失控) |
| 核心机制 | IO 和业务天生隔离:业务线程池满了通过 add_sync_task 返回 false 拒绝投递 → IO 层走 fail_503 返回过载响应但继续 accept;死锁检测 15 秒 watchdog 退出重启,不让请求无限堆队列吃内存 |
IO 线程数固定(setThreadNum):不管进来 1 千还是 10 万连接,都是这几个线程在跑;HttpConnectionLimit 插件限制总连接数上限 |
| 背后的计算 | 业务层如果没有 reject 通路,10 万请求堆队列 × 平均 1KB = 100MB 内存,继续涨就 OOM | 10 万连接如果每连接一个线程 × 8MB 栈 = 800GB 直接爆。单线程 Reactor 从根上杜绝了这个风险 |
| 代价 | 架构复杂度高(双线程池、strand、协程调度) | handler 跑在 ioLoop 上,阻塞 = 该线程所有连接假死 |
Drogon 防 DDoS 的能力在业界是公认的——muduo 单线程 Reactor 模型 + 固定线程数 + epoll,高连接数下内存不线性增长。Paozhu 防 OOM 的能力是架构兜底——业务层再怎么炸,IO 层永远呼吸不停,不会被拖死假死。
| 维度 | Paozhu | Drogon(本仓) |
|---|---|---|
| HTTP/1.1 | 自研解析 http_parse.cpp(122KB) + request.cpp/httppeer.cpp;无 HTTP/1.1 pipeline(请求-响应串行;但 pipeline 在现实中几乎没人用——Chrome/Firefox 从未默认开启,Firefox 54 直接移除,Nginx 1.29.7 之前默认 HTTP/1.0 根本没 pipeline;HTTP/2 多路复用 + h2c 明文已完全取代,Paozhu 支持 h2c,内网代理、微服务间调用无需 TLS 协商就能直接享多路复用,队头阻塞天然解决) |
自研状态机 HttpRequestParser.cc(kExpectMethod→…),完整支持 HTTP/1.1 pipeline(服务端 pushResponseToPipelining/popReadyResponses 按接收顺序排队响应,可配置 pipeliningRequestsNumber 最大深度;客户端 setPipeliningDepth() 批量发送;仅在 HTTP/1.1 连接下生效,TCP 队头阻塞仍在) |
| HTTP/2 | 内置:http2_parse.cpp(192KB) + http2_frame/huffman/ring_queue/send_queue,含 HPACK、ALPN、h2c,http2_enable=1 |
drogon-master 的 lib/ 无 HTTP/2 服务器实现(无 Http2Server/Http2Connection;HttpServer.cc 仅输出 HTTP/1.1,无 h2/ALPN 注册);trantor 仅含 TLS 层 ALPN 能力桩。 |
| 请求/响应模型 | httppeer(httppeer.h:41,enable_shared_from_this)持有 urlpath/get/post/output/session |
抽象类 HttpRequest/HttpResponse + Impl(HttpRequestImpl/HttpResponseImpl,renderToBuffer 序列化) |
| 注意点 | request.h 实为内置 JSON 微对象 obj_val,不是 HTTP 请求体 |
通过 HttpRequestPtr 智能指针传递;fromRequest<T>/toRequest<T> 隐式转换 |
说明:
request.h命名易误导——paozhu 真正的 HTTP 请求模型是httppeer。
两者都不是"每次请求实时压缩"——首次之后都走缓存。但缓存策略方向完全相反:Paozhu 懒压缩自动落盘,Drogon 要求预压缩文件提前存在。
Paozhu —— 懒缓存 + 自动回收(server.cpp:395-518):
rename 原子落盘(并发首访安全)。{原路径}.{hash(host+"\n"+urlpath)}.br 或 .gzip。后续请求命中直接 fopen 发送,零 CPU 开销。content_length > 16MB 跳过压缩(省内存)。mtime,后台按 CONST_HTTP_STATIC_CACHE_REFRESH_AGE 回收冷条目。Drogon —— 预压缩文件查找(config.example.json:296-301 + StaticFileRouter.cc):
gzip_static: true(默认)、brotli_static: false。请求到来时框架直接找同目录下预压缩好的 .gz / .br 文件,不存在就发原文。gzip -k / brotli 预压缩静态资源,压缩缓存管理交给 CI/CD 流程,框架自身不负责压缩计算。IOThreadStorage 隔离的静态文件内容缓存(StaticFileRouter.h:150-153,按 EventLoop 隔离免锁)。差异本质:Paozhu 把压缩缓存做成框架内自动管理(懒压缩 + 原子落盘 + 坏缓存防护 + 回收),开发者零成本;Drogon 把压缩缓存做成部署阶段责任,框架只读不写,换取框架自身更轻。两者运行时性能等价(命中后都零 CPU),但首次访问路径和运维模型不同。
两者都是「声明式路由注册」,但机制分叉:Drogon 走编译期反射 + 宏 + 静态自注册;Paozhu 走源码注解 + 构建期 codegen 生成
reg_raw表。此外 Paozhu 额外内置了基于httppeer::flow_method的简单任务编排(见 §3.4),Drogon 无对等机制。
Paozhu(controller/src/*.cpp 写 //@urlpath(pre,urlpath):mask,构建期 paozhu_pre 扫描):
common/autocontrolmethod.hpp 中是一张 reg_raw 调用表(autocontrolmethod.hpp:127-141):reg_raw(site, path, pre_sync, co_pre, reg_sync, co_reg, mask_code)。每个路由登记 5 个槽位:同步 pre、co_await 版 pre、同步 handler、协程 handler、以及 //@urlpath 注解里的数字 mask_code(区分 HTTP method)。:param 段的路径紧随其后用 reg_urlpath(site, path, {param_names...}) 单独登记参数名表(autocontrolmethod.hpp:139-140)。common/autorestfulpaths.hpp、common/reghttpmethod.hpp 等辅助表。Drogon(HttpController.h 宏,编译期):
METHOD_ADD(method, pattern, ...) / ADD_METHOD_TO(method, path, ...)(HttpController.h:31-34)→ registerMethod(&method, pattern, {...}, ...);还支持 ADD_METHOD_VIA_REGEX(:35)写正则。class methodRegistrator + static methodRegistrator registrator_(HttpController.h:127-139),构造函数里把方法名 + 参数包登记进框架——在 main 之前由 CRT 静态初始化完成,零运行时扫描。类名到路径的映射由 DrClassMap::demangle(typeid(T).name()) 自动推导(HttpBinder.h:235),:: 转 /。Paozhu:核心 router 类(router.h:32),运行时把 reg_raw 填进 _handlers / _urlpath_map / _site_to_slot(全局 slot 0,多租户见 §3.5);co_resolve(peer, known_idx)(router.h:79)协程驱动整条匹配+pre+handler 链,并带 route_exact_checked 优化(httppeer.h:298-299,rpc/同步链跳过重复精确查找)。
router.cpp 两级查表:lookup_exact → lookup_registered → lookup_key_in_slots,router.cpp:180-185),未注册的跳转名在编排排空时被跳过(router.cpp:350-363)。Drogon:HttpControllersRouter + StaticFileRouter。三套结构(HttpControllersRouter.cc):
simpleCtrlMap_:无参数精确匹配(小写 key 的 std::unordered_map)。ctrlMap_:带参数但结构固定的精确路由,ctrlMap_.find(loweredPath) 直接哈希命中(HttpControllersRouter.cc:626)。ctrlVector_:含 :param / 正则的路由,编译期把 {param} 占位符换成 ([^/]*)(regex_replace,:564),运行时 std::regex_match 线性匹配(:635-640)。binders_[req->method()] 按 HttpMethod 分桶;initMiddlewaresAndCorsMethods 自动注入中间件并开 OPTIONS/CORS(:58/63/75)。Paozhu —— 每条路由拆成 4 个独立槽位,同步与协程是「路由层一等公民」:reg_raw(site, path, pre_sync, co_pre, reg_sync, co_reg, mask_code)(common/autocontrolmethod.hpp)把一条路由注册为 同步 pre / 协程 pre / 同步 handler / 协程 handler 四个独立入口。主驱动 co_resolve(router.cpp:694)透明交织二者:协程步就地 co_await(pre co_await h.co_pre(peer);handler co_await h.co_regfun(peer)),同步步交给业务线程池执行。
pre_kind/reg_kind 自动分流,开发者无需自己切线程或包 std::async。这与 Drogon「handler 选一种形态、CoroMapper 是另一个类型」的设计思想不同——Paozhu 把「是否协程」下沉为每个注册入口的属性,而非整条 handler 的类型。pre_sync/co_pre 按声明顺序串成串行 pre 链;classify_step() 据返回值判定走向——@ok(放行)、同名(继续)、@exit(显式终止)、跳转名(router.cpp:76-87),首个非空即短路,空串是唯一放行信号(router.cpp:458-461)。此外还有站点级 pre/after hooks:run_site_hooks(同步 :462)与 co_run_site_hooks(协程 :478)批量跑 action_pre_lists/action_after_lists,超限时截断到前 site_hook_limit 条而非整条丢弃(避免鉴权静默失效)。pop_flow_index),若驱动是同步链却弹到协程条目则停止排空(:364-368),协程驱动则就地 co_await 继续(:865-873)。Drogon:pre 逻辑由 Filters(HttpFilter + ADD_FILTER_TO / GlobalFilters.cc)与 Middlewares(HttpMiddleware,MiddlewaresFunction.cc + HttpAppFramework.h:718)承载,框架在 handler 前按 path/全局顺序执行,与路由表解耦、可复用、可排序。同步/异步的选择落在单个方法形态上(binder 的 Blocking/Callback/future 三态,或协程用独立的 CoroMapper),没有「一条路由同时声明同步 pre + 协程 handler」这种按入口拆分的混编槽位。
flow_method)—— 是,Paozhu 支持结论:是的,Paozhu 在 httppeer 上内置了简单任务编排,通过 httppeer::flow_method 实现「单请求内多 handler 顺序编排」。
httppeer 持有一个懒分配的栈 std::unique_ptr<std::list<std::string>> flow_method(httppeer.h:300-301)——存的是后续要执行的 handler 名。httppeer.cpp:2135-2155):
push_next(name):push_back(栈顶,LIFO,后压先出)。push_last(name):push_front(栈底,FIFO,先压先出——顺序拆解的默认推荐)。push_steps({...}):批量压入。httppeer.cpp:2156-2169 pop_flow() 取 back 弹出;router.cpp:340-368 pop_flow_index):一个 handler 正常完成(返回空串)后,路由器从栈弹下一个已注册 handler 名并接着执行;一个 handler 只弹一个,剩余等它自己「返回空串」再弹,直到栈空或整链触顶(router.cpp:651-657 同步链、834-839 协程链)。kFlowOk / kFlowExit(显式终止、不弹栈)/ kFlowTimeloop(转事件循环)由 classify_step 解释(router.cpp:76-87)。:350-363);协程条目压到同步链上按设计停下(:364-368)。这意味着一个请求可以「handler A → 压 B、C、D → A 返回空串后依次跑 B→C→D」,构成轻量顺序工作流 / pipeline。Drogon 没有对等的内建机制:它的组合原语是 Filters/Middlewares(横切关注,非顺序步骤编排)与 handler 自身逻辑;若要在 Drogon 里串多个 handler,需 handler 内手动调用或重定向,框架层不提供编排栈。
Paozhu:路由天然挂在站点 slot 上——host_index → slot,_site_to_slot 每站点一条 slot,路由表按 slot 隔离(router.cpp:337 find_name_in_slots(peer_slot(peer), ...));reg_raw 第一个参数就是 site(域名,空串=全局 slot 0)。配合 §9 的主题视图,是框架级多租户。
Drogon:路由本身不带租户概念;多租户靠「多 app / 多 controller 分组 + DrObject 命名」在业务层实现,无一站式 slot 隔离。
| 维度 | Paozhu | Drogon(drogon-master 1.9.14) |
|---|---|---|
| 注册机制 | 源码 //@urlpath 注解 + 构建期 codegen 生成 reg_raw 表 |
反射宏 METHOD_ADD/ADD_METHOD_TO + 静态自注册(methodRegistrator,main 前) |
| 路径参数 | :param 占位 + reg_urlpath 参数名表 |
{param} 占位,编译期转 ([^/]*) 正则 |
| 精确匹配 | _urlpath_map + 前缀回退(最多 6 段) |
simpleCtrlMap_/ctrlMap_ 哈希;ctrlVector_ 正则 |
| Pre/横切 | 三层钩子体系:每路由 pre_sync/co_pre 串行链(@ok/@exit/跳转)+ 站点级 action_pre_lists/action_after_lists(serverconfig.h:63-64,run_site_hooks 串行,超限时截断到 site_hook_limit=15)+ 站点级 404 action_404_lists(httppeer.cpp:1088 无路由时触发) |
Filters(单向拦截)+ Middlewares(路由前后环绕,registerMiddleware 注册),可排序、可复用、与路由解耦,无站点级 slot 隔离 |
| 同步/协程混编 | 路由层一等公民:reg_raw 拆同步 pre/协程 pre/同步 handler/协程 handler 四槽位,co_resolve 透明交织 |
同步/异步落在单方法形态(Callback/future/CoroMapper),无按入口拆分的混编槽位 |
| 简单任务编排 | 有:httppeer::flow_method 栈(push_next/push_last/pop_flow),单请求多 handler 顺序执行 |
无内建;靠手动调用/重定向 |
| 多租户路由 | 多域名虚拟主机框架级内建:host_index 按 Host header 匹配站点(alias_domain 支持别名域名),每个 site 独立 wwwpath/证书/钩子/rewrite/CORS/上传限制/slot_id(serverconfig.h:30-79) |
无;业务层分组或多实例 |
核心差异:Drogon 的路由是「宏反射 + 静态注册 + Filters/Middlewares」的工业级经典范式,关注点分离干净;Paozhu 是「注解 + codegen + 站点 slot + flow_method 编排」的一体化范式——钩子体系比 Drogon 更细(按站点分,每个 site 独立 pre/post/404 钩子列表),额外给了请求内顺序任务编排这一轻量工作流能力,且多租户、PHP-FPM FastCGI 支持在框架层即内建。
Paozhu(框架级内建):isuse_php + php_root_document + rewrite_php_lists 三个配置,check_php_dispatch()(server.cpp:1369)完整实现:.php 后缀命中 → 查 rewrite 规则 → 定位 PHP 文件 → 设 compress=10 标记 → 走 FastCGI 转发给 php-fpm。支持 rewrite(rewrite_php_lists 正则匹配 URL 映射到 PHP 文件)、支持 index.php fallback(URL 空路径时查 php_root_document/index.php)。
Drogon(无):全仓 0 个 PHP/FastCGI 文件。要跑 PHP 需自行基于 trantor TcpClient 实现 FastCGI 协议,或用 Nginx 反代到 PHP-FPM。
Paozhu 每个站点对应 serverconfig.h 里的 sitehostinfos 结构体(serverconfig.h:30-79),host_index 按 HTTP Host header 匹配站点(支持 alias_domain 别名域名)。每个站点独立配置的字段:
| 字段 | 含义 | Drogon 有吗 |
|---|---|---|
mainhost + alias_domain | 主域名 + 别名域名(一个站点绑多个域名) | ❌ 全仓 0 个虚拟主机相关文件 |
wwwpath + php_root_document | 静态文件根 + PHP 根 | ❌ |
certificate_file + privateKey_file | 每个站点独立证书 | ❌(只有全局 addListener 配一套证书) |
action_pre_lists / action_after_lists / action_404_lists | 站点级 pre / post / 404 钩子列表 | ❌(Filter/Middleware 框架级注册,无站点隔离) |
rewrite_php_lists | 站点级 PHP rewrite 规则 | ❌ |
cors_domain + cors_origin_allowed | 站点级 CORS 白名单 | ❌ |
upload_max_size | 站点级上传大小限制 | ❌ |
themes + themes_url | 站点级主题/模板目录 | ❌ |
slot_id | 路由表 slot 隔离(每个站点独立路由表) | ❌ |
http2_enable | 站点级 HTTP/2 开关 | ❌ |
fastcgi_host + fastcgi_port | 站点级 PHP-FPM 后端地址 | ❌ |
siteid + groupid | 站点/分组 ID(业务层可用于租户鉴权) | ❌ |
匹配机制:HTTP 请求进来 → find_host_index()(httppeer.h:170)按 Host header 查 mainhost → 不命中再查 alias_domain → 定位 host_index → 路由、钩子、证书、根目录、CORS、上传限制全部按 host_index 切换,框架自动处理,业务代码不用写任何域名分支。
二者均支持 MySQL / PostgreSQL / SQLite 三方言,且 ORM 查询都支持 C++20 协程异步。但在生成时机、代码组织、查询风格、缓存、事务、关系表达上差异显著。
Paozhu(paozhu_cli orm <dbtag>,生成器 vendor/httpcli/modelfun.hpp)每张表产出三层:
<table>_base.h:纯数据/反射层——namespace <table>_info 含 col_count、enum cols、struct meta(字段定义)、type:: 类型别名、col_names、null_col 位图、assign_field_value(world_base.h:41-45、L76-80),以及脏字段位图 __dirty_bits + get_dirty_indices()/clear_dirty()/make_update_dirty_sql()。<table>_opsql.h:template<M_MODEL,B_BASE> class <table>_opsql : public B_BASE(含全部查询/缓存/脏更新方法,构造时按 dbtag 从 get_orm_conn_pool_obj() 取 orm_conn_pool)。
setXxx() 写字段时标记对应脏位;update_dirty()(文本协议)/ exec_update_dirty()(预编译)/ async_update_dirty() / async_exec_update_dirty() / commit_update_dirty() 仅对脏位字段拼 UPDATE,执行成功后 clear_dirty();脏位为空时短路、不发出全字段误更新,且缺 WHERE 时拒绝执行(article_opsql.h:9866/15248/10319/15323/13120)。这与 Drogon 的 dirtyFlag_[] 机制等价,都是「只写变化列」的增量更新。models/include/<Model>.h:薄壳 class <Model> : public <table>_opsql<<Model>,<table>_base>(models/include/World.h:11)。orm/fk_child_opsql.cpp、orm/fk_parent_opsql.cpp(见 §4.6)。Drogon(drogon_ctl create_model,复用 CSP 模板 model_h.csp/model_cc.csp)每张表生成一个模型类(示例 orm_lib/tests/mysql/Users.h):
struct Cols(列名常量)、表元数据(tableName/primaryKeyName/PrimaryKeyType:单主键为具体类型,复合为 std::tuple,无主键为 void)。getValueOfXxx() / getXxx() / setXxx()+setXxxToNull()(nullable)。toJson()/updateByJson()/validateJsonForCreation/Update。outputArgs/updateColumns/updateArgs/insertColumns,由 Mapper 内部调用。dirtyFlag_[]:决定 insert/update 只写变化列(Users.h:252-274)。sqlForFindingByPrimaryKey()、sqlForDeletingByPrimaryKey()、sqlForInserting(bool& needSelection)(按 dirtyFlag_[] 动态拼装列;PG 追加 returning *,MySQL/SQLite 依赖 LAST_INSERT_ID/insertId())。关键差异:Paozhu 把「数据定义(base)」与「SQL 操作(opsql)」分离成两个文件并支持外联关系 .cpp;Drogon 把所有东西塞进一个模型类,持久化细节用
private+friend封装,更面向对象。
Paozhu —— 链式 builder(返回 M_MODEL&),方法名偏「直接 SQL 动词」:
count() / page() / fetch() / fetch_one() / fetch_to<T>() / fetch_one_to()。update_col / exec_update_col / replace_col。where() / whereIn / whereNull / whereEQ / 每列快捷式 eqId();条件运算符 orm::wq::eq/nq/bt/be/lt/le。leftJoin()。async_fetch / async_count / async_page,及带 _prepared 的 exec_*。page() 内部先调 count(),count() 读取模型上已用 .where() 设好的筛选;随后 fetch() 也走同一 build_text_where——COUNT 与数据查询共用同一 where,条件只设一次。page() 还直接返回 (minpage,maxpage,page,total_page) 导航四元组,无需手写分页数学。Drogon —— 流式 Mapper<T> + Criteria(模板 Mapper 在 orm/Mapper.h:115,构造收 DbClientPtr):
findByPrimaryKey / findAll / count / findOne / findBy,各方法三种形态(同步/回调/future)。insert / update / updateBy / increment / deleteOne / deleteBy / deleteByPrimaryKey。Criteria(Criteria.h:73):CompareOperator 含 EQ/NE/GT/GE/LT/LE/Like/In/IsNull/IsNotNull;支持 operator&&/|| 组合、CustomSql+_sql 字面量、Criteria(Json::Value)。limit / offset / orderBy / paginate / forUpdate / innerJoin / leftJoin / rightJoin,占位符 $? 按 DB 替换为 $1,..(PG)或 ?(MySQL/SQLite)。风格差异:Paozhu 偏向「熟悉的链式动词 + 每列快捷方法」,上手成本低;Drogon 用
Criteria表达式对象 +Mapper流式设置,类型安全更强、但需理解Criteria/SqlBinder体系。 分页对比:Paozhu 的page()自动复用已设where,一次调用完成「计数 + 取数 + 返回导航四元组」;Drogon 的paginate(page,perPage)(Mapper.h:173/2013)仅设 offset/limit,count()默认Criteria()空条件(Mapper.h:459),需把同一Criteria分别传给count(criteria)与查询,分页导航(总页数/窗口)要自己算。
Paozhu —— 模型对象可选能力,开箱即用:缓存是模型对象上的可选功能,无需额外引入第三方库。核心 API:
use_cache() 查询前调用,后续自动走缓存;save_cache(exptime) 写回try_get_cache(name) 返回 std::optional(miss 为 nullopt),非抛;is_hit_cache 标记本次结果是否来自缓存std::hash<std::string>{}(sqlstring) 或自定义 cache_key_nameorm_cache_deadline() 钳制到 ORM_CACHE_DEFAULT_TTL(120s) ~ ORM_CACHE_MAX_TTL(3600s);后台单 daemon 每 ORM_CACHE_SCAN_INTERVAL(30s) 扫描过期条目。三者均可编译期 -D 覆盖。| Paozhu | Drogon(本仓) | |
|---|---|---|
| 查询结果缓存 | 有(orm_cache.hpp 的 model_meta_cache<BASE_T>,std::map<size_t,data_cache_t> + mutex) |
无(ORM 层零缓存,每次查询都打连接池) |
| 接入 | use_cache() / save_cache() / try_get_cache() / remove_cache() |
— |
| 回收 | 后台 daemon 周期扫过期条目 | — |
Paozhu —— 同步 + 协程双形态,协程是主范式:
fetch() / count() / page() / exec_fetch() / exec_count();协程 async_fetch() / async_count() / async_page() / async_exec_fetch()。co_await select_conn->async_fetch_directly(...);DML co_await edit_conn->async_exec_dml。orm_conn_pool.cpp:协程 async_get_mysql_select_conn / async_get_mysql_edit_conn 用 co_await async_add_*_connect 取/建连接;同步 get_mysql_*_conn。pg_conn.cpp async_read_loop co_await;sqlite_conn.cpp 经 run_on_worker 队列。reg_raw 同时提供同步槽(reg_sync / pre_sync)和协程槽(co_reg / co_pre),co_resolve 透明交织,开发者按签名选形态(见 §3.3)。Drogon —— 三形态(同步 / 回调 / future)+ 协程叠加:
Mapper 方法都提供三套:binder << Mode::Blocking(同步抛异常)/ binder >> rcb >> ecb(回调)/ std::promise+get_future(future)。DbClient::operator<<(sql) 返回 SqlBinder(DbClient.h:256),<< 绑参、>> 注册回调、exec() 执行。CoroMapper(CoroMapper.h:67,继承 Mapper<T>)返回 MapperAwaiter,await_suspend 把 callback 接入 CallbackAwaiter 并 resume();另有 execSqlCoro、newTransactionCoro。Result(Result.h:57,引用计数,含 size()/affectedRows()/insertId() 等多态实现)/ Row(Row.h:46,operator[] 返回 Field)。Paozhu 执行层是两个线程池并存,并发靠「strand 串行化 + 业务池 dispatch 回 strand + peer 链式共享所有权」拆成三层,无 GC 也安全:
io_context 协程池(server.h:381 单 io_context + runthreads,跑所有 co_spawn 协程——HTTP/1.1·HTTP/2·WS·MQTT·RPC)。ThreadPool clientrunpool(server.h:487,初值 hardware_concurrency()*2+2 线程,threadpool.h),承接从协程循环卸下的同步/阻塞活(路由 sync pre/regfun、定时器、ws 数据消息);支持动态扩缩(addthread/fixthread,上限 32)与 503 背压(force_reject_* → add_sync_task/add_conn_task 返回 false)。client_session(client_session.h:76,enable_shared_from_this)绑定 asio::strand,单连接内收发串行化、免锁;发送走 post_write(:81-91)入本连接发送环,由消费者协程串行写出。co_pool_run_step(pool_step.h:41)投到 clientrunpool;fn() 在池线程跑完,由 finish 经 asio::dispatch 把续体投回会话 strand——业务线程只跑 fn(),响应发送永远落 strand(pool_step.cpp:18-20)。peer 作一等参数贯穿「任务闭包 → finish → 唤醒 op → strand 恢复」,顺次各持一份强引用(pool_step.cpp:91-117),释放点固定在 strand 线程,发送线程不会拿到已释放对象;池未接单时不丢任务而是就地 rejected=true 完成 awaitable(pool_step.cpp:79-86),杜绝协程永久挂起。对照 Drogon:Drogon 是「多独立 EventLoop」模型(每 worker 一 EventLoop,MPSC 跨线程,epoll/kqueue),handler 跑在所属 ioLoop、重阻塞需自 offload;它没有 Paozhu 这种「共享 io_context + 独立通用 ThreadPool + strand 闭环」的跨池模型,故也回避了跨池交还对象的所有权问题。
Paozhu 事务有两层 API,业务代码里两种都有实例(controller/src/test_sql_commit.cpp):
① 原生 SQL 层(orm::db_conn 直接持事务连接):
auto ulink = std::make_unique<orm::db_conn>("cms");
ulink->begin_commit(); // BEGIN / BEGIN IMMEDIATE
ulink->edit_query("UPDATE sysuser set ..."); // 写
ulink->edit_query("INSERT INTO sysuser ..."); // 写
// 查询也走同一条写连接(读自己刚写的,无读写分离的主从延迟)
orm::cms::LocalusersqlStruct row;
ulink->edit_query("SELECT * FROM sysuser WHERE ...", row);
if (ulink->iserror) ulink->rollback();
else ulink->commit(); // COMMIT / ROLLBACK + 归还连接
db_conn 实例持有一条 mysql_edit_conn,事务里读写都走这一条写连接(edit_query(sql) 写、edit_query(sql, struct&) 读,第二个参数是输出),保证事务内读一致性、无主从复制延迟。async_begin_commit() / async_commit() / async_rollback() / async_edit_query()。② ORM 层(lock_conn 把事务连接挂到模型):
auto txnlink = std::make_unique<orm::db_conn>("cms");
txnlink->begin_commit();
// 多个不同模型对象都锁到同一条事务连接——跨对象事务
orm::cms::Sysuser user_m;
user_m.lock_conn(txnlink->mysql_edit_conn);
user_m.where("adminid", 10000);
user_m.update("nickname");
orm::cms::Sysroles role_m; // 不同表、不同模型类
role_m.lock_conn(txnlink->mysql_edit_conn); // 也锁到同一条
role_m.where("roleid", 50);
role_m.update("remark");
user_m.unlock_conn();
role_m.unlock_conn();
txnlink->commit(); // 统一提交或回滚
db_conn 事务里跑 ORM 操作会落在另一条自动提交的会话上——lock_conn() 把模型绑定到 db_conn 开的那条写连接上,事务内所有模型的读写都走同一条。lock_conn 到同一个 db_conn 的写连接,构成跨表事务。unlock_conn() 只解绑、不归还池(防未提交会话泄漏),最终由 db_conn 的 commit()/rollback() 统一归还。Drogon —— 一等 Transaction 对象:
Transaction 继承 DbClient,Mapper<T>(transactionPtr) 让后续读写在同事务连接上。newTransaction / newTransactionAsync / newTransactionCoro;TransactionImpl 从 readyConnections_ 借出单连接移入 busyConnections_,自动发 BEGIN / BEGIN IMMEDIATE / BEGIN EXCLUSIVE;提交/回滚后经 usedUpCallback 归还。TransactionType::{Deferred, Immediate, Exclusive}。对比:Drogon 用一等 Transaction 对象塞进 Mapper,API 干净、模型不用知道自己在不在事务里;Paozhu 是显式手动——要么用 db_conn 跑原生 SQL,要么 lock_conn 把 ORM 模型绑到事务连接上(模型需要感知事务状态)。两种都够用,但 Drogon 更优雅。
Paozhu:oneXXX/manyXXX 在外联 .cpp(orm/fk_child_opsql.cpp / orm/fk_parent_opsql.cpp),实现即用本表外键值对关联模型 where()/whereIn();另提供通用 leftJoin() 做显式 JOIN。
Drogon:关系是「生成期配置 + 运行时每模型方法」,无 hasMany/hasOne 风格的通用 Mapper 关系 API。
Relationship(create_model.h:211,Type{HasOne,HasMany,ManyToMany} + PivotTable 多对多中间表)。tryAddAutoRelationship(create_model.cc:181);PG 查 information_schema 外键视图、MySQL 查 KEY_COLUMN_USAGE、SQLite 查 PRAGMA foreign_key_list。model_cc.csp:1580-1789):按类型生成 getXxx(clientPtr) 同步/异步两版——HasOne 拼 select * from <target> where <key> = ?,HasMany 返回 vector<Target>,ManyToMany 联表 + pivot 返回 vector<pair<Target,Pivot>>。关系查询即 user.getWallet(client),内部仍走 DbClient/SqlBinder,可传事务 client。Paozhu:生成期 modelfun.hpp 用 pg_type_to_mysql_type() 与 SQLite 亲和性映射把 SQL 类型转 MySQL 二进制类型码,再 get_field_type() 按码映射 C++(整数/日期/字符串等),最终由 _base.h struct meta + type:: 落地。
Drogon:生成期 create_model.cc 直接按库写死映射(PG / MySQL / SQLite 各分支)。参数绑定 SqlBinder::operator<< 按 ClientType 分支:
htons/htonl/htonll 转网络序、format=1(二进制);getMysqlType<ParaType>() 映射(含 static_assert 拒绝不支持类型);sizeof(T) 选类型;另支持 trantor::Date/vector<char>/optional<T>/Json::Value。Field::as<T>()(Field.h:70-107,带大量特化)+ 模型构造 T(const Row&, indexOffset),对 date/bytea 做特殊解析,并据 dirtyFlag_[] 控制增量写列。Paozhu:orm_conn_pool.h 每个库有独立的读写分离连接池(get_mysql_select_conn 读 / get_mysql_edit_conn 写,三个数据库都支持),连接池 map 按库名索引(get_orm_conn_pool_obj() 返回 std::map<std::string, std::shared_ptr<orm_conn_pool>>,key 是 "cms"/"blog"/"lite" 等);ORM 模型生成在库名命名空间下(modelfun.hpp:9467 生成 namespace orm { namespace db_tag { class model {...} }}),不同库的同名表编译期隔离——orm::cms::user 和 orm::blog::user 是完全不同的类。多库调用:auto db = make_unique<orm::db_conn>("cms"); 直接选库。框架级表前缀(orm_conn_t::pretable,orm_common.h:718,在 conf/orm.conf 里配置):[docs] section 下写 pretable = web_,同一物理库上可以放多套业务的 ORM 模型(SELECT * FROM web_user vs SELECT * FROM shop_product),命名空间 + 表前缀双重隔离。
Drogon:DbClientManager 多库靠配置多个 db section + app().getDbClient("cms") 取;ORM 模型也支持命名空间隔离,但需要手动指定——drogon_ctl create model --namespace cms 命令行参数,或 JSON 配置里写 "namespace": "cms";默认值是 dbname。无读写分离:单个 DbClient 的 connections_ 池读写混用,应用层无法强制"读走从库、写走主库"。不支持表前缀:整个仓库无 table_prefix 相关实现,同一物理库放多套业务 ORM 只能靠建独立 db 或手动改生成的模型源码。
Paozhu 独有:
async_get_*_select_conn / async_get_*_edit_conn,读走只读实例、写走主库)--namespace 或 JSON "namespace" 字段)conf/orm.conf 里 pretable = web_,一个物理库放多套业务 ORM;Drogon 不支持)orm_connect_mar_t(orm_connect_mar.h/cpp):借出连接时 watch_mysql_connect(conn) 登记 weak_ptr + time_begin;server 主循环每 320 拍调 clear_connect() 扫描——weak_ptr 失效自动摘出、异步连接跳过、同步连接超过 CONST_ORM_QUERY_CONNECT_TIMEOUT 硬踢(hard_close())。限流:每轮最多扫 10 个,分多轮扫完,每轮 sleep 200ns 让出 CPU。push_log(sql_string, duration_ms) 记录慢查询,每 CONST_ORM_QUERY_LOG_TIME 拍数 save_log() 落盘。Drogon:DbClientLockFree(orm_lib/src/DbClientLockFree.cc)三态集合 connections_/readyConnections_/busyConnections_(互斥保护);execSql 有空闲则取用,否则压入 sqlCmdBuffer_(上限 20000,超限报 "Too many queries in buffer",DbClientLockFree.cc:203);按 connNum 决定事件循环数(SQLite 固定 1 个,PG/MySQL 取 min(hardware_concurrency(), connNum))。各库连接类 PgConnection/MysqlConnection/Sqlite3Connection 仅负责单连接非阻塞 I/O;支持 execSqlWithTimeout 超时抛 TimeoutError。无框架级连接健康检查(僵尸清理靠数据库服务端 wait_timeout)。
底层协议实现差异(架构级):
mysql_conn.cpp 手写 read_server_hello(解析 capability_flags、auth_plugin_salt_data)、caching_sha2_password 认证握手、mysqlnd_xor_string 加密、seq_next_id 包序号。pg_conn.cpp 手写 build_startup_message、SCRAM-SHA-256 握手(sasl_scram_sha256、int32_to_buf 消息帧)。sqlite_conn.cpp / sqlite_conn.h(L45)直接用官方 sqlite3 C API(sqlite3_open/sqlite3_prepare_v2/sqlite3_step/sqlite3_stmt*),#ifdef ENABLE_SQLITE 守护,CMake find_package(SQLite3 REQUIRED) 链接。SQLite 是嵌入式数据库,无独立线层协议可自研。CMakeLists.txt 不链 libmysqlclient/libpq(MySQL/PG 自研协议的代价),但 SQLite 需链 SQLite::SQLite3。MysqlConnection 直接 #include <mysql.h>(链 unofficial::libmariadb);PgConnection 用 libpq(find_package(pg));SQLite 用 SQLite3_lib。线层协议全部交给第三方库,Drogon 只做异步封装(基于 mysql_real_connect_start/cont 等非阻塞 API)。§4.2 讲的是 API 形态,这里给出真实业务代码长什么样(均取自两侧源码示例)。
Paozhu —— 模型对象即查询构建器,链式动词 + 协程/同步双形态(示例 controller/src/testormcache.cpp / testcrud.cpp):
// 模型对象本身就是 builder;条件、缓存、查询一气呵成
Article art;
art.where("isopen", 1).whereEQ(art.Alias::aid, aid).use_cache(); // 链式条件 + 查询缓存
co_await art.fetch_one(); // 协程查询,结果回填 art
client << "<br/>title:" << art.getTitle();
if (art.is_hit_cache()) { /* 命中缓存分支 */ }
// 分页:直接返回 (bar_min, bar_max, current_page, total_page)
unsigned int page = client.get["page"].to_int();
auto [nav_min, nav_max, cur_page, total_page] = art.page(page, 10, 5); // page, per_page, 导航栏宽度
风格要点:拿到模型实例 → 在其上 .where()/.whereEQ()/.use_cache()/.fetch_one()/.page() 链式拼装 → 协程 co_await 或同步 fetch_one();没有独立的 Mapper 层,模型即 DAO。
Drogon —— 泛型 Mapper<T> + Criteria 表达式,按模型类型 CRUD(示例 orm_lib/tests/db_test.cc):
using namespace drogon_model::postgres;
Mapper<Users> mapper(clientPtr); // clientPtr = app().getDbClient()
Users user;
user.setUserId("pg"); user.setUserName("alice");
mapper.insert(user); // 同步写入
auto u = mapper.findByPrimaryKey(2); // 按主键查
// 协程形态
CoroMapper<Users> cmapper(clientPtr);
auto cu = co_await cmapper.findByPrimaryKey(2);
auto us = co_await cmapper.orderBy(Users::Cols::_user_id).limit(2).findAll();
auto one = co_await cmapper.findOne(
Criteria(Users::Cols::_id, CompareOperator::EQ, 1));
风格要点:Mapper<Model> 是独立于模型对象的通用 CRUD 门面,条件用 Criteria(列, 比较符, 值) 表达式构造;模型对象只承载数据 + setter/getter,所有持久化操作经 Mapper 发起。
一句话:Paozhu 是「模型自己会查自己」(builder 内聚在模型上,动词式、上手快);Drogon 是「Mapper 代理模型 CRUD」(泛型门面 + Criteria 表达式,类型安全、关注点分离更清晰)。
| 维度 | Paozhu | Drogon(本仓) |
|---|---|---|
| 支持的 DB | MySQL / PostgreSQL / SQLite(dbtype 配置) |
PostgreSQL / MySQL(MariaDB) / SQLite3 |
| 生成来源 | 双模式:连活库 introspection(modelfun.hpp 跑 SHOW CREATE TABLE/PG 表列表,database-first)+ 也能纯离线读 schema/<dbtag>/tables/*.sql(CI 友好、与库解耦) |
连活库读 information_schema/PRAGMA(create_model,database-first) |
| 产物组织 | base(opsql 分离) + 外联 FK .cpp | 单模型类 + private/friend 封装 + dirtyFlag |
| 查询风格 | 链式动词 builder(where/page/fetch) |
Mapper<T> + Criteria 流式表达式 |
| 查询结果缓存 | 内建 orm_cache.hpp/model_meta_cache |
无(缓存交给上层/Redis) |
| 异步范式 | 同步 + 协程双形态:同步 count()/fetch()/page() + 协程 async_count()/async_fetch()/async_page()(asio::awaitable) |
同步/回调/future 三套 + CoroMapper 协程叠加(四形态) |
| 事务 | 两层 API:orm::db_conn 原生 SQL 事务 + 模型 lock_conn(txn_conn) 挂事务连接(手动解绑+由 db_conn 生命周期归还) |
一等 Transaction 对象塞进 Mapper,模型无感知,API 更优雅 |
| 关系/FK | oneXXX/manyXXX 外联 .cpp |
生成期 Relationship + 模型 getXxx(client) 方法 |
| 连接池 | dbtag 读写分离协程池 + 框架级健康检查(orm_connect_mar 定时扫描僵尸连接、超时硬踢)+ ORM 查询日志 |
DbClientLockFree 三态池 + 缓冲上限 20000;无框架级连接健康检查(僵尸清理靠数据库服务端 wait_timeout) |
| 类型映射 | SQL→MySQL 二进制码→C++ | 按库写死映射 + SqlBinder 二进制绑定 |
| 底层协议/客户端依赖 | 自研 MySQL/PG 线层协议(mysql_conn.cpp/pg_conn.cpp 手写握手与认证);SQLite 用官方 sqlite3 C API(sqlite3_open/sqlite3_prepare_v2),不链 libmysqlclient/libpq,链 SQLite3 |
封装所有官方客户端库(MysqlConnection→libmariadb、PgConnection→libpq、SQLite→sqlite3),线层协议交给第三方 |
核心差异要点:
create_model;Paozhu modelfun.hpp 连 mysql_conn_base/pg_conn_base/sqlite_db_conn 跑 SHOW CREATE TABLE/PG 表列表,并能把活库 dump 成 schema/*.sql);Paozhu 额外支持纯离线读 schema/*.sql 生成(与库解耦,CI 友好、可离线)。Mapper<T> + Criteria 统一 CRUD(面向对象、类型安全);Paozhu 每个表生成独立 _opsql.h 操作类(动词式 builder、上手快)。__dirty_bits 位图 + update_dirty()/exec_update_dirty()(脏位为空短路、缺 WHERE 拒绝执行);Drogon 用 dirtyFlag_[] 控制 insertColumns/updateColumns。机制等价。两者限流定位层次不同,但并非完全不重叠:Drogon 既有连接数硬闸门(
HttpConnectionLimit,全局+单 IP 硬上限),又有请求级(per-IP / per-user / per-route,令牌桶/滑窗/固定窗,插件开箱即用);Paozhu 是连接接纳级过载保护(60 秒连接数窗口 + 负载自适应放行率 + 排队慢放)+ 站点上传限额 + IP 封锁。两者在连接级保护上思路不同(硬上限 vs 排队放行),请求级配额则是 Drogon 独有的业务能力。
核心类(lib/inc/drogon/RateLimiter.h):
RateLimiterType 三算法:kFixedWindow / kSlidingWindow / kTokenBucket(RateLimiter.h:10-15)。RateLimiter::newRateLimiter(type, capacity, timeUnit=60s)(RateLimiter.h:42-45),接口 isAllowed()→bool(:52);SafeRateLimiter 包 mutex 做线程安全(:56-74)。TokenBucketRateLimiter / SlidingWindowRateLimiter / FixedWindowRateLimiter(lib/src/ 下 .cc/.h)。Hodor 插件(lib/inc/drogon/plugins/Hodor.h,Drogon 官方限流插件):
onHttpRequest(Hodor.h:144)AdviceCallback 前置建议,超限回 rejection_message(默认 "Too many requests")。Hodor.h:36-77):algorithm(默认 token_bucket)、urls 正则(空=全部)、time_unit、capacity(全局)、ip_capacity(单 IP)、user_capacity(单用户,配 setUserIdGetter)、sub_limits(按 URL 分组子策略)、trust_ips/use_real_ip_resolver(XFF/真实 IP)、limiter_expire_time(默认 600s,用 CacheMap 存各 IP/user 的 limiter)。Drogon 的请求级限流是生产级、细粒度、声明式:配一段 JSON 即可对
/api/.*做「全局 1000/s + 单 IP 100/s + 单用户 50/s」的组合策略,算法可换。
连接数硬闸门(lib/src/HttpConnectionLimit.h:26,accept 阶段原子计数):
HttpConnectionLimit::setMaxConnectionNum(num) — 全局连接数硬上限;setMaxConnectionNumPerIP(num) — 单 IP 连接数硬上限。HttpServer.cc:L121 accept 时调 tryAddConnection(),超限直接拒绝;连接关闭时 releaseConnection() 原子减计数。连接接纳限速(内建在 httpserver 核心,server.h:247 ratelimiter() 线程驱动):
update_minute_count()(server.cpp:8583-8632)——每个 accept 线程各持一份 60 桶 thread-local 环形,快照全局 total_count(连接计数),算出「最近约 60 秒进来的连接数」。compute_rate_from_load(has_save_link_count)(server.cpp:8636-8647)——把活跃连接数映射到 [lo,hi] 区间成百分比,再线性折成放行率 [num1,num2];阈值由 rate_limit_new_wait_num/rate_limit_accept_wait_num/rate_limit_second_num1/rate_limit_second_num2 控制。has_save_link_count > rate_limit_new_wait_num && live_link_count > rate_limit_new_wait_num,server.cpp:9014-9022 / 9197-9204)调 enqueue_rate_limited()(:8650)把连接压入 socket_session_wait_rate,限速线程被 rate_limit_condition 唤醒后按 rate_limit_second_time_num 个/秒逐拍慢放(:8749-8758 的 slot 循环)。上传限额:httppeer::check_upload_limit()(httppeer.cpp:789,站点级 host_index 守卫)——按站点限制上传体大小。
IP 封锁:check_blockip(peer_session)(server.cpp:5757,TLS 握手失败连接不计入流量),配合黑名单/白名单做接入层拦截。
Paozhu 没有内建的「per-IP / per-user / per-route 请求配额」层,但用框架已有能力即可实现:在
router的 site pre hooks(action_pre_lists,router.cpp:462)里加一个 IP 滑动窗口计数器(自己维护std::unordered_map<ip, deque<timestamp>>),超限返回httppeer::kFlowExit(@exit)拒掉请求即可——pre 链会在第一个非空返回值处短路。实现思路在框架内闭环,无需扩展核心。
| 维度 | Paozhu | Drogon(drogon-master 1.9.14) |
|---|---|---|
| 限流层级 | 连接接纳级(过载保护)+ 上传限额 + IP 封锁 | 连接级硬闸门 + 请求级配额(per-IP / per-user / per-route) |
| 连接数硬闸门 | 无硬上限;超过阈值后排队慢放(逐拍放行) | 全局 + 单 IP 硬上限(HttpConnectionLimit,accept 阶段原子计数),超限立即拒绝 |
| 算法 | 固定放行率 + 负载自适应(compute_rate_from_load) |
令牌桶 / 滑窗 / 固定窗(三选一),Hodor 插件 |
| 度量窗口 | 60 秒连接数窗口 + 活跃连接负载 | 可配 time_unit(默认 60s),按请求计数 |
| 单 IP/单用户请求配额 | 无内建;可用 site pre hooks + IP 滑动窗口计数器自行实现(框架内闭环) | 内建(ip_capacity / user_capacity) |
| 按路由细分 | 无(全站总闸门) | urls 正则 + sub_limits 分组 |
| 启用方式 | 编译进 httpserver,调阈值参数 |
连接闸门在 HttpAppFrameworkImpl.cc 硬编码 + Hodor 插件 JSON 配置 |
| 超限响应 | 连接排队慢放 / IP 直接 stop | 连接超限直接关掉;请求超限返回 rejection_message(默认 429 文本) |
核心差异:Drogon 限流是两层——底层 HttpConnectionLimit 硬闸门(防过载,超限即拒绝)+ 上层 Hodor 请求级配额(业务友好,per-IP/user/route,可换算法、插件化)。Paozhu 是单层连接接纳级保护——超过阈值不拒绝、而是排队慢放(更温和、防瞬时拒绝冲击),但请求级配额需自行用 site pre hooks 实现。两者在连接级保护上是同一问题的不同策略(硬拒绝 vs 排队放行),并非完全不重叠。
这一维差异极大:Paozhu 把五类协议全部内置到
httpserver核心(且用统一的subpub注册模型贯穿 ws/mqtt/socket);Drogon 仅内置 WebSocket 与 Redis,MQTT / RPC / 裸 Socket 服务器均无框架级支持。
websockets.h + ws_conn.cpp(连接)/ws_handshake.cpp(握手)/ws_parser.cpp + ws_wire.h(帧编解码)/http_websocket_client.cpp(65KB 客户端);websockets_callback.cpp + websockets.conf 配置;控制器层还提供 websockets/loopwebsockets.hpp / loopwebsockets_co.hpp(协程循环)/ wssleepwebsockets.hpp 等循环抽象。WebSocketController.h(服务端控制器)/ WebSocketConnection.h(连接)/ WebSocketClient.h(客户端),对应 *Impl.cc;tests/ 下有完整 coro/keepalive/upgrade 测试。两者能力对等。vendor/pzredis/):完整客户端 redis_client.h + redis_conn.h(连接)+ redis_pool.h(按 section 多实例连接池)+ redis_pubsub.h/redis_subpub.h(发布订阅)+ redis_reply.h(回复解析)+ redis_types.h;同步版裸名 / 协程版 async_ 前缀(redis_client.h:294-296 asio::awaitable),配置 redis.conf。不依赖 hiredis,自研 RESP 解析。nosql_lib/redis/):RedisClient + RedisConnection + RedisSubscriber + RedisTransaction + RedisClientManager;底层用 hiredis(cmake_modules/FindHiredis.cmake)。能力与 paozhu 对等,但借力第三方库。mqtt_broker.h 是单例 broker(mqtt_broker::instance()),含订阅表 subscribe()/unsubscribe()/unsubscribe_all、retained 消息、topic 过滤匹配、publish() 投递、prune_expired() 过期清理,并带全局配额 kMaxSubscriptionsTotal=100000(:84-198);mqtt_session.cpp + mqtt_frame.cpp(35KB,MQTT 帧编解码,含 MQTT5 的 No Local / Retain As Published / Retain Handling / 共享订阅 group+device);http_mqtt_client.cpp 客户端 + mqtt.conf,控制器 mqtt/echo_mqtt_client.hpp/my_test_mqtt.hpp。即开即用的 MQTT 服务。drogon-master 全仓 0 个 MQTT 文件,框架不提供 broker 也不提供 client;需引入第三方插件(如社区 drogon-mqtt)或自行基于 WebSocket/TCP 实现。rpc_parse.h/rpc_parse.cpp 解析专属 rpc 线格式(请求行 + KV 头 + 二进制 body,含大小限制 CONST_RPC_*),http_rpcclient.cpp(73KB)承载客户端与连接逻辑,并按自身路由分发;控制器 test_rpc_handle.cpp 演示 handler。即在同一个连接层上提供 RPC 语义。lib/ 中唯一 rpc 命中是 Windows 平台的 #include <rpc.h>(Utilities.cc:24,与 RPC 框架无关);无 protobuf / thrift / gRPC 封装。要做 RPC 需自行叠加(如基于 JSON + 自定义 controller,或外接 gRPC 进程)。http_socket.h + http_socket_client.cpp 提供裸 TCP 连接承载;sock_subpub_reg.h/.cpp + sock_subpub_client.h 把 socket 接入统一的 subpub 注册/订阅模型,sockets_config.cpp 配置。即框架可同时作为 HTTP/WS/MQTT/Socket 多协议服务器。HttpAppFramework 仅 addListener() 启 HTTP 监听器(:864),无 createTcpServer/UdpServer/裸 socket 服务器 API;要裸 TCP 需直接基于 trantor 的 TcpServer 或自管 asio,框架不替你做协议分派。subpub 注册模型Paozhu 在 ws/mqtt/socket 三处都使用 *_subpub_reg(ws_subpub_reg / mqtt_subpub_reg / sock_subpub_reg)——同一套「订阅注册 + 发布分发」内核贯穿多协议;Redis 也走 redis_subpub。这是 paozhu「协议全家桶」背后的统一抽象。Drogon 各能力彼此独立(WebSocket 控制器、Redis nosql 库、无 MQTT/socket),没有框架层跨协议的统一 pub/sub 内核;但业务层有 PubSubService<MessageType> 模板类(lib/inc/drogon/PubSubService.h)可用——提供命名 topic + subscribe/publish/unsubscribe,shared_mutex + copy-on-write,是给业务代码用的进程内工具,而非框架内部的协议分发机制。
jwt.cpp/jwt.h 是自研完整实现,依赖 OpenSSL 3;支持 HS256、RS256/RS384/RS512(PKCS1 + PSS)、ES256(:523 es256_verify),含 alg:none 防护(签名段可空但有明确错误态)、exp/iat/nbf claim 严格扫描、整令牌长度界 CONST_JWT_TOKEN_MAX_LEN、分离 decode_without_verify 与 verify(未验签令牌不会被判为合法)——安全取向自研。libs/oauth/oauth_store.cpp(35KB,授权码/令牌/刷新存储)+ controller/src/oauth/ 全端点:oauth_authorize / oauth_token / oauth_introspect / oauth_jwks / oauth_userinfo / oauth_revoke / oauth_metadata / oauth_purge;配套 conf/oauth.conf + RSA 密钥对;schema/cms/tables/oauth_*.sql 生成 OauthClient/OauthCode/OauthRefresh ORM 模型。即开即用的授权服务器。libs/oauth_client/oauth_client.cpp + controller/src/oauth_client/oauth_client_demo.cpp;libs/github/github_oauth.cpp 内置 GitHub OAuth 登录。drogon-master 全仓 0 个 JWT / OAuth 文件(lib/ 仅 HttpResponse.h/HttpHeaderTest.cc 顺带出现 Bearer 字样的 HTTP 头类型,非实现)。JWT 需引入第三方库(如 jwt-cpp)或社区插件;OAuth 2.0 服务端/客户端需自行实现或外接(社区 drogon-oauth 等)。httppeer 上直接持有 http::obj_val session(httppeer.h:228)——就是那个 JSON 微对象,天然嵌套、弱类型;parse_session()/save_session()/clear_session()/get_session_id() 生命周期方法;两种持久化——文件存储 save_session_file/parse_session_file + 内存存储 save_session_memory/parse_session_memory;SameSite 自动处理——session_samesite():站点配了 cors_credentials 且当前是 HTTPS 时自动给 "None",其余情况空串(不发该属性)。Session 类(lib/inc/drogon/Session.h,map<string, std::any> + mutex)由框架级 SessionManager 统一管理生命周期、可配置超时;any + any_cast<T> 强类型访问,比 Paozhu obj_val 更安全但略重;需手动配置 SameSite。httpclient + http_websocket_client.h 客户端,同步和协程(asio::awaitable)双形态——co_await httpclient(peer, "GET https://...") 直接拿响应;支持 add_client_task_loop 让 client_context 托管;示例 controller/src/testcowaitclient.cpp 演示协程 HTTP client。无 HTTP/1.1 pipeline 客户端——但这基本不是缺失,因为 Chrome/Firefox 从未默认开启 pipeline,Nginx 1.29.7 之前默认 HTTP/1.0 根本没有;HTTP/2 多路复用在单连接上并行交错发多个请求,效果等价且更彻底(天然解决队头阻塞);加上 Paozhu 支持 h2c 明文 HTTP/2,内网微服务调用、代理转发场景无需 TLS 协商开销,也能直接享多路复用。绝大多数现代场景走 HTTP/2 即可覆盖。HttpClient.h + HttpClientImpl.cc,同步/回调/future 三形态 + 协程 Task 叠加层;独有 HTTP/1.1 pipeline 客户端——setPipeliningDepth() 在单 HTTP/1.1 连接上批量发送,pipeliningCallbacks_ 队列按序回调(HttpClientImpl.h:62/185)。examples/client_example/ 与 tests/integration_test/client/ 有完整测试。该能力仅在 HTTP/1.1 连接下生效,TCP 队头阻塞仍在,且现实中几乎没有浏览器或反代默认开启 pipeline。controller/src/serverwatch.cpp 注册 paozhu_status 路由(返回版本号、当前在线连接数 total_count、协程池 clientrunpool 线程状态);frametasks_timeloop 是框架级间隔任务注册口(router 按 run_timeloop 链名定时触发);Access log 内嵌 server.cpp(access_loglist + httpwatch_flush_access_log)。但无 Prometheus 风格的 Collector/Registry/Counter/Gauge/Histogram 抽象——业务层如需 Prometheus 指标,需自行用 prometheus-cpp 埋点(docs/qiyedev.txt 有说明)。lib/inc/drogon/utils/monitoring/ — Collector/Counter/Gauge/Histogram/Metric/Registry 全套抽象 + PromExporter 插件(plugins/PromExporter.h),JSON 配置即启用,默认暴露 /metrics 端点,可配置 collectors + labels(如 http_requests_total{method, status});version.cc 也明确列出 Prometheus 相关依赖。生产可观测性开箱即用。两边都有框架级通用内存缓存(非 ORM 专用),实现风格却截然不同——直接反映了各自线程模型的差异:
| 维度 | Paozhu pzcache<BASE_TYPE> |
Drogon CacheMap<T1, T2> |
|---|---|---|
| 位置 | vendor/httpserver/include/pzcache.h |
lib/inc/drogon/CacheMap.h |
| 底层存储 | static std::map<string, data_cache_t> + 全局一把 mutex |
时间轮(四轮子 × 200 buckets),tickInterval * 200^4 秒有效期 |
| 过期清理 | 定时线程扫 exptime < nowtime 条目 |
时间轮到期自动触发 fnOnErase 回调,无需轮询 |
| 实例模型 | 全局单例 pzcache<T>::conn()(故意不析构,清理线程安全) |
每 EventLoop 一个独立实例(IOThreadStorage 风格) |
| 线程安全 | 全局 std::mutex(IO 线程 + 业务线程跨池,两边都要访问) |
每 EventLoop 独立 → 零锁(handler 固定在那个 ioLoop 上跑) |
| API | save(hashid, data, expnum, cover_data) / get(hashid) / check(hashid) / remove(hashid) / update(hashid, exptime) / clear() / remove_exptime() |
内部 insert / erase,通过 fnOnInsert / fnOnErase 回调通知业务 |
| 演示 / 使用 | controller/src/testpzcache.cpp 完整 CRUD 示例;ORM 查询结果缓存 orm_cache.hpp 是同一模式 |
框架内部用:HttpConnectionLimit 限流 limiter、静态文件缓存、rate limiter 状态存储 |
线程模型决定了一切:Drogon handler 固定在 ioLoop 上跑 → 每 EventLoop 一个 CacheMap → 零锁;Paozhu IO 和业务线程跨池了 → 两边都要访问缓存 → 只能全局 mutex。这和 IOThreadStorage vs 全局 pzcache 是完全同构的取舍。
| 维度 | Paozhu | Drogon(trantor EventLoop Timer) |
|---|---|---|
| 底层机制 | 帧拍驱动:server.cpp 主循环的 fps 计数器(server.cpp:7656 clientloop_ticks += 1),fps % timeloop_num == 0 时触发 |
timerfd + 最小堆(trantor/net/inner/TimerQueue.h),内核级高精度定时器 |
| 注册口 | 业务层零样板插拔:controller 写 //@urlpath(null, 任务名) 注解自动注册为路由 + 定时任务目标;任意 handler(URL 触发、pre hook、启动 init)里调 peer->add_timeloop_task("任务名", 间隔拍数) + return "@timeloop" 即可挂入调度 |
EventLoop::runAt(Date) / runAfter(delay) / runEvery(interval) — 框架不提供 interval task 到路由 handler 的自动映射,业务代码需自己拿 EventLoop + 管理回调生命周期 |
| 任务载体 | 路由 handler(controller 函数),触发时走 router.run_timeloop_task() → call_sync_regfun(taskname) — 和 HTTP 路由共用一套注册体系;但只接受同步注解函数(fn_kind::sync),协程 handler 被跳过(timeloop 跑在同步线程池,没有 strand 可 co_spawn) |
std::function<void()> 任意回调 |
| 业务层插拔成本 | 极低:新增定时任务 = 写一个同步 controller 函数 + 一个 arm 路由。删除定时任务 = 删掉 controller 文件,任务表自动清掉。所有用 //@urlpath 注解注册的同步函数都能被 timeloop 调(名字匹配即可) |
较高:拿 EventLoop → 写 lambda → 管理 TimerId → 生命周期随 EventLoop 绑定。无 controller 注解自动注册口 |
| 运行线程 | clientrunpool 线程池(同步-only,已在 §3.3 说明) |
EventLoop 所在 IO 线程(单线程串行,无跨线程交还问题) |
| 时间精度 | 依赖 server 主循环帧率(通常 ~ms 级) | 亚毫秒级(timerfd) |
| 周期调度 | ✅ 每 N 拍触发 | ✅ runEvery(interval) 每 interval 秒 |
| 一次性任务 | ✅ 通过计数实现:timecount_num 初始 1,handler 里调 peer->add_timeloop_count() 累加,够 N 次后 add_timeloop_count(0) 清零 → 下一拍 server 发现 timecount_num==0 自动移除任务 |
✅ runAfter(delay) 延迟执行一次(框架自动一次性) |
| 指定时间点 | ❌ 无 | ✅ runAt(Date) 在指定时间点触发 |
| 取消 | clear_timeloop_task() 设 timeloop_num=0 |
cancel(TimerId) |
| 单飞保护 | ✅ timeloop_inflight atomic CAS:上一拍还在跑就跳过当前拍,不积压 |
回调自己负责幂等;TimerQueue 本身不做单飞 |
| Cron 表达式 | ❌ 无 | ❌ 无 |
| 跨 EventLoop | 全局统一调度(主循环一个 fps 计数器管所有 task) | 每个 EventLoop 独立一套 TimerQueue,跨线程需自己协调 |
Paozhu timeloop 工作流(以 frametasks_timeloop 为例):
1. 业务代码调 httppeer.add_timeloop_task("frametasks_timeloop", 100)
→ peer.linktype = 7, timeloop_num = 100
2. server.cpp 主循环每帧 fps++
3. fps % 100 == 0 且 timeloop_inflight == false 时:
→ CAS 置位 timeloop_inflight
→ 投递给 clientrunpool 线程池(linktype==7 → timetasks_run)
4. 线程池里执行 threadpool.cpp:603
→ router.run_timeloop_task(peer, "frametasks_timeloop")
→ call_sync_regfun 找到同名 controller 函数(serverwatch.cpp:67 注册)
→ 执行后 inflight_release() 重置 timeloop_inflight
Drogon EventLoop Timer 用法:
auto loop = drogon::app().getMainEventLoop();
// 每 5 秒跑一次
auto tid = loop->runEvery(5.0, [] { doSomething(); });
// 取消
loop->cancel(tid);
总结:两者都没有 cron 表达式支持,都是 interval-based 定时器,但业务层插拔成本天差地别。Paozhu 把定时任务绑定到路由 handler + 线程池执行,和框架路由体系完全融合——写一个带 //@urlpath(null, taskname) 注解的同步 controller 函数,再写一个 arm 路由(URL 触发/启动时注册),定时任务就挂进调度了;删掉 controller 文件任务表自动清掉。零样板、零框架 API 学习成本、路由和任务共用一套注册,但只接受同步注解函数(fn_kind::sync),协程 handler 被跳过(timeloop 跑在同步线程池,没有 strand 可 co_spawn)。Drogon 是 EventLoop 级别的基础原语(精度更高、API 更全,有 runAt/runAfter),但 interval task 到业务 handler 的映射、EventLoop 归属管理、TimerId 生命周期全得业务自己处理,没有 controller 注解自动注册口。
| 协议 / 中间件 | Paozhu | Drogon(drogon-master 1.9.14) |
|---|---|---|
| WebSocket | 内置 server + client | 内置 server + client |
| Redis | 内置(pzredis,自研 RESP,按 section 池,sync/async) |
内置(nosql_lib/redis,基于 hiredis) |
| MQTT | 内置 broker + client(MQTT5,含 retained/共享订阅) | 无(需第三方插件) |
| RPC | 内置 自定义 rpc 协议(rpc_parse/http_rpcclient) |
无内置(无 protobuf/thrift/gRPC) |
| 裸 Socket / TCP | 内置(http_socket + sock_subpub_reg) |
无(仅 HTTP addListener,裸 TCP 需自管 trantor/asio) |
| JWT | 内置(jwt.cpp,HS256/RS256/ES256,OpenSSL3,安全取向自研) |
无内置(需第三方库如 jwt-cpp / 社区插件) |
| OAuth 2.0 / OIDC | 内置 授权服务器 + 客户端(完整端点 + ORM 模型 + GitHub 集成) | 无内置(需自行实现 / 社区插件) |
| Session | 内置(httppeer.session 即 obj_val,弱类型,文件/内存双存储,SameSite 自动处理) |
内置(Session 类 + SessionManager,强类型 std::any,手动配 SameSite) |
| HTTP Client | 内置(同步/协程双形态;无 HTTP/1.1 pipeline 客户端,但 pipeline 现实中几乎没人用——Chrome/Firefox 从未默认开启,HTTP/2 多路复用 + h2c 明文覆盖内外网) | 内置(同步/回调/future/协程四形态 + HTTP/1.1 pipeline 客户端 setPipeliningDepth,仅 HTTP/1.1 生效,现实中几乎没浏览器反代默认开启) |
| 监控 / Prometheus | 基础内建:serverwatch.cpp 状态路由 + Access log + 间隔任务注册;无 Collector/Registry 抽象,Prometheus 指标需业务层自行埋点 |
内置完整体系(Collector/Counter/Histogram + PromExporter 插件 JSON 配置即用) |
| 定时任务 | 帧拍驱动:add_timeloop_task(taskname, 间隔拍数) → 路由 handler + clientrunpool 线程池执行;全局统一调度、天然单飞保护;但精度受主循环帧率限制、只能"每 N 拍" |
EventLoop 级:runAt/runAfter/runEvery + timerfd;精度亚毫秒、支持指定时间点和一次性任务;但每 EventLoop 独立一套 TimerQueue,无全局调度 |
| 跨协议统一抽象 | 有:ws/mqtt/socket/redis 共用 subpub 注册模型 |
框架层无(各能力独立);业务层有 PubSubService<> 模板工具 |
| RequestStream 流式请求体 | 部分有:multipart 上传 3MB 阈值自动落盘(内存保护)+ HTTP client save() 流式写文件;但无 handler 逐块回调的流式 API |
完整有:RequestStreamReader + MultipartStreamParser,handler 逐块回调处理 |
| 反向代理 | 无(自行写 HTTP client) | 有(框架级 forward() + 插件示例 SimpleReverseProxy) |
| PostgreSQL LISTEN/NOTIFY | 无(仅同步/协程查询) | 有(DbListener,事件驱动、无需轮询) |
| 常驻出站客户端 | 有:Redis 订阅 / WS / 裸 TCP / MQTT 四种常驻长连接(框架启动即拉起,client_context 托管生命周期,resident_stop 自检路由) |
无(DbListener 仅数据库层;HttpClient 按需调用) |
| 插件体系 | 无统一机制(AccessLog/静态文件重定向内嵌 server.cpp) | 完整 AOP Advice(registerSyncAdvice/registerPreHandlingCallback/registerPostHandlingCallback + 依赖拓扑排序) |
核心差异:Paozhu 是「多协议 + 内生身份全家桶 + 常驻出站」——一个框架同时是 HTTP/2、WebSocket、MQTT(broker)、Redis、RPC、裸 Socket 服务器,内置 JWT 与 OAuth 2.0/OIDC 授权服务器,还有 Session、HTTP Client 以及四种常驻出站客户端(Redis 订阅 / WS / TCP / MQTT);但监控需业务自行接入、无 HTTP pipeline、无 handler 逐块回调的流式请求体(只有解析层自动落盘的内存保护)、无反向代理、无 LISTEN/NOTIFY。Drogon 是「专注 HTTP 的生产级 Web 框架」——WebSocket 与 Redis 一等公民、HTTP Client 能力更强(含 pipeline)、Session/监控/Prometheus/RequestStream/反向代理/DbListener 全链路内建,并有统一插件体系;但 JWT/OAuth/MQTT/RPC/TCP/常驻出站交由外部库或自行扩展,框架本身不内建。
以下能力 Drogon 框架层直接提供,Paozhu 无对等 API:
RequestStream 流式请求体(RequestStream.h + MultipartStreamParser.h):分块回调,不等待整个 body 收完才处理;multipart 流式解析边收边拆。入口 enableRequestStream(true),handler 接收 RequestStreamPtr。官方示例 examples/async_stream/RequestStreamExampleCtrl.cc。
注:Paozhu 也有大文件内存保护,但机制不同。multipart 上传有 3MB 阈值自动落盘(
http_parse.cpp:超过阈值开启fp后,post_multipart_itemcontent_append()每攒一块fwrite清 buffer);HTTP client 有save(path)→respreadtofile()流式写文件接口——两者都不会把大文件攒在内存,但没有 Drogon 的RequestStream那种"handler 逐块回调处理"的框架级流式 API:Paozhu 是框架解析层自动落盘,业务 handler 拿到的是已落好的临时文件路径(httppeer::upload_file.tempfile),业务层看不到字节流本身。
反向代理 / forward()(HttpAppFramework.h:732-764):框架级内置反向代理,forward(req, targetHost) 转发请求到指定后端。插件示例 examples/simple_reverse_proxy/ 用 IOThreadStorage<vector<HttpClientPtr>> 做连接池,按 EventLoop 隔离免锁。
DbListener —— PostgreSQL LISTEN/NOTIFY(DbListener.h):基于 PostgreSQL 原生 LISTEN/NOTIFY,事件驱动、无需轮询。DbListener::newPgListener(connectionInfo) → listen(chan, cb) / unlisten(chan)。有完整 db_listener_test.cc。
插件体系(AOP Advice):框架统一插件机制,三个钩子 registerSyncAdvice / registerPreHandlingCallback / registerPostHandlingCallback。Redirector、SlashRemover、AccessLogger、GlobalFilters、PromExporter、Hodor 全部走这套体系;插件间可声明依赖,启动顺序自动拓扑排序。
框架启动时可以拉起四种常驻出站客户端,走和 HTTP handler 同一条协程池跑,用于跨进程上报/订阅:
| 类型 | 启用宏 | 注册容器 | 典型用途 |
|---|---|---|---|
| Redis 订阅常驻 | ENABLE_REDIS_CLIENT |
server.redis_subpub_tasks |
订阅 Redis channel,接收其他服务的消息 |
| WebSocket 出站常驻 | ENABLE_WEBSOCKETS_CLIENT |
server.ws_subpub_tasks |
连到另一个 WS 服务做上报/双向订阅 |
| 裸 TCP 出站常驻 | ENABLE_SOCKETS_CLIENT |
server.sockets_clients |
连到任意 TCP 服务(如私有协议推送) |
| MQTT 出站常驻 | ENABLE_MQTT_CLIENT |
server.mqtt_clients |
连到外部 MQTT broker 做设备上报 |
统一管理基础设施:
client_context(client_context.h/cpp)—— 托管常驻客户端生命周期,超时自动 shutdown 底层连接,唤醒阻塞中的 poll/recv(防止僵尸协程)resident_stop 自检路由(resident_stop.cpp)—— GET /resident_stop?kind=redis|ws|sock|mqtt&name=<conf 段名>:调 stop() 后等待连接句柄清空,验证常驻协程是否正确退出add_client_task_loop(peer) 把常驻协程挂到 client_context 托管,无需自己写心跳/重连循环subpub_reg 注册模型,和框架内 WS/MQTT/Socket 服务器端天然互联互通Drogon 对比:框架级没有常驻客户端注册体系。DbListener(PostgreSQL LISTEN/NOTIFY)是数据库层的事,HttpClient 是按需调用——都不是"进程启动即拉起、长期存活的出站长连接"。
| 维度 | Paozhu | Drogon(本仓) |
|---|---|---|
| 代码生成器 | 构建期 paozhu_pre(CMake target paozhu_codegen,CMakeLists.txt:466-492),扫描 controller/src |
drogon_ctl 命令行工具 |
| 输入 | controller/src/*.cpp 里的 //@urlpath(pre,urlpath) 注解 |
命令行手工/半自动生成骨架 |
| 增量 | cache/method_notes.data(md5+mtime) 跳过未变文件(autopickmethod.cpp:170-288) |
— |
| 生成产物 | controller/include/<name>.h(前向声明)+ common/autocontrolmethod.hpp(reg_raw 路由表) |
create_controller 骨架、create_view 把 .csp→DrTemplate 子类 |
| 模板引擎 | view/ HTML 编译为 viewsrc/ C++ 源码(viewsrc/view/) |
CSP(C++ Server Pages):.csp 编译成 DrTemplateBase 子类 genText() |
| 视图机制 | view() / theme_view()(多主题前缀) |
DrTemplate<ClassName> + newTemplate() |
差异要点:
drogon_ctl 是开发者手动执行的 CLI,产物常驻仓库。Paozhu —— 注解函数(取自 controller/src/testrand.cpp,真实最简写法):
#include "httppeer.h"
namespace http
{
//@urlpath(null,testrand) // 站点(null=全局 slot) + URL 路径;无前置过滤器
std::string testrand(std::shared_ptr<httppeer> peer)
{
httppeer &client = peer->get_peer();
client << "<p>随机数: " << rand_range(100, 999) << "</p>";
return ""; // 空串 = 正常完成/放行;非空串为跳转或终止
}
}//namespace http
std::string handler(shared_ptr<httppeer>) 即注册为同步 handler(reg_raw 的 reg_sync 槽);返回 asio::awaitable<std::string> 即落入 co_reg 协程槽——同步/协程由签名决定,无需额外宏。paozhu_pre 扫描 //@urlpath 自动生成 reg_raw 路由表,开发者不写任何注册语句。Drogon —— 注册宏(HttpController + 反射,类名自动转路径):
class TestCtrl : public drogon::HttpController<TestCtrl>
{
public:
METHOD_LIST_BEGIN
// 路径 "/testrand",GET 方法,绑定 testrand 方法
ADD_METHOD_TO(TestCtrl::testrand, "/testrand", Get);
METHOD_LIST_END
void testrand(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&cb) const
{
auto resp = HttpResponse::newHttpResponse();
resp->setBody("<p>随机数</p>");
cb(resp); // 回调式返回
}
};
HttpController<Derived> 子类上,靠 DrObject 反射在 main 前静态自注册;类名 TestCtrl 默认映射为 /testctrl(可用 ADD_METHOD_TO 显式覆盖路径)。std::future / CoroMapper/Task 协程),与 Paozhu「签名即槽位」不同。一句话:Paozhu 是「在业务代码里写一行
//@urlpath注解就完成注册」,回归式、零样板;Drogon 是「在类里用METHOD_LIST_BEGIN/ADD_METHOD_TO宏声明路由」,宏反射、需继承HttpController基类。
vendor/httpcli)与数据库迁移工具澄清:Paozhu 的 vendor/httpcli/ 不是数据库迁移工具,而是其构建期代码生成 / ORM schema 解析引擎——即 paozhu_pre(CMake target paozhu_codegen)背后的实现,等价于 Drogon 的 drogon_ctl。它做三件事:
autopickmethod.cpp(@dest controller method pick file)扫描 //@urlpath 注解生成 reg_raw 路由表;autopickcontrolmethod.hpp / controllerparse.hpp 解析控制器。dbtypes.hpp(parse_pg_ddl/parse_sqlite_ddl/parse_mysql_show_create/gen_ddl)+ dbconvert.hpp/dbcreate.hpp/modelfun.hpp,把 schema/<tag>/tables/*.sql 编译成 orm/*/include/*_opsql.h 等模型头。struct_reflectfile.cpp、templateparse.hpp、dbdump_all.hpp/dbdump_schema.hpp/dbschema_check.hpp(后者是「schema/<tag>/tables/*.sql 管理工具」——校验 SQL 完整性、用 gen_ddl() 重写规范化 SQL,管理 schema 文件本身,而非运行时迁移)。迁移工具对比:
Paozhu(httpcli 引擎) |
Drogon(drogon_ctl) |
|
|---|---|---|
| 本质 | 构建期 codegen:双模式——连活库 introspection(modelfun.hpp 的 SHOW CREATE TABLE/PG 表列表)或离线读 schema/*.sql,二者都生成 ORM 模型 + 控制器抽取 + 结构体反射 |
CLI:连活库(create_model)→ ORM 模型 / 生成 controller/view/plugin 骨架 |
| Schema 来源 | 双模式:database-first(活库 introspection)+ 离线 SQL 文件(可手写或由活库 dump) | database-first(连活库 create_model) |
| 运行时迁移器(版本化 up/down) | 无;dbschema_check 仅校验/规范化 SQL 文件 |
无;create_model 仅反向生成模型 |
| 等价性 | httpcli ≈ drogon_ctl(都是代码生成器,都不是迁移工具) |
结论:两者都没有 Flyway/Liquibase 式的版本化迁移执行器,且都支持 database-first(连活库反向生成模型:Drogon
create_model;Paozhumodelfun.hpp跑SHOW CREATE TABLE/PG 表列表)。差异在于 Paozhu 额外支持纯离线读schema/*.sql生成(CI 友好、与库解耦),而 Drogon 的create_model必须连活库。若项目需要真正的 DB 迁移流水线,两边都要外接工具或自管 SQL 脚本——这正是「全家桶自研」在 DB 工程化上未覆盖的一块。
| 维度 | Paozhu | Drogon(本仓) |
|---|---|---|
| 支持 | 全栈协程:asio::awaitable<T> + co_await + co_spawn(..., detached),遍布 server/router/httppeer/ORM/客户端 |
完整 C++20 协程:Task<T>(coroutine.h:160)、AsyncTask、CallbackAwaiter、when_all、Mutex 协程锁 |
| 触发点 | 每连接 http1loop/http2loop 协程;路由 co_resolve;DB 查询 |
ORM CoroMapper;co_future/sync_wait 桥接 future |
| 定位 | 协程是主范式(连 ORM 都是),回调极少 | 协程是可选范式,默认仍是回调 / future,协程为增强 |
关键差异:Paozhu 把协程作为唯一一等公民(IO、业务、DB 全链路 awaitable);Drogon 是「回调/future 为基础 + 协程叠加」的混合模型。Paozhu 用 Asio 的标准 co_await;Drogon 自研 Task/Awaiter 体系。
| 维度 | Paozhu | Drogon(本仓) |
|---|---|---|
| 机制 | 站点 slot 模型:host_index → slot,_site_to_slot 每站点一条 slot,目录即命名空间(split_domain_path) |
多 app / 多 controller 分组;依赖 DrObject 命名 |
| 主题 | theme_view() 自动加 themes= 前缀;get_theme()(httppeer.h) |
无内建 theme_view 等价机制(需自行实现) |
Paozhu 的多租户是框架级内建(域名→slot→路由表隔离 + 主题视图),Drogon 本仓无对等的一站式多租户抽象。
| 维度 | Paozhu | Drogon(本仓) |
|---|---|---|
| 第三方核心 | Asio(可切 boost::asio)、OpenSSL 3;SQLite 可选链 SQLite::SQLite3(ENABLE_SQLITE=ON);MySQL/PG 无库依赖(协议自研) |
trantor(自研网络库)、OpenSSL / Botan(TLS 可插拔) |
| 构建 | CMake,构建期 codegen(paozhu_pre),controller/include 自动生成 |
CMake,drogon_ctl 外部 CLI 生成 |
| 附加模块 | vendor/ 绝大部分自研/零外部依赖:pzimage(图片) / pzexcel(Excel) / pzword(Word) / webpdf(PDF) / pzcharts(图表) / sms(短信) / webpay(支付) / pzzip(压缩) / pzredis(Redis 客户端) / httpcli(codegen)。仅 SQLite 需链 sqlite3、HTTP client 支持 brotli 可选链 brotlienc/brotlidec |
数据层库:nosql_lib/redis(Redis 客户端,基于 hiredis)+ orm_lib(ORM,MySQL/MariaDB/PG/SQLite 全链官方客户端库)。注:nosql_lib/ 在 drogon-master 仅含 redis/(无 MongoDB/Cassandra 等);图片/Excel/Word/PDF/图表/SMS/支付等均无内置,需 opencv / libxlsxwriter / libharu 等外部库 |
| 解析器 | 全自研(HTTP1/HTTP2/HPACK 无第三方) | 自研 HTTP1 解析;drogon-master 无 HTTP/2 |
| 证书自动化(ACME / OCSP) | 内置 ACME 客户端:acme_client.cpp + acme_run.cpp + acme_config.cpp 自动签发/续期,配合 acme.conf/ocsp_intv_time;OCSP stapling 由 ocsp_check.cpp 自动刷新装订 |
无内置 ACME 客户端(仅可在静态 location 配置 uri_prefix: /.well-known/acme-challenge/ 来提供 HTTP-01 挑战文件);lib/ 无 OCSP stapling 实现(BotanTLSProvider 中的 ocsp 仅是客户端校验证书链时接收的 OCSP 响应,非服务端装订);证书获取/续期依赖外部工具 |
| TLS 后端 | 仅 OpenSSL 3 支持 | OpenSSL / Botan 可插拔条件编译 |
| 压缩 | gzip + brotli 都内置(gzip.h / https_brotli.h;两者都走 §2.1 静态文件压缩缓存策略:首次懒压缩落盘,命中零 CPU) |
gzip + brotli 都支持(gzip 静态文件查找默认开,brotli 通过 USE_BROTLI 条件编译;两者都走预压缩文件查找,框架只读不写) |
| 进程守护 | 框架级自保护 + daemonize:① 死锁检测 httpwatch_check_deadlock() — 核心机制靠两个计数器的平衡矛盾。accept 是独立阻塞线程(不跑在 io_context 上):SSL accept 加 total_http2_count,HTTP accept 加 total_http1_count;sslhandshake 协程层减 total_http2_count,clientpeerfun 协程层减 total_http1_count(加/减应平衡);另有 total_count(只增不减,累计自启动以来进入过 clientpeerfun 的连接数)。handler 死锁时 accept 仍能高速接新连接(total_httpX_count 持续 ++,但协程排队在死锁 strand 上永远没机会 --)→ total_httpX_count > 4 且 total_count 冻结 → 看护线程每 5 秒检查一次,连续 3 次(15 秒)检测到矛盾 → isstop=true 主动退出。所有死锁场景都能覆盖(包括部分卡死:accept 两边都接,卡死的那个协议 count 持续增长但 -- 不掉);② 负载阈值重启 httpwatch_check_restart_threshold()(可配置时段,持续超阈值主动退出);③ 看护线程退出 std::terminate() 保护(看护线程自己挂了也触发重启);④ 外部重启文件(restart_file / restart_ssl_file);⑤ init_daemon.hpp 可 daemonize(fork→setsid→关 fd) |
无框架级自保护:无死锁检测、无周期性体检、无看护线程退出 terminate;依赖 drogon::app().quit() / shutdown() 主动关停;daemonize 需外部工具 |
| 外部重启链路 | 框架主动退出(isstop=true → 主循环 break → 进程正常结束),配合 systemd Restart=on-failure / Docker --restart=always 拉起新进程——形成完整闭环:内部检测卡死(15 秒内)→ 自动退出 → 外部重启 |
无主动卡死退出机制;进程挂掉/卡死由 systemd WatchdogSec=(通常 30 秒+)/ Docker health check 被动超时 kill+重启 |
把前面各节的自研点收拢成一句话:Paozhu 的强制外部依赖极薄(仅 Asio + OpenSSL 3),其上是几乎全栈自研——唯一的例外是 SQLite(嵌入式数据库,无独立协议,用官方 sqlite3 C API,ENABLE_SQLITE=ON 时链入);Drogon 核心同样薄(trantor + OpenSSL/Botan),但能力靠组合生态。
Paozhu 自研覆盖(不引入对应第三方库)
libmysqlclient/libpq(SQLite 用官方 sqlite3 C API 是嵌入式数据库的自然选择,无线层协议可自研)。jwt.cpp,HS256/RS256/ES256,OpenSSL3,安全取向自研)与 OAuth 2.0/OIDC 授权服务器(libs/oauth + controller/src/oauth 全端点 + GitHub 集成)自研内置,无需 Keycloak/Auth0 等外部 IdP 即可自建 SSO(§6.7)。vendor/httpcli/modelfun.hpp 跑 SHOW CREATE TABLE/PG 表列表,database-first)与离线读 schema/*.sql 均可,后者与运行库解耦、CI 友好(§4.1/§7.2)。acme_client.cpp/acme_run.cpp/ocsp_check.cpp),无需 certbot 等外部工具。subpub 注册内核(§6.6),是这套全家桶的串联线索。Drogon 对照(薄核心 + 生态组合)
代价与收益同源:Paozhu 的「全家桶自研」带来部署简单(少装外部库)、能力一致、零协议授权/版本纠缠,但代码体量巨大、自研一切正确性自担、生态/社区小(见 §11);Drogon 则把「做透 HTTP + 易组合」作为工程主线。
Paozhu 强项
asio::io_context 暴露到底层,asio-grpc/boost::beast/async_mqtt/boost::redis/quiche/nghttp2_asio 等任何 Asio 库直接 drop in,co_spawn/co_await 即插即用。Drogon 完全不可用(trantor EventLoop 是 muduo 分支自研,非 asio::io_context)。subpub 内核串联多协议,部署简单、无协议库版本纠缠。client_context 托管生命周期,天然打通跨进程上报/订阅。std::terminate + ORM 连接健康检查(orm_connect_mar_t)。Paozhu 代价
io_context 协程池 + clientrunpool 业务线程池)、巨量方言 ORM 头文件(~450KB/方言),代码体量与维护面大。Drogon 强项
forward()、PostgreSQL LISTEN/NOTIFY(DbListener)、插件体系(AOP Advice + 依赖拓扑排序)、静态文件预压缩查找、HTTP/1.1 pipeline 及其客户端(HTTP/1.1 仅,TCP 队头阻塞仍在;但现实中几乎没人用——Chrome/Firefox 从未默认开启,Firefox 54 直接移除,Nginx 1.29.7 之前默认 HTTP/1.0;HTTP/2 多路复用 + h2c 明文已完全取代)。Collector/Counter/Histogram + PromExporter 插件 JSON 配置即用)。Drogon 代价(drogon-master 实测)
lib/ 1.9.14:HttpServer.cc 仅输出 HTTP/1.1,无 Http2Server/ALPN 注册)。asio::io_context,所有 Asio 第三方库(asio-grpc、boost::beast、async_mqtt 等)无法直接对接。WatchdogSec= / Docker health check 被动超时 kill+重启。wait_timeout)。概念区分:多域名虚拟主机是一个物理服务器绑多个域名、每个域名跑完全独立的网站(域名隔离,证书/根目录/钩子各自独立);SaaS 多租户是一个或多个域名下服务多个租户、租户共享同一套代码(通常数据隔离在 tenant_id 字段),两种场景不同。
| 场景 | 建议 |
|---|---|
| 多域名虚拟主机 / 建站平台(一个服务器绑多个域名,每个站点独立证书/根目录/钩子/CORS/上传限制) | Paozhu(框架级内建,host_index 按 Host header 自动匹配 sitehostinfos,每个 site 独立配置 12+ 字段;Drogon 全仓零虚拟主机代码,只能靠 Nginx 反代层路由 + 多实例) |
| SaaS 多租户(一个或多个域名下服务多个租户,需要租户隔离) | Paozhu(框架级虚拟主机 + siteid/groupid 字段辅助租户鉴权;每个租户绑独立子域名/独立域名时,证书/根目录/钩子/CORS/上传限制全部自动隔离,业务代码只需按 host_index 切租户上下文。Drogon 无 site 隔离字段,租户隔离完全靠业务层实现) |
| 需要 HTTP/2 服务器支持 | Paozhu(drogon-master 无 HTTP/2 服务器实现;Paozhu 内置) |
| 追求成熟生态、文档、社区、快速上手 | Drogon |
| 全链路协程异步、统一心智模型 | Paozhu(协程为主范式) |
| 主题化站点 / 模板多套切换(同一域名不同主题目录/模板渲染) | Paozhu(themes + themes_url 站点级字段,框架内建;Drogon 无站点隔离的主题机制) |
| 离线 CI / 不依赖运行库生成 ORM | Paozhu(SQL schema 生成) |
| 经典 Reactor + 反射宏 + 编译期注册 | Drogon |
| 一站式零依赖全家桶(多协议 + 自管证书 + 自研 DB 协议,少装外部库) | Paozhu |
| 需要内置鉴权/SSO(JWT + OAuth 2.0/OIDC 授权服务器,少接外部 IdP) | Paozhu |
| 需要 HTTP/1.1 Pipeline(现实中几乎没人用:Chrome/Firefox 从未默认开启、Firefox 54 移除、Nginx 1.29.7 前默认 HTTP/1.0;Paozhu 无 HTTP/1.1 pipeline,但 HTTP/2 多路复用 + h2c 明文内外网都能覆盖并发场景,效果等价且更彻底) | Drogon(HttpServer.cc pipeline 队列 + HttpClient setPipeliningDepth()) |
| 需要内置 Prometheus 监控 | Drogon(Collector/Counter/Histogram + PromExporter 插件 JSON 配置即用) |
| 需要 HTTP/1.1 pipeline 客户端(现实中几乎没人用——HTTP/2 多路复用已覆盖并发请求;内网还能走 h2c) | Drogon(HttpClientImpl.h:62 独有能力) |
| 连接级保护偏好「硬拒绝」 vs 「排队慢放」 | Drogon(HttpConnectionLimit 硬拒绝型)vs Paozhu(排队放行型,更温和) |
| 需要 RequestStream 流式请求体 / 大文件低内存上传 | Drogon(RequestStreamReader + MultipartStreamParser 分块回调) |
| 需要 PostgreSQL LISTEN/NOTIFY 事件驱动 | Drogon(DbListener,事件驱动、无需轮询) |
| 需要框架级反向代理 | Drogon(forward() + 插件示例 SimpleReverseProxy) |
| 内网代理 / 微服务间调用(需明文 HTTP/2 + 多路复用,避免 TLS 协商开销) | Paozhu(h2c 明文 HTTP/2 直接享多路复用,无 TLS 协商开销,单连接并行交错发多个请求;Drogon 无 HTTP/2 服务器,反代后端只能走 HTTP/1.1,无多路复用) |
| 需要统一插件体系(AOP Advice + 依赖拓扑排序) | Drogon(registerSyncAdvice/registerPreHandlingCallback/registerPostHandlingCallback) |
| 静态文件压缩缓存偏好 | Paozhu(懒压缩自动落盘 + mtime 感知,更新源文件自动重新压缩,零运维成本)vs Drogon(预压缩 .gz/.br 文件部署时手动/构建时生成,框架只读不写、无源文件 mtime 感知——源文件更新时运维必须同步更新 .gz/.br,否则浏览器拿到过期压缩内容;内存缓存(默认 5 秒)过期前也不会刷新) |
| 电商开发(支付模块) | Paozhu:内置 vendor/webpay/(支付宝 + 微信支付 + 小程序支付,封装签名/验签/回调处理,ENABLE_WEBPAY 开关)+ libs/oauth/(自有 OAuth 2.0 授权服务器)+ 全协程异步处理支付回调高并发。Drogon 侧支付需自行集成第三方 SDK,无 OAuth 服务端内建 |
| 企业开发(WebPDF 等文档处理) | Paozhu:内置 vendor/webpdf/(HTML→PDF 生成)+ vendor/pzexcel//pzword/(Excel/Word 读写)+ pzimage/(图片处理)+ pzcharts/(图表)——零外部依赖,CI/CD 一条命令编过。Drogon 侧需分别装 libharu/libxlsxwriter/opencv 等,构建链复杂 |
| 物联网开发(MQTT 模块) | Paozhu:内置 MQTT broker + client(MQTT5,retained 消息、共享订阅、subpub_reg 统一分发)——即开即用,无需部署额外 EMQX/Mosquitto。Drogon 全仓零 MQTT 代码,需引入第三方插件或外接 broker |
| 需要 HTTP/2 + 大量模块一把梭 | Paozhu(HTTP/2 + MQTT broker + JWT + OAuth + ACME/OCSP + webpay/webpdf/pzexcel/pzword/pzredis,全内置零依赖) |
| 证书自动化免运维(ACME 自动签发/续期 + OCSP stapling 自动刷新) | Paozhu:框架内建 acme_client.cpp/acme_run.cpp/ocsp_check.cpp,配 acme.conf/ocsp_intv_time 就全自动(每天指定时段自动续签 OCSP)。Drogon 完全没有内置:ACME 需外接 certbot/lego/openssl 脚本 + cron + nginx stapling 手动配,OCSP stapling 全仓零代码 |
| 需要兼容 Asio 第三方库生态 | Paozhu(直接用 standalone Asio,asio::io_context 暴露到底层,任何 Asio 库——asio-grpc / beast / async_mqtt / boost::redis / quiche / nghttp2_asio 等——都能直接 drop in,co_spawn/co_await 即插即用)。Drogon 不可用(EventLoop 是 trantor 自研的 muduo 分支,epoll_wait + timerfd + 最小堆,不是 asio::io_context,Asio 生态库无法直接对接) |
| 框架级自保护(死锁检测 + 主动退出重启闭环) | Paozhu(accept 独立线程 + handler 层天然隔离的检测机制,所有死锁场景都能覆盖,15 秒内主动退出;比 systemd WatchdogSec=30s 还快) |
vendor/httpserver/src/server.cpp、include/client_session.h:56-108(每连接 asio::strand 串行化)、src/pool_step.cpp:79-117 + include/pool_step.h(双池并发:co_pool_run_step / run_one_step_with_peer / peer 链式共享所有权 / rejected=true 就地完成防死锁)、include/router.h:79、include/httppeer.h:41、src/http2_parse.cpp、vendor/httpcli/autopickmethod.cpp、vendor/httpcli/modelfun.hpp(ORM 双模式生成:活库 introspection + 离线 SQL)、autopickcontrolmethod.hpp:411、conf/server.conf、vendor/httpserver/include/orm_cache.hpp(model_meta_cache + orm_cache_deadline TTL 钳制)。trantor/trantor/net/EventLoop.h、net/TcpServer.cc:65、net/inner/poller/EpollPoller.cc、lib/src/HttpRequestParser.cc、lib/src/HttpControllersRouter.cc:626、lib/inc/drogon/HttpController.h:31、orm_lib/inc/drogon/orm/Mapper.h:115、lib/inc/drogon/utils/coroutine.h:160、drogon_ctl/create_model.cc、drogon_ctl/create_view.cc。