☰
Ubuntu 上用 drogon 框架搭建 C++ Web 服务:从入门到部署避坑
2026/10/13 14:34:04 网站建设 项目流程

简介:Ubuntu环境下的Drogon框架实战资料包,面向有一定C++基础、希望使用高性能C++14 Web框架搭建服务的开发者。内容紧密围绕Drogon的安装构建、项目模板生成、登录接口实现与Token鉴权返回展开,并演示如何通过内置ORM同时集成MySQL与SQLite3,覆盖Post/Get请求处理、数据库连接配置、模型映射与查询语句封装等关键环节。包内共16个文件,包含6个cc源码、4个头文件、2个json配置、1个db数据库及辅助文本文件,整体约23KB,目录层级直观,插桩与配置分离,便于快速定位。已有374人学习使用,适合用作从零上手Drogon的入门参考,可直接对照源码理解控制器定义、Http响应构造和数据库读写流程,减少环境搭建与踩坑时间。

1. Ubuntu 上跑 Web 服务:drogon 框架到底解决什么问题

提起 ubuntu web drogon 框架,多数人的第一反应是:C++ 写 Web,是不是找罪受。实际用下来,drogon 是少数能把 C++ Web 开发做到顺手程度的框架——它内建了异步网络库、协程支持和整套 Web 基础设施,编译完就是一个自带 HTTP 服务的二进制,不用自己拼线程池和 socket。在 Ubuntu 上做低延迟接口、网关、统计上报这类场景,它比想象中成熟。

适合用的,一类是 C++ 团队想把算法库直接暴露成 HTTP 接口,另一类是追求吞吐、不想为 Web 层再引一门新语言的开发者。新手照流程走,半小时能跑起服务,前提是先迈过安装阶段的两个坑。

下面按依赖编译、第一个接口、工程化、部署避坑、进阶用法展开,每步都可在 Ubuntu 上原样执行。

2. 编译安装 drogon:依赖关系、编译选项和最容易卡住的两个地方

安装 drogon 不是一条 apt 命令能解决的。主线版本推荐源码编译,原因是仓库里的版本往往落后于框架本身的迭代,而 drogon 的编译链路里又夹着一个网络库 trantor,整个过程第一次走会有几个位置莫名卡住。先把依赖关系讲清楚,再动手,能省掉大部分试错时间。

2.1 先认清依赖关系:框架、网络库与编译器的三角关系

drogon 不是单一仓库里全部代码自包含的项目。它本体负责 HTTP 解析、路由、控制器和配置加载,底层的非阻塞网络事件循环在 trantor 里实现。拉源码时如果没把子模块一起拉下来,编译会直接报找不到头文件,这是新手最常遇到的第一个“卡住”。

编译器方面,drogon 要求 C++17 及以上,近几年的 Ubuntu LTS 自带的 g++ 都满足。这里要确认的是 cmake 版本不要太旧,以及系统里有没有装开发库。我一般会提前装好 openssl、jsoncpp、zlib、uuid 这几类开发包,它们分别用于 HTTPS 支持、JSON 序列化、压缩和标识生成。缺哪一个,cmake 阶段就会用红字提示你,与其来回补包,不如一次装齐。

还有一个容易忽略的点:如果之前装过旧版 drogon,或系统里存在多个 cmake 版本,编译前最好确认cmake --version和g++ --version是预期版本。这种环境问题在重装系统的机器上尤其常见,先花两分钟确认,后面就顺畅了。

2.2 最小安装命令:从 clone 到 make install

下面这套命令是我在 Ubuntu 上反复使用的最小流程。仓库地址从 drogon 项目主页复制,注意 clone 时一定要带--recursive,否则 trantor 不会自动拉下来。

sudo apt update sudo apt install -y git cmake g++ libssl-dev libjsoncpp-dev uuid-dev zlib1g-dev git clone --recursive <drogon 仓库地址> drogon cd drogon mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_EXAMPLES=OFF -DBUILD_TESTING=OFF make -j$(nproc) sudo make install sudo ldconfig

