1. 引言:为什么还需要一个 C++ Web 框架
在 Python(Django/FastAPI)、Go(Gin)、Java(Spring Boot)几乎垄断 Web 服务端开发的今天,用 C++ 写 Web 服务的理由反而越来越硬:
- 极致性能:单机吞吐量是脚本语言的数倍到数十倍,CPU 密集与高 QPS 场景下优势明显;
- 低资源占用:内存占用可控,适合容器化部署与边缘计算设备;
- 与现有 C++ 技术栈无缝衔接:算法库、音视频处理、游戏服务、量化交易等核心逻辑本身就是 C++,直接用 C++ 写 HTTP 服务可避免跨语言桥接;
- 云原生趋势:Serverless / 微服务对冷启动与单实例吞吐有苛刻要求,C++ 天然占优。
但 C++ 写 Web 服务的痛点也很明显:标准库没有 HTTP 服务器、没有路由、没有 JSON 与 ORM 的一体化方案,需要自己组装 Asio + Beast + 序列化库 + 线程池,工程成本极高。Drogon 正是为了填平这个鸿沟而生的一体化 Web 应用框架。
2. Drogon 简介与开源信息
| 项目 | 信息 |
|---|---|
| 名称 | Drogon(drogon/drogon,取自《权力的游戏》龙名) |
| 开源协议 | MIT(宽松,可商用) |
| 语言标准 | C++17(推荐 C++20/23 使用协程) |
| 核心作者 | an-tao(An Tao,腾讯 Tars 框架核心成员) |
| 定位 | 跨平台高性能 HTTP/WebSocket 应用框架,内置 ORM、过滤器、WebSocket、协程支持 |
| 支持平台 | Linux / macOS / Windows / FreeBSD / OpenWrt 等 |
| 核心依赖 | 可选:libuv、OpenSSL、jsoncpp、SQLite3、PostgreSQL、MySQL/MariaDB、Redis 客户端 |
| 官方仓库 | https://github.com/drogon/drogon |
| 配套工具 | drogon_ctl(命令行脚手架:建项目/控制器/过滤器/ORM 模型) |
Drogon 与很多"组装型"框架不同:它把HTTP 服务端、路由分发、参数解析、JSON 序列化、数据库 ORM、WebSocket、协程并发、静态文件、过滤器(中间件)全部收进一个框架,开箱即用。官方 Benchmark(TechEmpower 类测试)中长期位于 C++ Web 框架第一梯队,常与 nginx、Go 的 gin 同台竞技。
3. 核心特性与使用优点
3.1 异步与协程双引擎,天然高并发
Drogon 的事件循环基于非阻塞 I/O 多路复用(Linux epoll / macOS kqueue / Windows IOCP),默认线程池模型与 nginx 类似:
- 主线程:负责 accept 新连接并分发;
- I/O 线程(默认与 CPU 核数相关):各自持有独立事件循环,处理连接读写;
- 业务线程:可配置数量,承接耗时业务,避免阻塞 I/O 循环。
C++20 协程支持是 Drogon 的杀手锏:co_await 一个异步数据库查询或 HTTP 请求,代码看起来是同步顺序的,实际却不占线程。相比"回调地狱"(Beast 的 async 链)与"每连接一线程"(传统阻塞模型),开发效率与并发能力兼得。
// 同步写法背后是协程:不阻塞线程,却可读性极佳 Task<HttpResponsePtr> getUser(const HttpRequestPtr& req) { auto dbResult = co_await dbClient->execSqlCoro( "SELECT name, email FROM users WHERE id = $1", req->getParameter("id")); auto resp = HttpResponse::newHttpJsonResponse( drogon::json{{"name", dbResult[0]["name"].as<std::string>()}}); co_return resp; }3.2 一体化:路由、ORM、过滤器开箱即用
不需要像"Beast + 自研路由 + 自选 JSON + 自接数据库"那样拼积木。Drogon 内置:
- 注解式路由:ADD_METHOD_TO 宏 / METHOD_ADD 宏,或 C++17 的 HTTP_METHOD 注解宏,把 URL 路径直接绑到成员函数;
- ORM(orm::DbClient):链式查询构造器 + 类型安全的模型类,支持 PostgreSQL / MySQL / SQLite3;
- 过滤器(Filter):类似中间件,登录校验、权限控制、限流只需注册一个过滤器;
- JSON 支持:drogon::json 是 jsoncpp 的轻封装,HttpResponse::newHttpJsonResponse 一键返回 JSON;
- WebSocket:WebSocketController 接口化,握手、收发、广播开箱即用;
- 静态文件服务:配置 document root 即可托管前端资源,无需 nginx 前置。
3.3 性能与资源控制
- 零拷贝传输路径:响应数据在多数路径下直接走 sendfile / writev 批量发送;
- 连接复用:HTTP/1.1 keep-alive、HTTP/2(需 OpenSSL 支持)默认开启;
- 精细线程池:client_threads_num / threads_num 可独立配置,避免业务线程抢占 I/O 线程;
- 连接级超时、请求体大小限制、SSL/TLS 终结全部内置,无需再套一层网关。
3.4 开发效率与工程质量
- drogon_ctl 脚手架:drogon_ctl create project xxx 生成工程骨架,drogon_ctl create controller 生成控制器模板;
- 配置驱动:config.json / config.yaml 声明端口、线程数、日志级别、数据库连接池等,无需改代码调优;
- 内置日志(基于 spdlog 风格)、统一异常处理、drogon::app().run() 一键启动。
4. 使用场景
| 场景 | 说明 | 为什么选 Drogon |
|---|---|---|
| 高并发 API 网关 / 微服务 | 短请求、高 QPS、低延迟 | 协程 + 事件循环,单机吞吐远超脚本框架 |
| 游戏后端 / 实时对战服务 | WebSocket 长连接、广播推送 | 内置 WebSocket 控制器 + 协程房间逻辑 |
| 音视频 / 算法服务封装 | C++ 算法库对外暴露 HTTP 接口 | 同栈集成,避免 Python 桥接开销 |
| 量化交易 / 高频行情服务 | 毫秒级延迟敏感 | 零拷贝 + epoll,延迟可预测 |
| IoT / 边缘设备 Web 服务 | 资源受限、需静态文件 + 轻 API | 无重型依赖,可裁剪编译,支持 OpenWrt |
| 内部管理系统后端 | CRUD + 权限 + 报表 | 内置 ORM + 过滤器,开发效率接近脚本框架 |
| 嵌入式设备管理面板 | 设备本地 Web 配置页 | 单二进制分发,静态文件 + REST 一体 |
5. 快速上手:从零搭一个服务
5.1 安装与工程生成
推荐方式一(vcpkg):
vcpkg install drogon推荐方式二(源码编译,可裁剪依赖):
git clone https://github.com/drogon/drogon cd drogon mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_EXAMPLES=OFF cmake --build . -j$(nproc) cmake --install .生成工程骨架:
drogon_ctl create project hello_drogon cd hello_drogon && mkdir build && cd build cmake .. && cmake --build . ./hello_drogon浏览器访问 http://localhost:8080,默认返回框架欢迎页;/api/hello 返回 JSON。
5.2 最小 CMake 工程(FetchContent)
cmake_minimum_required(VERSION 3.14) project(hello_drogon CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(drogon GIT_REPOSITORY https://github.com/drogon/drogon GIT_TAG v1.9.6 ) FetchContent_MakeAvailable(drogon) add_executable(hello_drogon main.cpp) target_link_libraries(hello_drogon PRIVATE drogon)5.3 第一个控制器
// main.cpp #include <drogon/drogon.h> int main() { drogon::app() .addListener("0.0.0.0", 8080) .setThreadNum(4) // I/O 线程数 .setClientThreadsNum(4) // 业务线程数 .run(); }// controllers/HelloController.h —— 注解式路由(C++17 风格) #pragma once #include <drogon/HttpController.h> class HelloController : public drogon::HttpController<HelloController> { public: METHOD_LIST_BEGIN ADD_METHOD_TO(HelloController::hello, "/api/hello", drogon::Get); METHOD_LIST_END void hello(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr&)>&& callback) { auto resp = drogon::HttpResponse::newHttpJsonResponse( drogon::json{{"message", "Hello, Drogon!"}}); callback(resp); } };ADD_METHOD_TO(类::方法, "/路径", HttpMethod) 即完成注册;框架自动完成路径匹配、HTTP 方法校验、参数绑定,业务代码只关心"收到请求→返回响应"。
6. 具体使用方式详解
6.1 路由与参数绑定
Drogon 路由支持路径参数({} 占位)与查询参数:
ADD_METHOD_TO(UserController::info, "/user/{id}", drogon::Get, "UserFilter"); void UserController::info(const HttpRequestPtr& req, std::function<void(const HttpResponsePtr&)>&& callback, std::string id) // 第 3 个参数起按路径占位顺序绑定 { // 查询参数: /user/42?fields=name,email auto fields = req->getOptionalParameter<std::string>("fields"); // 请求体 JSON auto bodyJson = req->getJsonObject(); // 响应 auto resp = HttpResponse::newHttpResponse(); resp->setStatusCode(k200OK); resp->setBody("user id = " + id); callback(resp); }常用请求 API 一览:
| API | 作用 |
|---|---|
| req->getMethod() | HTTP 方法 |
| req->getPath() / getFullPath() | 路径 / 完整路径 |
| req->getParameter("k") / getOptionalParameter<T>("k") | 查询参数(后者带类型转换与空安全) |
| req->getJsonObject() | 解析请求体 JSON(jsoncpp 对象) |
| req->getHeaders() / getHeader("k") | 请求头 |
| req->getBody() | 原始请求体 |
| req->getCookie("k") | Cookie |
| req->getPeerAddr() / getLocalAddr() | 对端 / 本端地址 |
6.2 JSON 响应与文件响应
// JSON 响应 auto resp = HttpResponse::newHttpJsonResponse( drogon::json{{"code", 0}, {"data", drogon::json::array({1, 2, 3})}}); // 文件响应(自动识别 Content-Type,大文件走零拷贝) auto fileResp = HttpResponse::newFileResponse("/var/www/report.pdf"); // 字符串 + 自定义类型 auto resp2 = HttpResponse::newHttpResponse(); resp2->setContentTypeCode(CT_TEXT_HTML); resp2->setBody("<h1>hello</h1>");6.3 过滤器(中间件):登录与权限控制
// filters/LoginFilter.h class LoginFilter : public drogon::HttpFilter<LoginFilter> { public: void doFilter(const HttpRequestPtr& req, FilterCallback&& fcb, FilterChainCallback&& fccb) override { auto token = req->getHeader("X-Token"); if (token == "valid-token") { fccb(); // 放行,继续进入控制器 } else { auto resp = HttpResponse::newHttpResponse(); resp->setStatusCode(k401Unauthorized); fcb(resp); // 拦截并直接返回 } } };过滤器支持链式组合:ADD_METHOD_TO(..., "LoginFilter", "AdminFilter") 多个过滤器按声明顺序组成链路,适合鉴权、限流、审计日志等横切逻辑。
6.4 ORM:类型安全的数据库访问
auto db = drogon::app().getDbClient(); // 1) 链式查询构造器 auto result = co_await db->getCoroFuture( Criteria("age", CompareOperator::GT, 18) && Criteria("status", CompareOperator::EQ, "active"), OrderBy("created_at", SortOrder::DESC), Limit(20)); // 2) 原生 SQL + 参数绑定(防注入) auto res = co_await db->execSqlCoro( "SELECT id, name FROM users WHERE id = $1", 42); for (auto& row : res) { auto id = row["id"].as<int64_t>(); auto name = row["name"].as<std::string>(); } // 3) 事务 auto trans = co_await db->newTransactionCoro(); co_await trans->execSqlCoro("UPDATE accounts SET balance = balance - 100 WHERE id = $1", 1); co_await trans->execSqlCoro("UPDATE accounts SET balance = balance + 100 WHERE id = $2", 2); co_await trans->commit(); // 或 rollback()ORM 支持模型生成:drogon_ctl create model 从数据库表生成 C++ 模型类,字段访问带类型检查,编译期即可发现列名错误。
6.5 WebSocket:实时通信
class ChatController : public drogon::WebSocketController<ChatController> { public: WS_PATH_LIST_BEGIN WS_PATH_ADD("/ws/chat"); WS_PATH_LIST_END void handleNewMessage(const WebSocketConnectionPtr& wsConn, std::string&& message, const WebSocketMessageType& type) override { // 广播给所有在线连接 app().getLoop()->queueInLoop([message]() { for (auto& conn : app().getWebSocketConnections()) { conn->send(message); } }); } void handleNewConnection(const HttpRequestPtr& req, const WebSocketConnectionPtr& conn) override { /* 握手成功回调 */ } void handleConnectionClosed(const WebSocketConnectionPtr& conn) override { /* 连接关闭回调 */ } };配合协程与 Redis 订阅,可以轻松实现聊天室、实时行情推送、协作白板等服务端广播架构。
6.6 C++20 协程控制器(推荐写法)
启用 -std=c++20 后,控制器可以直接用协程返回值:
Task<HttpResponsePtr> OrderController::create(const HttpRequestPtr& req) { auto body = req->getJsonObject(); auto orderId = co_await orderService->createOrder(*body); co_return HttpResponse::newHttpJsonResponse( drogon::json{{"order_id", orderId}}); }框架自动把 Task<HttpResponsePtr> 挂到事件循环上调度,协程挂起时不占线程;这是 Drogon 高并发下仍保持代码可读性的关键。
6.7 静态文件与 HTTPS 配置(config.json)
{ "listeners": [ { "address": "0.0.0.0", "port": 8080, "https": false }, { "address": "0.0.0.0", "port": 8443, "https": true, "cert": "/etc/ssl/server.crt", "key": "/etc/ssl/server.key" } ], "document_root": "./static", "static_file_headers": { "enable": true, "headers": [ {"name": "Cache-Control", "value": "max-age=3600"} ] }, "threads_num": 8, "client_threads_num": 8, "db_clients": [ { "name": "main", "rdbms": "postgresql", "host": "127.0.0.1", "port": 5432, "dbname": "app", "user": "app", "password": "secret", "connection_number": 10 } ], "log": {"log_level": "info"} }6.8 优雅停机与生命周期钩子
int main() { drogon::app() .addListener("0.0.0.0", 8080) .registerBeginningAdvice([]() { // 启动前初始化:加载配置、预热连接池 }) .registerPreShutdownAdvice([]() { // 停机前:停止接收新请求 }) .registerPostShutdownAdvice([]() { // 资源释放 }) .run(); }7. 底层原理窥探
7.1 事件循环与线程模型
Drogon 的 EventLoop 是对 epoll/kqueue/IOCP 的封装,每个 I/O 线程一个 EventLoop。主线程 accept 后按负载均衡把连接分发到各 EventLoop;连接上的读写都注册为非阻塞事件,回调在所属 EventLoop 线程内执行,天然免锁。耗时业务通过 app().getLoop()->queueInLoop() 或业务线程池执行,避免阻塞 I/O 循环。
7.2 协程调度器
Drogon 自研协程(drogon::Task<T> / drogon::CoroTask)支持自定义 Awaitable。核心是协程挂起时把 continuation 注册到当前 EventLoop 的待调度队列,I/O 完成事件触发后恢复执行。这意味着:
- 协程恢复始终发生在所属线程,不需要跨线程切换;
- 数据库等待期间线程立即去处理其他请求,线程利用率接近 100%;
- 相比回调,协程栈帧在堆上保存,挂起/恢复开销极小(微秒级以下)。
7.3 HTTP 解析与响应路径
Drogon 内置高性能 HTTP/1.x 解析器,请求头解析避免逐字节拷贝;响应发送优先使用 writev 合并头部与 body,静态文件走 sendfile 零拷贝。keep-alive 连接复用避免频繁建连;HTTP/2 多路复用(开启 SSL 后可用)进一步降低队头阻塞。
8. 性能表现(参考)
TechEmpower Framework Benchmarks(Round 21,纯 JSON 序列化场景)中,Drogon 长期位于 C++ 框架前列,典型表现(4 核云主机量级):
| 指标 | 数值参考 |
|---|---|
| 纯 JSON 响应吞吐 | 百万级 req/s(优化配置) |
| 单请求延迟 P99 | 亚毫秒级 |
| 内存占用(空闲连接) | 每连接 KB 级 |
说明:性能数字随硬件、编译器(GCC/Clang)、配置差异较大,建议以本机 Benchmark 为准。Drogon 仓库自带 drogon_benchmark 与 TechEmpower 测试代码可复现。
9. 与同类框架对比
| 框架 | 范式 | 协程 | ORM | WebSocket | 静态文件 | 适用定位 |
|---|---|---|---|---|---|---|
| Drogon | 异步事件循环 | 原生支持 | 内置 | 内置 | 内置 | 一体化高性能 Web 应用 |
| CROW | 异步 | 无 | 无 | 部分 | 无 | 轻量快速原型 |
| Oat++ | 异步 | 无 | 无 | 支持 | 支持 | 企业级 REST(偏重配置) |
| cpp-httplib | 同步/异步 | 无 | 无 | 无 | 支持 | 单文件嵌入式 HTTP 服务 |
| Boost.Beast | 异步回调 | 需配 Asio coro | 无 | 协议层 | 无 | 协议/底层网络库 |
| nginx | 事件驱动 | 无 | 无 | 反向代理 | 内置 | 反向代理/网关 |
结论:需要完整 Web 应用能力(路由+ORM+WebSocket+过滤链)且追求性能,选 Drogon;只需要一个嵌入式 HTTP 端点,选 cpp-httplib;要自己掌控协议细节,选 Beast。
10. 常见坑点与避坑指南
- 业务线程阻塞 I/O 循环:不要在 I/O 线程回调里做重计算,应使用 app().getLoop()->queueInLoop 配合业务线程池或协程。
- 协程与线程亲和性:co_await 恢复线程与挂起线程一致,不要依赖跨线程数据共享;跨线程发请求用 getCoroFuture 系列而非直接共享对象。
- Task<T> 生命周期:协程返回的 Task 必须被框架接管(控制器返回值、app().getLoop()->createTask),不要随手丢弃导致悬挂。
- 配置不生效:config.json 需放在工作目录或通过 -c 指定;改了端口/线程数记得重启进程。
- HTTPS 证书路径:相对路径基于工作目录解析,容器部署建议用绝对路径。
- 数据库连接池耗尽:connection_number 过小 + 慢查询会导致池耗尽,配合 execSqlCoro 超时与连接数监控。
- Windows 编译:需 vcpkg 安装 OpenSSL、jsoncpp 等依赖,或用 -DBUILD_CTL=OFF 裁剪工具链减少编译时间。
- ADD_METHOD_TO 与类外注册:路径冲突(/user/{id} 与 /user/me)按注册顺序匹配,静态路径应注册在前。
- WebSocket 广播遍历:遍历 getWebSocketConnections() 时不要在回调内直接修改连接集合,用 queueInLoop 延迟执行。
- 日志级别:生产环境 log_level 设为 info 以下,debug 会显著拖慢高 QPS 路径。
11. FAQ 速查表
| 问题 | 答案 |
|---|---|
| Drogon 需要 C++20 吗? | 不需要,C++17 可用全部核心功能;C++20 才能用协程控制器 |
| 如何部署到生产? | 编译 Release 单二进制 + config.json,可配 systemd/容器 |
| 支持 HTTP/2 吗? | 支持,需编译时启用 OpenSSL 并配置 HTTPS |
| 能做反向代理吗? | 不擅长,建议 nginx 前置;Drogon 专注应用服务 |
| 中文乱码怎么办? | 设置 resp->setContentTypeCode(CT_APPLICATION_JSON) 或显式 UTF-8 Content-Type |
| 如何做限流? | 过滤器 + 令牌桶(Redis/本地计数器) |
| 有集群会话吗? | 会话支持 Cookie/Session,可对接 Redis 做分布式会话 |
12. 总结
Drogon 是目前 C++ 生态中少有的"开箱即用的一体化 Web 应用框架":协程编程模型让异步代码回归顺序可读,内置 ORM/过滤器/WebSocket 覆盖了 Web 服务 90% 的日常需求,MIT 协议与跨平台支持让它适合从边缘设备到云端微服务的全谱系部署。如果团队已经用 C++ 承载核心业务,又希望用最少的胶水代码对外提供 HTTP/WebSocket 能力,Drogon 是当前性价比最高的选择。