资讯动态

Ubuntu 上用 Drogon 搭建高性能 C++ Web 服务

发布时间:2026/10/23 11:16:29 来源:尧图企业网站定制
简介围绕Ubuntu系统上的Drogon框架Web开发这份压缩包内含一套可直接运行的C示例工程。它面向希望借助Drogon实现登录鉴权与数据库集成的开发者内容涵盖Post请求登录、Token生成与校验、MySQL及SQLite3数据库连接等关键场景。压缩包共16个文件以6个cc源文件、4个头文件为主体配合CMakeLists构建脚本、JSON配置文件与SQLite数据库文件。工程内包含登录控制器、WebSocket回显、测试控制器及学生模型等模块方便按控制器、模型、配置等层次对照阅读。整体仅23KB轻量清晰。目前已有375人学习/下载适合具备一定C基础、想快速上手Drogon的读者。通过现有控制器和模型示例可直观学习Drogon的ORM查询、HTTP响应处理以及多种数据库适配方式有效降低在Ubuntu下从零构建Web服务的门槛。1. 在 Ubuntu 上跑 Drogon为什么我把 C 拉来写 Web 服务大多数人听到「C 写 Web 服务」的第一反应是劝退——编译慢、没生态、人才难招Java 和 Go 不香吗。直到我在某次高并发场景里被 Node.js 的单线程天花板卡住换了台机器用 Drogon 跑同样的接口吞吐量直接翻了几倍才意识到 C 在 Web 领域并不是老古董而是一直被低估。Drogon 是一个基于非阻塞 IO 和线程池的 C Web 框架在 Ubuntu 上从零编译到第一个接口跑通熟练的话半天就能完成。这篇笔记就是要把「ubuntu web drogon 框架」这条路捋清楚怎么编译、怎么组织代码、哪些参数值得调、哪些坑必须躲。适合两类人一是 C 开发者想给内部工具写个高性能 HTTP 服务二是被 Node/Go/Python 性能瓶颈卡住、想找替代方案的架构师。2. 编译安装与最小项目先让 Drogon 动起来2.1 选型理由Drogon 拿什么跟 Node/Go 抢高并发Drogon 的核心卖点不是语法糖而是底层模型。它使用了非阻塞网络 IO 加多线程事件循环每个网络线程维护自己的 epoll 事件循环业务回调在线程池里执行避免了传统阻塞模型的线程切换开销。对比一下日常见到的方案Node.js 单线程事件循环遇到 CPU 密集任务容易把整个进程拖住Go 的 goroutine 模型虽然并发能力强但调度器和 GC 在大流量下也有损耗。Drogon 在几个公开的 hello-world 级压测里经常排在 C/Rust 一档但生态和文档成熟度又比 Rust 的 Actix 更容易上手。这里我不是劝你所有项目都换 C而是说如果接口逻辑简单、追求极致吞吐、对内存占用敏感Drogon 值得纳入选型对比。常见的对比维度我列在下面方便你按需判断。对比项DrogonNode.js/ExpressGo/Net-HTTP并发模型非阻塞 IO 线程池单线程事件循环goroutine业务逻辑复杂时回调里别写阻塞操作阻塞会拖垮整个进程有 GC 停顿部署产物单一可执行文件需要运行时与依赖单一可执行文件静态类型与内存控制极好弱中等团队上手成本高C 门槛低低适合场景网关、内部高性能服务、物联网接入快速业务系统云原生应用我的判断是优先把 Drogon 用在「接口不多、逻辑偏 IO、并发要求高」的位置比如物联网设备接入网关、实时指标上报接口、内部微服务的边缘入口。业务逻辑复杂、迭代频繁的模块用其他语言更划算。2.2 Ubuntu 依赖安装与源码编译一份能照着敲的清单我先后在两台 Ubuntu 服务器上装过 Drogon一台 20.04一台 22.04编译过程基本一致。先装依赖缺一个后面 CMake 阶段就会报错。sudo apt update sudo apt install -y git cmake g make pkg-config \ libssl-dev libjsoncpp-dev uuid-dev zlib1g-dev \ libbrotli-dev libc-ares-dev依赖解析libssl-dev提供 TLS/SSL 支持如果接口只跑 HTTP 不跑 HTTPS可以不要但建议装上后面万一要开 TLS 不用回头再编。libjsoncpp-devDrogon 的 HTTP 接口常返回 JSON 数据框架内部用它序列化。uuid-dev生成 UUID数据库连接池和分布式追踪场景会用到。libbrotli-dev和libc-ares-dev新版 Drogon 的 Brotli 压缩与异步 DNS 支持缺了也能编但功能不全。依赖装完克隆源码并编译git clone --depth 1 drogon 官方仓库地址 cd drogon git submodule update --init mkdir -p build cd build cmake -DCMAKE_BUILD_TYPErelease .. make -j$(nproc) sudo make install sudo ldconfig编译参数说明--depth 1只拉取最新一次提交省时省流量也让版本跟着主干走。git submodule update --init会拉取 Drogon 依赖的网络库组件漏了这步编译时会出现头文件缺失。-DCMAKE_BUILD_TYPErelease指定发布模式开启编译优化。Debug 模式性能与发布版相差很大压测前务必检查这一项。make -j$(nproc)让 make 使用全部 CPU 核数并行编译。我第一次在 4 核机器上编大概花了十几分钟耐心等。编译完成后sudo make install会把头文件装到/usr/local/include/drogon库文件装到/usr/local/lib。最后再执行sudo ldconfig刷新动态链接库缓存。验证是否装好在终端里输入pkg-config --modversion drogon能输出版本号就说明环境 OK。如果提示找不到包八成是pkg-config的搜索路径不包含/usr/local/lib/pkgconfig这时可以把/usr/local/lib/pkgconfig加入PKG_CONFIG_PATH环境变量再试。这一步是玄学高发区环境变量没配好的话后面 CMake 阶段找库会翻车。2.3 用 drogon_ctl 生成项目骨架并跑通默认页Drogon 提供命令行工具drogon_ctl专门用来生成控制器、过滤器、项目骨架。一条命令就能把目录结构拉出来drogon_ctl create project hello_web cd hello_web tree -L 2生成后的标准结构如下hello_web ├── CMakeLists.txt ├── config.json ├── controllers │ └── controller0.h │ └── controller0.cc ├── filters ├── main.cc ├── models ├── tests └── views目录含义controllers/控制器实现一个控制器可以挂多个路由。filters/过滤器做鉴权、限流等前置逻辑。models/数据库模型配合 ORM 使用。views/服务端渲染模板。main.cc入口文件负责读取配置并启动应用。编译并启动mkdir -p build cd build cmake .. make -j$(nproc) ./hello_web启动后终端会打印监听地址和端口默认是0.0.0.0:8080。浏览器或 curl 访问一下curl http://127.0.0.1:8080/如果返回 404别慌——默认骨架生成的控制器不一定给根路径注册了路由。用 curl 请求/hello试一下能返回 JSON 就算项目骨架跑通了。这一步的目的是确认「编译、链接、启动、监听」全链路没问题先别纠结具体响应内容。我第一次用这个工具时生成的项目编译了三次才过原因分别是没跑git submodule update和 CMake 版本太老后面避坑章会展开。3. 路由、控制器与配置把第一个接口交出去3.1 控制器的两种写法与路由注册宏Drogon 的路由注册不依赖注解扫描而是靠宏在编译期把 URL 和函数绑在一起。见过有人在这上面绕半天——宏写错了一层 namespace 就会链接失败。先来看最常用的 HTTP 控制器写法// controllers/HelloController.h #pragma once #include drogon/HttpController.h using namespace drogon; class HelloController : public HttpControllerHelloController { public: void hello(const HttpRequestPtr req, callback_t callback) const; void getInfo(const HttpRequestPtr req, callback_t callback, std::string id) const; private: void registerHandlers() override; };对应的实现文件// controllers/HelloController.cc #include HelloController.h #include drogon/HttpResponse.h #include drogon/HttpJsonResponse.h void HelloController::hello(const HttpRequestPtr req, callback_t callback) const { Json::Value ret; ret[code] 0; ret[message] hello from drogon; auto resp HttpResponse::newHttpJsonResponse(ret); callback(resp); } void HelloController::getInfo(const HttpRequestPtr req, callback_t callback, std::string id) const { Json::Value ret; ret[id] id; ret[name] user_ id; auto resp HttpResponse::newHttpJsonResponse(ret); callback(resp); } void HelloController::registerHandlers() { addHandler(hello, HelloController::hello, {Get}); addHandler(info/{id}, HelloController::getInfo, {Get}); }关键点说明HelloController继承了HttpControllerHelloController这是 Drogon 的 CRTP 模式控制器自身类型作为模板参数传给基类。addHandler的第一个参数是「控制器内部路径」,真正对外暴露的 URL 是控制器注册路径加内部路径。比如控制器在路由里注册为/api/hello内部路径是hello最终访问/api/hello/hello。想避免这种重复路径可以直接在main.cc里给控制器设一个空前缀或者在addHandler里用完整的路径映射。占位符{id}会自动匹配 URL 中的一段函数签名里用std::string id接收框架做类型转换。如果接口比较少不想建控制器Drogon 也支持在main.cc里直接注册普通函数路由#include drogon/drogon.h using namespace drogon; int main() { app().registerHandler(/ping, [](const HttpRequestPtr req, callback_t callback) { auto resp HttpResponse::newHttpResponse(); resp-setBody(pong); callback(resp); }, {Get}); app().run(); }这种写法适合做健康检查或临时接口代码量最小但业务一多还是建议按控制器组织否则路由散落在一堆 lambda 里后续维护会头疼。3.2 config.json 里 5 个必须搞清楚的参数新生成的项目自带config.jsonDrogon 启动时会默认读取同目录下的这个文件。我最常改的字段有 5 个拿不准的参数别乱加。{ listeners: [ { address: 0.0.0.0, port: 8080, https: false } ], threads_num: 0, static_path: ./static, client_max_body_size: 1M, log: { log_level: info } }参数逐个说明address监听地址0.0.0.0表示接收所有网卡流量只在本机调试时改成127.0.0.1更安全。threads_num核心参数设为 0 时 Drogon 自动按 CPU 核数创建线程。这个值不能随便拍脑袋放太大反而会有线程切换开销第 5 章专门讲。static_path静态文件根目录指向存放 HTML/CSS/JS 的文件夹。前端资源直接由 Drogon 托管省得再挂一层 Nginx。client_max_body_size请求体上限默认 1M接口要接收文件上传就得调大比如10M。log_level日志级别info意味着每个请求都会在终端打一条访问日志压测时建议临时改成warn否则刷屏刷得看不清有用信息。启动时还可以用命令行参数覆盖配置./hello_web -c /opt/hello_web/config.json -l /var/log/hello_web-c指定配置文件路径-l指定日志目录。把配置文件和可执行文件分开是上线部署时的好习惯避免在代码目录里翻日志。3.3 编译运行CMake 链接与启动参数说明生成项目的CMakeLists.txt已经写好了找 Drogon 的逻辑但新手经常在改完代码后忘记重新编译还纳闷为什么加了接口没反应。这一步必须走cd build cmake .. make -j$(nproc)编译命令里的玄学在于cmake ..会重新读取 CMakeLists 并检查依赖库是否变化如果新增了源文件而没执行这步make可能不会自动扫描并编译新文件。所以「改了 CMakeLists 或新增了源文件就重新执行 cmake再 make」这是我的血泪经验。编译过程中最常见的错误是找不到头文件类似fatal error: drogon/HttpController.h: No such file or directory这个报错说明 CMake 没找到 Drogon 的安装路径。解决办法是在项目根目录的 CMakeLists.txt 里显式指定set(CMAKE_PREFIX_PATH /usr/local) find_package(Drogon REQUIRED) target_link_libraries(hello_web Drogon::Drogon)CMAKE_PREFIX_PATH告诉 CMake 去哪找已安装的库默认只搜/usr/local之外的路径。改完再跑一遍 cmake 与 make一般能过。启动程序时别直接在后台按CtrlZ糊弄自己Drogon 支持正规的守护方式./hello_web -d-d参数让程序以 daemon 方式后台运行断开 SSH 也不会死。这是上线前必须掌握的启动方式后面配 systemd 时会用到。4. 给接口加鉴权与跨域过滤器让业务可控4.1 过滤器卡在路由前的守门逻辑业务接口一旦放出去首先要面对的就是「谁有权限调」和「跨域能不能调」两个问题。Drogon 的过滤器机制解决的是前者——在请求进入路由处理函数之前先走一道自定义逻辑。常见用途包括校验登录态、IP 白名单、接口限流。过滤器类需要继承Drogon::HttpFilter模板核心是重写doFilter方法。结构非常固定通过就调用callback把请求放行不通过就构造一个拒绝响应传给callback。// filters/TokenAuthFilter.h #pragma once #include drogon/HttpFilter.h using namespace drogon; class TokenAuthFilter : public HttpFilterTokenAuthFilter { public: void doFilter(const HttpRequestPtr req, FilterCallback fcb, FilterChainCallback fccb) override; };实现文件里写具体逻辑#include TokenAuthFilter.h #include drogon/HttpResponse.h void TokenAuthFilter::doFilter(const HttpRequestPtr req, FilterCallback fcb, FilterChainCallback fccb) { auto headers req-headers(); auto token headers.get(X-Token); if (token expected-token-value) { fccb(); // 放行 return; } auto resp HttpResponse::newHttpResponse(); resp-setStatusCode(k401Unauthorized); resp-setBody({\code\:401,\message\:\invalid token\}); fcb(resp); // 拦截 }代码逻辑说明headers()返回请求头集合从中取自定义头X-Token实际项目中这个 token 一般是 JWT 或签名串这里用固定值演示流程。fccb()是放行回调调用它表示请求继续沿路由链往下走。fcb(resp)是拦截回调传入一个响应对象框架会直接把这个响应返回给客户端不再进入控制器。注意拦截分支里return必须写否则函数会继续执行出现「既拦截又放行」的诡异行为。4.2 写一个 token 鉴权过滤器并挂到指定路径过滤器写好后要在路由上挂载才能生效。挂载方式有两种一种是把多个过滤器名字以字符串形式注册在路由宏里另一种是在main.cc里注册过滤器实例并绑定 URL 正则。推荐第一种直接在控制器实现里指定直观且好维护void HelloController::registerHandlers() { addHandler(hello, HelloController::hello, {Get}, TokenAuthFilter); }在main.cc里把过滤器注册到应用中#include filters/TokenAuthFilter.h #include drogon/drogon.h using namespace drogon; int main() { app().registerFilterTokenAuthFilter(); app().run(); }这样配置后访问该路由若不带X-Token头会直接收到 401。多个过滤器可以叠加addHandler(hello, HelloController::hello, {Get}, TokenAuthFilter, RateLimitFilter);执行顺序按传入参数从左到右所有过滤器都放行才会真正进入控制器方法。需要注意的一个坑是过滤器注册名必须与类名一致写错不会编译报错但运行时挂载不生效路由直接绕过鉴权非常危险。我习惯在注册后立刻用 curl 不带 token 请求一次验证 401 是否真的返回。4.3 跨域用 CORS 插件而不是到处加响应头前后端分离项目里浏览器直接请求 Drogon 接口经常会报跨域错误。很多人的第一反应是在控制器里手动加Access-Control-Allow-Origin响应头这样做做一两个接口还行接口一多就变成到处重复。Drogon 自带 CORS 插件用配置驱动比写代码优雅得多推荐直接在config.json里启用。{ plugins: [ { name: Drogon::CORSPlugin, dependencies: [], config: { allow_origins: [http://localhost:3000, https://your-frontend.example.com], allow_methods: [GET, POST, PUT, DELETE, OPTIONS], allow_headers: [Content-Type, X-Token, Authorization], allow_credentials: true, max_age: 3600 } } ] }参数说明allow_origins明确指定哪些前端域名可以跨域调用生产环境不要配*否则任何网站都能往你的接口发请求配合浏览器机制容易引发 CSRF 风险。allow_methods按需放开 HTTP 方法不是所有接口都支持 DELETE 就不写进去。allow_headers如果前端要带X-Token这种自定义头必须在这里声明否则浏览器会认为请求不合法。max_age预检请求的缓存时间单位秒。设大一点能减少 OPTIONS 请求次数但要注意前端有新的自定义头加进来时旧配置缓存会挡路。改完config.json后需要重启服务才能生效。这里有个常见的翻车点改了配置后程序没重启前端照样报跨域错误排查半天发现是忘了重启。调试跨域问题时浏览器开发者工具里看到的是「No Access-Control-Allow-Origin header」字样第一反应应该是先确认服务进程是否加载了新配置。5. 避坑Ubuntu 上 Drogon 最容易翻车的 6 处5.1 编译期酷酷报错找不到 drogon 头文件现象项目里#include drogon/drogon.h后编译直接报No such file or directory。原因多半是库文件已安装但 CMake 的搜索路径没覆盖/usr/local/include或者头文件权限被限制。解决在 CMakeLists 里加set(CMAKE_PREFIX_PATH /usr/local)然后cmake .. make。如果还报错执行sudo ldconfig刷新库缓存再检查/usr/local/include/drogon目录是否存在。我第二次装时漏了libssl-dev编译到一半才报错排查了十分钟所以依赖清单一次装齐很重要。5.2 threads_num 不是越大越快现象把threads_num调到 64以为能显著提升吞吐结果压测数据反而比默认值低一截。原因Drogon 的每个线程都跑独立的事件循环线程过多时 CPU 上下文切换和锁竞争会吃掉收益纯 IO 接口尤其明显。解决先设 0 让框架自动按核数创建压测后再尝试核数乘 2。如果代码里有同步的数据库查询一个查询卡住会占用一个线程这种场景下调大线程数才有意义接口内部全是异步回调时线程数追着 CPU 核数走就够。5.3 访问日志疯涨磁盘悄没声儿满了现象服务跑了一周某天接口全部变慢SSH 登上去发现磁盘 100%。原因默认日志级别是info每个请求都写一行日志高并发下日志文件以 GB 级增长。解决config.json里把log_level调成warn并给日志配 logrotate 轮转策略。运维层面别偷懒日志目录单独挂一块盘更稳妥否则光删日志都够你忙半天。这是我最扎心的一次经历——线上服务没崩先被日志撑崩了。5.4 改完配置重启失败端口残留与 daemon 进程现象停掉旧服务后启动新进程提示bind 0.0.0.0:8080 failed: Address already in use。原因-d方式启动的进程变成了守护进程终端里按 CtrlC 或杀掉终端进程服务可能还在后台跑着端口被旧进程占着。解决先pkill旧进程等几秒再启动新的pkill -f hello_web sleep 2 ./hello_web -d启动后别急着走用curl请求一个接口确认新配置生效。如果服务有多个进程协同工作用pkill -f会一并杀掉子进程执行前先把命令行参数看清楚。5.5 老版本 Ubuntu 与旧 apt 源里的 Drogon 版本纠结现象在 Ubuntu 18.04 上按新教程编译C17 特性支持不全报各种模板语法错误。原因Drogon 要求较新的编译器老系统自带的 GCC 7 对 C17 支持不完整。解决优先使用 20.04 及以上版本编译前gcc --version确认版本。另一个隐藏坑是 Ubuntu 的 apt 源里可能也有 Drogon用apt install libdrogon-dev装出来的是旧版本API 和老教程对不上项目里调addHandler都不认识。我的建议是Drogon 这种更新节奏快的框架直接源码编译跟踪最新版别图方便走 apt。5.6 前端接口 404路由宏与编译顺序的玄学现象接口明明写进了控制器前端请求时返回 404。原因要么是路由注册前缀不对要么是改完代码没有重新编译跑的还是老版本的二进制。解决请求前先用 curl 在服务器上测一遍curl -i http://127.0.0.1:8080/api/hello/hello如果服务器上通而浏览器不通问题在网关或跨域配置如果服务器上就 404检查控制器类里addHandler的注册前缀以及是否加上了过滤器。改完代码后想要确保二进制是最新的就重新执行cmake .. make -j$(nproc)别图省事只跑 make。6. 用 systemd 托管并做一轮压测性能心里有底6.1 用 systemd 把服务变成开机自启前面手动-d启动只适合开发环境正式环境建议交给 systemd崩溃自动拉起、开机自启、日志统一管理。在/etc/systemd/system/hello_web.service里写[Unit] DescriptionDrogon hello web service Afternetwork.target [Service] ExecStart/opt/hello_web/hello_web -c /opt/hello_web/config.json Restarton-failure RestartSec3 Userwww-data Groupwww-data NoNewPrivilegestrue [Install] WantedBymulti-user.target配置说明Restarton-failure进程异常退出时自动拉起防止接口半夜挂掉没人发现。Userwww-data用普通用户跑服务别用 root安全收紧一点没坏处。NoNewPrivilegestrue禁止进程提权经典加固项。启动并设置为开机自启sudo systemctl daemon-reload sudo systemctl enable hello_web sudo systemctl start hello_web之后改配置只需systemctl restart hello_web再配合前面的端口排查思路部署整个服务打包成一个干净流程。6.2 压测与关键内核参数别让默认值拖后腿服务上线前我会用wrk跑一轮压测给自己一个性能基准wrk -t4 -c200 -d30s http://127.0.0.1:8080/api/hello/hello参数含义4 个线程模拟压测200 个并发连接持续 30 秒。跑完重点看两个值Requests/sec 和 Latency 的 p99。如果 CPU 没跑满但性能上不去检查是否撞了系统文件描述符限制调大内核参数ulimit -n 65535 sudo sysctl -w net.core.somaxconn8192somaxconn是 TCP 全连接队列长度高并发下默认 128 很容易成为瓶颈调完后重启服务再压一遍。压测这步不是走形式我习惯把数字记进部署文档下次优化代码或升级机器时对比着看性能变化一目了然。我养成的一个习惯是每接一个新项目先在 Ubuntu 上把 Drogon 的编译、路由、过滤器、systemd 四件事跑通再写任何业务代码。这套地基验证完后面填业务逻辑基本不会折腾环境。之前某个内部网关项目上线后稳定跑了半年唯一一次事故就是日志撑满磁盘——自那以后我每台服务器的日志都强制挂轮转教训都是踩出来的。希望这篇笔记能帮你把 Drogon 在 Ubuntu 上的路走顺少踩几个我踩过的坑。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