第一步安装的是编译期依赖,git 和 cmake 不用解释,g++ 提供 C++17 支持,后面四个 dev 包对应 drogon 运行时要链接的库。clone 时带--recursive是关键,它把 trantor 子模块一起拉全。-j$(nproc)让 make 用满 CPU 核心,能明显缩短编译时间,机器核数多的时候从几分钟压到几十秒。

-DCMAKE_BUILD_TYPE=Release会开编译优化,对性能敏感的服务这是必选项;Debug 模式适合追代码,但响应延迟会差一个量级。BUILD_EXAMPLES=OFF和BUILD_TESTING=OFF是为了跳过示例和测试的编译,这两个开关能让构建时间再降一截。安装完成后执行sudo ldconfig,让动态链接器识别新装的库,否则运行编译出来的程序可能提示找不到共享库。

2.3 编译期的三个典型报错和对应出路

第一个典型报错是编译时找不到 trantor 相关头文件,比如fatal error: trantor/net/EventLoop.h: No such file or directory。原因几乎都是 clone 时漏了子模块,或者在已经 clone 的目录里没执行子模块更新。解决方法是回到 drogon 根目录执行git submodule update --init --recursive,然后重新进 build 目录编译。

第二个是 cmake 阶段提示编译器不支持 C++17,或者编译时出现与std::optional、std::string_view相关的语法错误。这通常是 gcc 版本太老,老旧机器上没升级过编译工具链就会出现。解决思路是先把 g++ 升级到新版本,或者直接换一台系统较新的 Ubuntu 环境,不值得在这种问题上硬调代码。

第三个是 cmake 提示找不到 OpenSSL 或 jsoncpp,红字会明确写出缺失的库名。对应的做法是把对应的-dev包装上再重新 cmake。如果改过 cmake 选项后反复报同样的错误,不要犹豫,把 build 目录删掉重建一次,残留的 CMakeCache 有时候比缺依赖更坑。

3. 写第一个接口:最小 main.cpp、控制器注册与 curl 验证

编译安装只是热身,真正让 drogon 的价值落地的是把接口跑起来。drogon 的接口开发模式跟主流 Web 框架不太一样:它允许用 lambda 直接注册路由,也支持面向对象的控制器写法。先从单文件起步,把服务跑通,再过渡到工程化结构。

3.1 一个文件跑起服务:main.cpp 与最小 CMakeLists

drogon 不需要外部 Web 容器,它自己监听端口、自己处理 HTTP 协议。新建一个目录,放一个 main.cpp,内容如下:

#include <drogon/drogon.h> using namespace drogon; int main() { // 注册一个 GET 路由,路径 /hello 返回 JSON 响应 app().registerHandler("/hello", [](const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback) { Json::Value json; json["message"] = "hello from drogon"; auto resp = HttpResponse::newHttpJsonResponse(json); callback(resp); }, {Get}); // 监听所有网卡,端口 8080 app().addListener("0.0.0.0", 8080).run(); return 0; }

registerHandler的第一个参数是路径,第二个是回调,第三个是允许的 HTTP 方法。回调里req是请求对象,callback用于异步返回响应,这是 drogon 典型的异步风格:不直接 return,而是构造好响应后调用 callback。HttpResponse::newHttpJsonResponse会自动设置Content-Type: application/json,比手工拼字符串响应方便得多。

addListener指定监听地址和端口。0.0.0.0表示监听所有网卡,这样局域网内其他机器也能访问,如果只想本机调试可以改成127.0.0.1。.run()会阻塞当前线程,进入事件循环,直到收到退出信号。

编译时我习惯用一个最小 CMakeLists,直接 g++ 硬编需要自己找库路径,麻烦且容易错:

cmake_minimum_required(VERSION 3.10) project(hello_drogon) set(CMAKE_CXX_STANDARD 17) find_package(Drogon REQUIRED) add_executable(hello main.cpp) target_link_libraries(hello Drogon::Drogon)

