Paozhu vs Drogon —— C++ Web framework 系统设计对比

基于源码实测(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 是两者间的实际差异化能力。


0. 一句话定位


1. 并发 / IO 模型(核心差异)

维度 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 大小

设计哲学差异:

阻塞场景对比:4 个耗时任务同时到来

这是两种架构最直观的差异点:假设同时有 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 层永远呼吸不停,不会被拖死假死。


2. HTTP 协议层

维度 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。

2.1 静态文件服务 + 压缩缓存(两边都有,策略相反)

两者都不是"每次请求实时压缩"——首次之后都走缓存。但缓存策略方向完全相反:Paozhu 懒压缩自动落盘,Drogon 要求预压缩文件提前存在。

Paozhu —— 懒缓存 + 自动回收(server.cpp:395-518):

Drogon —— 预压缩文件查找(config.example.json:296-301 + StaticFileRouter.cc):

差异本质:Paozhu 把压缩缓存做成框架内自动管理(懒压缩 + 原子落盘 + 坏缓存防护 + 回收),开发者零成本;Drogon 把压缩缓存做成部署阶段责任,框架只读不写,换取框架自身更轻。两者运行时性能等价(命中后都零 CPU),但首次访问路径和运维模型不同。


3. 路由系统(深度)

两者都是「声明式路由注册」,但机制分叉:Drogon 走编译期反射 + 宏 + 静态自注册;Paozhu 走源码注解 + 构建期 codegen 生成 reg_raw 表。此外 Paozhu 额外内置了基于 httppeer::flow_method 的简单任务编排(见 §3.4),Drogon 无对等机制。

3.1 注册方式与代码生成

Paozhu(controller/src/*.cpp 写 //@urlpath(pre,urlpath):mask,构建期 paozhu_pre 扫描):

Drogon(HttpController.h 宏,编译期):

3.2 匹配结构

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/同步链跳过重复精确查找)。

Drogon:HttpControllersRouter + StaticFileRouter。三套结构(HttpControllersRouter.cc):

3.3 预处理链(pre handler)与同步/协程混编

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)),同步步交给业务线程池执行。

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」这种按入口拆分的混编槽位。

3.4 简单任务编排(Paozhu flow_method)—— 是,Paozhu 支持

结论:是的,Paozhu 在 httppeer 上内置了简单任务编排,通过 httppeer::flow_method 实现「单请求内多 handler 顺序编排」。

这意味着一个请求可以「handler A → 压 B、C、D → A 返回空串后依次跑 B→C→D」,构成轻量顺序工作流 / pipeline。Drogon 没有对等的内建机制:它的组合原语是 Filters/Middlewares(横切关注,非顺序步骤编排)与 handler 自身逻辑;若要在 Drogon 里串多个 handler,需 handler 内手动调用或重定向,框架层不提供编排栈。

3.5 多租户 / 站点 slot

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 隔离。

3.6 小结对照表

维度 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 支持在框架层即内建。

3.4 PHP-FPM FastCGI 支持(Paozhu 独有)

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。

3.5 多域名虚拟主机(Paozhu 框架级内建)

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 切换,框架自动处理,业务代码不用写任何域名分支。


4. ORM 深度对比

二者均支持 MySQL / PostgreSQL / SQLite 三方言,且 ORM 查询都支持 C++20 协程异步。但在生成时机、代码组织、查询风格、缓存、事务、关系表达上差异显著。

4.1 生成代码结构

Paozhu(paozhu_cli orm <dbtag>,生成器 vendor/httpcli/modelfun.hpp)每张表产出三层:

Drogon(drogon_ctl create_model,复用 CSP 模板 model_h.csp/model_cc.csp)每张表生成一个模型类(示例 orm_lib/tests/mysql/Users.h):

关键差异:Paozhu 把「数据定义(base)」与「SQL 操作(opsql)」分离成两个文件并支持外联关系 .cpp;Drogon 把所有东西塞进一个模型类,持久化细节用 private + friend 封装,更面向对象。

4.2 查询 API 风格

Paozhu —— 链式 builder(返回 M_MODEL&),方法名偏「直接 SQL 动词」:

Drogon —— 流式 Mapper<T> + Criteria(模板 Mapper 在 orm/Mapper.h:115,构造收 DbClientPtr):

风格差异: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) 与查询,分页导航(总页数/窗口)要自己算。

4.3 查询结果缓存(最大功能差异)

Paozhu —— 模型对象可选能力,开箱即用:缓存是模型对象上的可选功能,无需额外引入第三方库。核心 API:

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 周期扫过期条目 —

4.4 异步与协程模型

Paozhu —— 同步 + 协程双形态,协程是主范式:

Drogon —— 三形态(同步 / 回调 / future)+ 协程叠加:

4.4.1 双池并发架构(io_context 协程池 + 独立 ThreadPool)

Paozhu 执行层是两个线程池并存,并发靠「strand 串行化 + 业务池 dispatch 回 strand + peer 链式共享所有权」拆成三层,无 GC 也安全:

对照 Drogon:Drogon 是「多独立 EventLoop」模型(每 worker 一 EventLoop,MPSC 跨线程,epoll/kqueue),handler 跑在所属 ioLoop、重阻塞需自 offload;它没有 Paozhu 这种「共享 io_context + 独立通用 ThreadPool + strand 闭环」的跨池模型,故也回避了跨池交还对象的所有权问题。

4.5 事务

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 + 归还连接

② 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();                      // 统一提交或回滚

Drogon —— 一等 Transaction 对象:

对比:Drogon 用一等 Transaction 对象塞进 Mapper,API 干净、模型不用知道自己在不在事务里;Paozhu 是显式手动——要么用 db_conn 跑原生 SQL,要么 lock_conn 把 ORM 模型绑到事务连接上(模型需要感知事务状态)。两种都够用,但 Drogon 更优雅。

4.6 关系 / 外键

Paozhu:oneXXX/manyXXX 在外联 .cpp(orm/fk_child_opsql.cpp / orm/fk_parent_opsql.cpp),实现即用本表外键值对关联模型 where()/whereIn();另提供通用 leftJoin() 做显式 JOIN。

Drogon:关系是「生成期配置 + 运行时每模型方法」,无 hasMany/hasOne 风格的通用 Mapper 关系 API。

4.7 类型映射与参数绑定

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 分支:

4.8 连接池 & 多库隔离

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 独有:

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)。

底层协议实现差异(架构级):

4.9 业务代码风格(实际写法)

§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 表达式,类型安全、关注点分离更清晰)。

4.10 小结对照表

维度 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),线层协议交给第三方

核心差异要点:


5. 流量限制对比

两者限流定位层次不同,但并非完全不重叠:Drogon 既有连接数硬闸门(HttpConnectionLimit,全局+单 IP 硬上限),又有请求级(per-IP / per-user / per-route,令牌桶/滑窗/固定窗,插件开箱即用);Paozhu 是连接接纳级过载保护(60 秒连接数窗口 + 负载自适应放行率 + 排队慢放)+ 站点上传限额 + IP 封锁。两者在连接级保护上思路不同(硬上限 vs 排队放行),请求级配额则是 Drogon 独有的业务能力。

5.1 Drogon —— 请求级 RateLimiter + Hodor 插件

核心类(lib/inc/drogon/RateLimiter.h):

Hodor 插件(lib/inc/drogon/plugins/Hodor.h,Drogon 官方限流插件):

Drogon 的请求级限流是生产级、细粒度、声明式:配一段 JSON 即可对 /api/.* 做「全局 1000/s + 单 IP 100/s + 单用户 50/s」的组合策略,算法可换。

连接数硬闸门(lib/src/HttpConnectionLimit.h:26,accept 阶段原子计数):

5.2 Paozhu —— 连接接纳级过载保护 + 上传限额 + IP 封锁

连接接纳限速(内建在 httpserver 核心,server.h:247 ratelimiter() 线程驱动):

上传限额: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 链会在第一个非空返回值处短路。实现思路在框架内闭环,无需扩展核心。

5.3 小结对照表

维度 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 排队放行),并非完全不重叠。


6. 协议与中间件支持(WebSocket / MQTT / Redis / RPC / Socket)

这一维差异极大:Paozhu 把五类协议全部内置到 httpserver 核心(且用统一的 subpub 注册模型贯穿 ws/mqtt/socket);Drogon 仅内置 WebSocket 与 Redis,MQTT / RPC / 裸 Socket 服务器均无框架级支持。

6.1 WebSocket

6.2 Redis

6.3 MQTT

6.4 RPC

6.5 裸 Socket / TCP 服务器

6.6 架构主题:统一的 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,是给业务代码用的进程内工具,而非框架内部的协议分发机制。

6.7 JWT 与 OAuth 2.0 / OIDC

6.8 Session 管理

6.9 HTTP Client

6.10 监控与可观测性

6.11 通用内存缓存层

两边都有框架级通用内存缓存(非 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 是完全同构的取舍。

6.12 定时器与定时任务

维度 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 注解自动注册口。

6.12 小结对照表

协议 / 中间件 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/常驻出站交由外部库或自行扩展,框架本身不内建。

6.13 Drogon 独有的 HTTP 增强能力(§6.12 表中标记为 Drogon 独占的 4 项)

以下能力 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 全部走这套体系;插件间可声明依赖,启动顺序自动拓扑排序。

6.14 Paozhu 独有的常驻出站客户端(§6.12 表中标记为 Paozhu 独占)

框架启动时可以拉起四种常驻出站客户端,走和 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 做设备上报

统一管理基础设施:

Drogon 对比:框架级没有常驻客户端注册体系。DbListener(PostgreSQL LISTEN/NOTIFY)是数据库层的事,HttpClient 是按需调用——都不是"进程启动即拉起、长期存活的出站长连接"。


7. 控制器与代码生成

维度 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()

差异要点:

7.1 最小可运行示例(注解函数 vs 注册宏)

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

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);   // 回调式返回
    }
};

一句话:Paozhu 是「在业务代码里写一行 //@urlpath 注解就完成注册」,回归式、零样板;Drogon 是「在类里用 METHOD_LIST_BEGIN/ADD_METHOD_TO 宏声明路由」,宏反射、需继承 HttpController 基类。

7.2 代码生成引擎(vendor/httpcli)与数据库迁移工具

澄清:Paozhu 的 vendor/httpcli/ 不是数据库迁移工具,而是其构建期代码生成 / ORM schema 解析引擎——即 paozhu_pre(CMake target paozhu_codegen)背后的实现,等价于 Drogon 的 drogon_ctl。它做三件事:

迁移工具对比:

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;Paozhu modelfun.hpp 跑 SHOW CREATE TABLE/PG 表列表)。差异在于 Paozhu 额外支持纯离线读 schema/*.sql 生成(CI 友好、与库解耦),而 Drogon 的 create_model 必须连活库。若项目需要真正的 DB 迁移流水线,两边都要外接工具或自管 SQL 脚本——这正是「全家桶自研」在 DB 工程化上未覆盖的一块。


8. C++20 协程

维度 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 体系。


9. 多租户 / 主题

维度 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 本仓无对等的一站式多租户抽象。


10. 依赖与工程化

维度 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+重启

10.1 收口:零第三方依赖的内置「全家桶」(Paozhu 工程化主线)

把前面各节的自研点收拢成一句话:Paozhu 的强制外部依赖极薄(仅 Asio + OpenSSL 3),其上是几乎全栈自研——唯一的例外是 SQLite(嵌入式数据库,无独立协议,用官方 sqlite3 C API,ENABLE_SQLITE=ON 时链入);Drogon 核心同样薄(trantor + OpenSSL/Botan),但能力靠组合生态。

Paozhu 自研覆盖(不引入对应第三方库)

Drogon 对照(薄核心 + 生态组合)

代价与收益同源:Paozhu 的「全家桶自研」带来部署简单(少装外部库)、能力一致、零协议授权/版本纠缠,但代码体量巨大、自研一切正确性自担、生态/社区小(见 §11);Drogon 则把「做透 HTTP + 易组合」作为工程主线。


11. 设计取舍总结

Paozhu 强项

Paozhu 代价

Drogon 强项

Drogon 代价(drogon-master 实测)


12. 选型建议

概念区分:多域名虚拟主机是一个物理服务器绑多个域名、每个域名跑完全独立的网站(域名隔离,证书/根目录/钩子各自独立);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 还快)

附:关键源码证据索引

← 返回主文档