find_package(Drogon REQUIRED)会找到第 2 章安装的框架,Drogon::Drogon是它导出的 CMake target,把编译选项、头文件路径和链接库一次性带齐。然后构建运行:

mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release make -j$(nproc) ./hello

看到启动日志里出现监听端口的信息,服务就起来了。这里注意:如果运行时报找不到动态库,说明第 2 章的sudo ldconfig没执行,补上即可。

3.2 路由注册的三种写法与使用时机

单文件用 lambda 注册路由很方便,但接口一多,main.cpp 里会堆成山。drogon 更常见的做法是用控制器类来组织路由。下面是一个最小控制器写法:

#include <drogon/drogon.h> class HelloController : public drogon::HttpController<HelloController> { public: METHOD_LIST_BEGIN METHOD_ADD(HelloController::hello, "/hello", drogon::Get); METHOD_LIST_END void hello(const drogon::HttpRequestPtr &req, std::function<void(const drogon::HttpResponsePtr &)> &&callback) { auto resp = drogon::HttpResponse::newHttpResponse(); resp->setBody("hello from controller"); callback(resp); } };

METHOD_LIST_BEGIN和METHOD_ADD是框架提供的注册宏,把类的成员函数挂到指定路径上。第三参drogon::Get限定只响应 GET 请求。这样写的好处是路由集中声明、方法分散实现,接口多了以后按路径前缀就能找到对应逻辑。

第三种写法是直接用drogon_ctl工具生成控制器骨架,drogon_ctl 会同时生成头文件和实现文件,连路由注册都替你写好。项目生成器生成的骨架用的就是上面这种宏注册方式,自己不用从零敲。

用哪种取决于项目规模。原型和工具脚本用 lambda,正式项目用控制器类,团队协作时优先让 drogon_ctl 生成统一模板,避免每个人风格不同。前两种可以混用,后两者通常只取其一,混着用反而增加阅读负担。

注册方式适合场景说明
lambda 注册小服务、内部工具代码集中,但路由散在 main 里
控制器类注册中大型 Web 服务路由与方法同文件,可维护性好
drogon_ctl 生成团队项目结构统一,生成后按需修改即可

3.3 curl 验证与日志观察

服务跑起来后用 curl 验证最直接:

curl -i http://127.0.0.1:8080/hello

-i会把响应头也打出来。正常情况下能看到 HTTP/1.1 200 状态码,Content-Type是 application/json,响应体是{"message":"hello from drogon"}这样的 JSON。再去访问一个不存在的路径,比如/nope,返回的是 404。能区分这两个状态码,说明路由和 HTTP 状态处理都正常。

drogon 的启动日志会打印监听端口、线程数和框架初始化信息,这些内容在跑第一个服务时值得仔细看一眼。比如日志里会显示当前配置的线程数,这个数字后期会直接影响压测表现。如果日志没出来,检查是不是输出被重定向了,或者日志级别设置得太高,把配置里的日志等级降到 info 再看。

4. 从单个文件到项目:drogon 目录结构、配置项与静态资源

单文件服务能跑通,但真实业务的接口数量、配置参数和资源文件会迅速膨胀,这一步必须切换到工程化结构。drogon 的项目生成器已经定义好了一套规范目录,照着放即可,不需要自己发明组织方式。

4.1 骨架结构:生成器创建的项目里每层放什么

用 drogon_ctl 创建项目骨架,命令很简单:

drogon_ctl create project myapp cd myapp mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release make -j$(nproc)

生成出来的目录大致是这样的:

myapp/ ├── CMakeLists.txt ├── config.json ├── main.cc ├── controllers/ ├── models/ ├── views/ ├── filters/ └── plugins/

controllers放路由控制器,上一章的 HelloController 就应该挪到这里;models放数据库实体和业务模型;views放 csp 模板页面,就是 drogon 的后端渲染方案;filters用来做请求前置处理,比如鉴权和限流;plugins放启动时加载的插件。main.cc是入口,里面只保留应用初始化和插件加载逻辑,不再堆路由。

CMakeLists.txt由生成器自动写好,它会把你新加的.cc文件自动纳入编译。手动往 controllers 里加文件后重新 cmake 一次,新文件就能被识别到,不需要手工维护源文件列表,这是生成器比手动写 CMake 省心的地方。

4.2 config.json 常用项:端口、线程数与日志

项目里的 config.json 是运行时配置的核心。一个最小可用的配置长这样:

{ "listeners": [ { "address": "0.0.0.0", "port": 8080, "https": false } ], "threads": 4, "log": { "log_level": "info" }, "db_clients": [] }

listeners数组可以配多个监听地址,比如同时监听 HTTP 和 HTTPS 端口,或让管理接口只绑内网 IP。threads控制事件循环线程数,我一般会先设成和 CPU 核心数一致,再根据压测结果微调。这里有个常见误解:线程数不是越大越好,drogon 的线程是为事件循环服务的,线程过多反而增加上下文切换开销。

log段控制日志输出级别,生产环境建议用 info,排查问题时临时切到 debug。db_clients配数据库连接池,用不到数据库就留空数组;这个连接池是按配置项预创建的,配了不用的数据库客户端反而白白占用连接。

配置项作用建议
listeners监听地址、端口、协议生产至少留一个内网监听
threads事件循环线程数先按 CPU 核数设,再压测调整
log.log_level日志详细程度生产 info,排查 debug
db_clients数据库连接池不用的库不要配置

改配置后重启进程才会生效。如果改了没变,先确认程序加载的是不是这个 config.json,再看进程有没有被 systemd 拉起导致你杀掉的只是旧进程。这两个点是最常见的“配置不生效”来源。

4.3 静态资源与模板页面

drogon 可以直接托管静态文件。在代码里调用app().setDocumentRoot("./public"),把静态文件放进 public 目录,浏览器访问/a.css时框架就会从该目录找文件返回。也可以把document_root写进 config.json 的 listener 配置里,效果一样,区别只是配置放在哪。

如果服务只面向接口消费方,不需要返回 HTML 页面,静态资源服务可以不开。少开一个能力就少一块暴露面,部署时也少操一份心。

views目录里的 csp 模板是 drogon 的后端渲染方案,适合做管理后台这类服务端渲染页面。模板文件里可以直接嵌 C++ 代码片段,灵活度很高,但这也意味着模板里不能出现业务逻辑堆积。我一般只在简单的列表页渲染场景用 csp,复杂的页面逻辑还是交给接口返回 JSON、前端负责渲染,职责更清楚。新项目没有服务端渲染需求的话,views 目录可以先空着。

5. 部署期常见问题与避坑清单:从编译、端口到句柄

开发和部署是两种完全不同的工作。本地跑通只代表代码层面没问题,真实环境里还有防火墙、进程管理、系统文件描述符这些因素在等着。这里把部署阶段最容易踩的坑按阶段列出来,每一条都是实践中反复遇到过的。

5.1 编译与构建期的坑:子模块缺失与编译器版本错位

现象:全新机器上按流程 clone、cmake、make,编译到一半报错找不到 trantor 头文件,整个构建中断。原因:clone 时没带递归参数,子模块目录是空的,框架本体能拿到源码,底层网络库却是缺失状态。解决:在 drogon 根目录执行git submodule update --init --recursive,然后删掉 build 目录重新 cmake,不要带着残留配置直接重编。

现象:cmake 配置阶段一切正常,编译时报出大量与 C++17 标准库相关的奇怪语法错误,错误信息里频繁出现std::optional和std::string_view。原因:系统 gcc 版本过旧,不支持 C++17,但 cmake 检测时没有明确拒绝。解决:升级 g++ 到新版本,或者选择系统较新的 Ubuntu 环境;不建议为了绕过编译错误改代码里的标准库用法,治标不治本。

现象:改过 cmake 参数后,重新编译发现改动没生效,还是用旧参数在编。原因:build 目录里的 CMakeCache 记住了上次的配置,再次 cmake 时没有完全刷新。解决:删掉 build 目录整个重建,这是面对 cmake 诡异行为时最可靠的后悔药。

5.2 网络与端口的坑:本机能通、外面不通,改了端口不生效

现象:服务在本机 curl 一切正常,局域网内其他机器访问却超时。原因:多半是系统防火墙没有放行对应端口,或者云主机安全组的入站规则没加。解决:先用sudo ufw status查看防火墙状态,如果是开启的,执行放行命令把端口打开;云主机还要去安全组里确认端口是否允许入站。这个坑在部署期的占比极高,网络排查的一大半血泪都在这。

现象:把 config.json 里的端口从 8080 改成 9090,重启服务后访问 8080 依然有响应。原因:旧进程没有被杀掉,新进程绑定 9090 失败或直接没起来,而你访问的还是那个活着的旧进程。解决:用lsof -i :8080找到占用端口的进程 PID,确认后 kill,再启动新进程,并用ss -lntp确认端口监听情况。如果走了 systemd 管理,改完配置后还要执行 daemon-reload 再重启服务,只让进程退出是不够的。

5.3 运行与资源的坑:连接数上不去、事件循环被卡住

现象:压测时并发连接数到了几百就上不去,新请求堆积,日志里没有明显报错。原因:系统的文件描述符限制太低,Ubuntu 默认的进程句柄上限往往只有 1024,对 Web 服务来说这个数字很快就会被连接耗尽。解决:临时调高可以执行ulimit -n 65535,永久生效要写进 systemd 服务文件的LimitNOFILE字段,或者改系统级的 limits 配置。

现象:接口平时延迟正常,偶尔突然集体变慢,持续几百毫秒后恢复,压测时 p99 曲线出现明显毛刺。原因:某个回调里做了阻塞式操作,比如同步访问数据库或磁盘 IO,把事件循环线程卡死了。drogon 是异步框架,事件循环里不能有长时间阻塞代码。解决:把耗时操作丢到独立线程池,或者用协程发起异步调用,回调返回后再继续处理结果。这是 drogon 使用里最典型的“性能玄学”,表面看是框架问题,实际是事件模型没被尊重。

6. 进阶用法:定时任务、WebSocket 推送与压测验证

服务稳定之后,下一步是让 drogon 承担更具体的业务职责。我这里提三个最常用的进阶方向:推数据、跑定时任务、以及上线前怎么验证性能。

6.1 给接口加 WebSocket 推送

接口轮询在实时性要求高的场景下体验很差,drogon 对 WebSocket 的支持比较完整。常见做法是注册一个 WebSocket 控制器,在连接建立、收到消息、连接关闭三个回调里写业务逻辑,把服务端的实时事件主动推给前端。相比自己实现长连接维护,框架已经处理了握手和心跳,你只需要关心业务消息。

6.2 定时任务与异步处理

drogon 底层事件循环提供了定时器接口,类似 runEvery 的机制可以按固定周期执行任务,适合做清理、汇总、心跳上报这类逻辑。要注意的是定时回调同样不能做阻塞操作,和 HTTP 回调一个规矩。周期性的重活要么放线程池,要么拆成小任务分批处理。

6.3 压测看什么:延迟分布与 CPU 头部

上线前我会用 wrk 这类工具压一下,重点不是看 QPS 数字有多高,而是看延迟分布是否平稳。p99 如果远高于平均值,基本可以断定有阻塞或配置问题。结合观察进程 CPU 占用,如果 CPU 没跑满但延迟已经很高,优先怀疑线程数配置或文件描述符限制。先把这两项稳住,再谈调优。

我的习惯是每次改完配置或代码,先压一轮再决定是否上线,而不是等流量把问题暴露出来。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询