1. 开篇:为什么要在 Ubuntu 上折腾 Cutelyst
作为一个常年用 Qt 写桌面应用的开发者,我一直觉得 Qt 就是用来做 GUI 的。直到去年某个项目里需要给内部工具加一个轻量级的 Web 管理界面,我才开始接触 Cutelyst——这个把 Qt 的异步能力和 Web 服务端逻辑结合起来的 C++ 框架。说实话,第一次看到这个名字我也是一脸懵,但用了一段时间之后不得不承认:如果你已经熟悉 Qt 的信号槽机制和 QObject 模型,Cutelyst 的上手成本比想象中低很多,而且对于本来就是 C++ 技术栈的团队来说,整套服务的可维护性比混插 Python 或 Node 的方案要好不少。
这篇笔记是我个人实际操作记录的第九篇,重点讲在 Ubuntu 20.04 环境下,搭配 Qt 5.15.2,用 Qt Creator 从零到一把 Cutelyst 编译成可运行的 web 服务器。这篇内容的适用对象很明确:已经会基础 Qt 开发、想尝试用 C++ 写 Web 服务、但不想走编译地狱弯路的开发者。
这篇文章不是官方文档的翻译,也不会从头讲 Cutelyst 的完整 API,我只把从环境准备、依赖安装、源码编译到 Qt Creator 工程配置这一路踩过的坑和验证过的方法完整记录下来。如果你跟着做一遍还编译不过,欢迎对照我在文末写的排查清单逐项检查,大概率问题就出在那一两个容易被忽略的依赖版本上。
2. 编译前的三个关键选择:Qt 版本、Cutelyst 分支、编译器
2.1 为什么选 Ubuntu 20.04 + Qt 5.15.2 这套组合
先说结论:这不是随意搭配,而是经过验证的稳定组合。很多人在 Windows 或 macOS 上编译 Cutelyst 其实更省事,因为依赖库都好找,但生产环境跑 Linux 服务器的团队占了大多数,所以 Ubuntu 20.04 的系统镜像非常常见,既不会太老导致新库不兼容,也不会太新导致文档跟不上。
Qt 5.15.2 是 Qt 5 系列的长期支持版本,对 Cutelyst 1.0 系列来说足够稳定,而且 5.15.2 是最后一代支持很多老式 Ubuntu 20.04 默认编译环境的 Qt 版本。如果选 Qt 6,Cutelyst 的某些模块(尤其是插件系统相关的部分)会有额外的兼容工作,对新手不太友好,所以我建议初学阶段老老实实用 Qt 5.15.2。
2.2 Cutelyst 分支选择的逻辑
Cutelyst 官方仓库的主分支常年保持开发状态,功能最新,但某些时刻(尤其是依赖库更新之后)可能出现构建脚本和依赖版本不匹配的情况。我实际操作中默认选择 v1.0.0 系列的稳定 tag 或者最新的 release 分支,不追主分支,原因很简单:这个框架更新频率不高,稳定分支和主分支之间差异没那么大,但稳定分支的文档、示例代码和网上讨论的匹配度更高,出了问题容易搜到答案。
这个选择逻辑和 Qt 版本选择是一个道理——开发框架的稳定性,永远比追新版本带来的新鲜感更重要。
2.3 编译器与 CMake 版本检查
Ubuntu 20.04 系统自带的默认 gcc 是 9.4 版本,这足够支持 C++17 标准,而 Cutelyst 1.0 正好要求 C++17,所以直接用系统自带 gcc 就好,不需要额外升级编译器,省掉了很多不必要的折腾。
但有两个东西必须查版本,很重要:
- CMake:Cutelyst 使用 CMake 作为构建系统,官方 README 写的最低要求是 CMake 3.14。Ubuntu 20.04 默认的 CMake 据我所知是 3.16.3,刚好满足要求。
- 额外检查:Qt5 的 CMake 配置文件是否能被正确找到,这直接关系到后面 Qt Creator 能否正确加载项目。后面会详细说。
提示:最好别在 Ubuntu 20.04 上去 apt 装最新 CMake,默认源版本就已经够用。如果你之前装过其他版本,建议用 cmake --version 先确认,避免版本混乱。
3. 依赖库的安装顺序,一个都不能漏
3.1 Cutelyst 的完整依赖清单
Cutelyst 不是单个库,它包含核心模块和一堆可选插件。编译的时候,如果你的系统里缺了对应依赖,CMake 会自动跳过某些模块的编译,表面上看起来编译成功了,实际上你需要的功能并不在包里。这就是好多人编译完之后发现“怎么少了某某模块”的原因。
就算你只想跑个 Hello World 服务器,下面这些依赖也必须装齐。我列一张我在 Ubuntu 20.04 上验证过的清单:
| 依赖库 | 系统包名 | 用途 | 是否必需 |
|---|---|---|---|
| Qt 5 核心模块 | qtbase5-dev | 提供 Qt Core、Qt Network 等基础模块 | 必需 |
| Qt 5 SQL 模块 | libqt5sql5 | Cutelyst 的部分插件需要数据库支持 | 推荐安装 |
| OpenSSL | libssl-dev | 提供 HTTPS 加密支持 | 推荐安装 |
| CMake | cmake | 构建工具 | 必需 |
| g++ 编译器 | g++ | 编译 C++ 代码 | 必需 |
| UUID 库 | uuid-dev | 用于生成请求唯一标识等操作 | 必需 |
3.2 具体安装命令与一条附加建议
在 Ubuntu 20.04 上,用 apt 装依赖是最省心的方式:
sudo apt update sudo apt install -y build-essential cmake uuid-dev libssl-dev libqt5sql5 qtbase5-dev其中 build-essential 会带出 gcc、g++、make 等基础编译工具,qtbase5-dev 是 Qt 5 开发包的核心,uuid-dev 和 libssl-dev 是 Cutelyst 在编译时检查的硬性依赖。如果你后面打算连接 MySQL、PostgreSQL 或者使用 Redis 作为 session 存储,建议提前把对应模块也装了,免得第一次编译过了、后面加功能又要重新编译。
我刚接触的时候踩过一个坑:只装了 qtbase5-dev,而没装 libqt5sql5,结果最后 Cutelyst 的某个插件在 CMake 配置阶段被静默跳过,导致我后面想用数据库持久化功能时找不到对应的头文件,又要重新编译整个框架,白白浪费了半小时。
3.3 Qt 5.15.2 的安装建议
Ubuntu 20.04 的 apt 源里自带的 Qt 版本我记得是 5.12.8 或者类似的老版本,如果你想用 5.15.2,建议直接去 Qt 官方下载 Qt 5.15.2 的离线安装包,安装过程中只勾选你要用的模块就行。如果你硬盘空间和内存都够,建议至少勾选 Qt 5.15.2 下的 Desktop gcc 64-bit 这一项,这是编译期默认使用的工具链。
这里有一个很多人不理解的细节:qtbase5-dev 是给系统自带 Qt 用的开发包,安装它的目的是为了让 Cutelyst 在依赖检查阶段能找到 Qt5 的基础模块。而你后续用 Qt Creator 新建项目时,会指定刚才手动安装的 Qt 5.15.2 路径。两者各司其职,不冲突。
4. 编译 Cutelyst 源码,从 clone 到 make install
4.1 拉取源码并切换稳定分支
我习惯先建一个专用目录,把源码和后续构建文件都放在一起,路径里不要有空格和中文字符,这是 C++ 项目最常见的隐藏问题之一。
mkdir -p ~/dev/cutelyst && cd ~/dev/cutelyst git clone https://github.com/cutelyst/cutelyst.git cd cutelyst git checkout v1.0.0这里之所以 check out 到 v1.0.0,是保证我们用的代码和这篇笔记里的验证结果是一致的。如果你想追最新版本,可以把分支名换成最新的 release 标签,但要注意依赖版本可能不同。
4.2 配置 CMake 构建参数
Cutelyst 的构建参数比较直观。我个人的建议是,一切以默认参数为主,只开启你确实需要的选项。新手不建议上来就改各种配置,反而会因为配置组合太多而出莫名其妙的问题。
mkdir build && cd build cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_PREFIX_PATH=/opt/Qt/5.15.2/gcc_64解释几个关键点:
CMAKE_PREFIX_PATH指向你手动安装的 Qt 5.15.2 路径。这个很重要,因为系统默认的 CMake 查找路径里不一定包含这个目录,不指定的话 CMake 大概率会找到 /usr/lib/x86_64-linux-gnu/cmake 下的系统 Qt,后续就不会报错。我遇到的情况是:如果你之前用 apt 装过 qtbase5-dev,系统 Qt 版本比 5.15.2 旧,CMake 会默认找到旧版,而 Cutelyst 要求 Qt 5.15.0 以上,直接配置失败。所以这里一定要显式指定。CMAKE_BUILD_TYPE=Release,编译出来的库体积小,而且正式运行也推荐 Release 版本。- 如果你想编译 Cutelyst 的测试用例,可以加
-DBUILD_TESTING=OFF跳过,节省时间。
配置完成后,终端会输出一大段检查结果,重点看这两点:
- 有没有出现
Could NOT find开头的错误 - Qt 相关路径是否是
/opt/Qt/5.15.2/gcc_64
4.3 编译与安装,注意并发数
配置完成后直接编译:
make -j$(nproc) sudo make install-j$(nproc)表示使用 CPU 的全部核心并行编译,在你首次编译一个全新项目的时候明显提高速度。如果你内存不够(比如虚拟机只分配了 2GB),建议改成make -j2,否则编译过程中可能出现内存耗尽导致编译器被杀进程的情况。
sudo make install会把编译好的动态库安装到/usr/local/lib,然后你需要刷新一下动态链接库缓存:
sudo ldconfig此时可以用一条命令验证 Cutelyst 是否安装成功:
pkg-config --modversion CutelystQt5如果输出类似 1.0.0 的版本号,那恭喜你,Cutelyst 核心库已经编译安装完成。这一步非常重要,后面 Qt Creator 构建项目也要靠这个 pkg-config 文件来定位库。
4.4 编译时长与性能参考
在同一台 4 核 CPU、8GB 内存的虚拟机上,从零开始编译整个 Cutelyst(含核心模块和常用插件)大概需要 8 到 15 分钟。如果你只是编译核心模块,可以加-DBUILD_PLUGINS=OFF跳过插件编译,时间会更快一点。当然,插件后面需要时再单独编译,方式一样。
5. 用 Qt Creator 创建第一个 Cutelyst Web 服务器
5.1 新建项目,配置 Kit
打开 Qt Creator,选择“New Project”,在模板列表里找到 Cutelyst 相关的模板选项,也可能你装的是较早版本,模板不叫 Cutelyst,而叫 “Qt Console Application” 之类的模板,需要手动改 CMakeLists。
我个人更容易上手的方式是手动创建一个 CMake 项目,然后自己写 CMakeLists.txt。好处是能够直观感受到这个框架的构建逻辑,如果后续要部署到服务器上,这个过程会很有帮助。用以下内容创建 CMakeLists.txt:
cmake_minimum_required(VERSION 3.14) project(MyFirstServer CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(CutelystQt5 REQUIRED) add_executable(myfirstserver src/main.cpp) target_link_libraries(myfirstserver Cutelyst::Core)这里的关键之处是find_package(CutelystQt5 REQUIRED),它会让 CMake 运行pkg-config的封装逻辑来定位 Cutelyst 库。系统里如果没找到,CMake 会在生成阶段直接停下来并报错。
5.2 写一个能跑的入口 main.cpp
Cutelyst 的入口看起来很像 Qt 的 main 函数,因为它本身就是基于 Qt 事件循环的。最简单能跑的服务器代码如下:
#include <Cutelyst/Application> #include <Cutelyst/Controller> #include <Cutelyst/Request> #include <Cutelyst/Response> #include <Cutelyst/Context> using namespace Cutelyst; class RootController : public Controller { Q_OBJECT public: C_ATTR(display, :Path("") :AutoArgs()) void display(Context *c) { c->response()->body() = QStringLiteral("Hello Cutelyst!"); } }; int main(int argc, char *argv[]) { QCoreApplication::setOrganizationName("MyDev"); QCoreApplication::setApplicationName("MyFirstServer"); QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); Application app(argc, argv, new RootController); return app.exec(); }这里面有几个 Cutelyst 特有的语法点需要解释一下:
C_ATTR宏是 Cutelyst 用来声明路由的方式。:Path("")表示请求路径为根路径/,:AutoArgs()表示自动处理 URL 参数。Context是 Cutelyst 对一次请求的处理环境封装,通过它来拿请求数据、写响应内容。- 根控制器要传给
Application构造函数,这个对象继承自QCoreApplication,所以整个服务是跑在 Qt 的事件循环里的。
5.3 在 Qt Creator 里配置构建套件
新建项目后,Qt Creator 会让你选择构建套件(Kit),这里必须选择一个能同时定位到 Qt 5.15.2 和系统编译器的套件。如果你安装 Qt 5.15.2 时是通过 Qt Catalog 安装的,Qt Creator 会自动识别出对应的 Kit;如果你只装了离线库、没装 Qt Creator 自带的 Kit,手动添加 Kit 时记得同时指定:
- 编译器:系统自带 gcc,路径一般在
/usr/bin/gcc和/usr/bin/g++ - Qt 版本:
/opt/Qt/5.15.2/gcc_64/bin/qmake - CMake:系统自带的
/usr/bin/cmake
Kit 配置正确后,直接点击构建。第一次构建时会比较慢,因为 CMake 要重新跑一遍依赖解析,之后每次只编译改动的文件,速度就很快了。
5.4 运行与验证
构建成功后,在 Qt Creator 里直接点击运行。此时应该能看到控制台输出类似这样的日志:
[INFO ] Application started on port 8080然后浏览器访问http://localhost:8080/,就能看到 “Hello Cutelyst!” 字样。
如果端口 8080 被占用了,可以在 main 函数里设置环境变量CUTELYST_LISTEN_PORT来改端口,或者干脆在编译前用setenv环境变量设置。我个人更喜欢在.env里维护这些配置,不过这就属于后续部署的范畴了。
6. 常见问题与排查技巧实录
6.1 Qt 路径被系统旧版本抢占
这个问题我遇到过不止一次:CMake 配置阶段报Could NOT find Qt5: found unsuitable version "5.12.8",说明 CMake 查找 Qt 的时候优先找到了 apt 里的旧版。解决办法很简单,在 CMake 配置命令里显式指定:
cmake .. -DCMAKE_PREFIX_PATH=/opt/Qt/5.15.2/gcc_64如果这个变量已经写了但依然不生效,可以使用cmake -LA . | grep QT_来查看当前缓存里的 Qt 相关变量,逐一确认路径是否被正确替换。
6.2 Qt Creator 里无法找到 Cutelyst 的 CMake 包
在 Qt Creator 构建时如果提示By not providing "FindCutelystQt5.cmake",通常不是 Cutelyst 没装好,而是 Qt Creator 使用的 CMake 没有把/usr/local/lib/cmake/CutelystQt5这个路径加进查找目录。解决办法是在项目 CMakeLists.txt 里临时加一行:
list(APPEND CMAKE_PREFIX_PATH "/usr/local")或者更优雅一点,在项目里创建一个.cmake配置,在你的用户目录下设置:
export CMAKE_PREFIX_PATH=/usr/local:$CMAKE_PREFIX_PATH顺手说一句,安装后 Cutelyst 的 CMake 配置文件默认被安装到了/usr/local/lib/cmake/CutelystQt5,如果你的安装路径不同,记得对应修改 CMAKE_PREFIX_PATH。
6.3 运行时提示找不到动态库
编译链接都成功了,但运行时报error while loading shared libraries: libCutelystQt5Core.so.1: cannot open shared object file,说明可执行文件在运行时找不到 Cutelyst 的.so文件。多数情况是动态链接库缓存没有刷新。依次跑:
sudo ldconfig ldd your_executable | grep Cutelyst如果 ldd 还是显示 not found,那就把/usr/local/lib加入动态链接库配置:
echo "/usr/local/lib" | sudo tee /etc/ld.so.conf.d/cutelyst.conf sudo ldconfig这个问题在纯命令行编译时很常见,但用 Qt Creator 有时候不太明显,因为 Qt Creator 运行时环境设置会带上一些库路径,但服务器部署时就会暴露出来。
6.4 浏览器访问不了,但启动日志正常
如果你的服务启动日志显示监听端口正常,但浏览器访问不通,优先排查防火墙和监听地址。Cutelyst 默认监听的是所有网络接口,也可能只监听 localhost,这个要看版本默认配置。如果只监听了 localhost,你把地址改成http://127.0.0.1:8080访问即可;如果想外网访问,需要在启动时设置监听地址为0.0.0.0。
Ubuntu 20.04 的防火墙默认是 ufw,如果你开着防火墙,记得把对应端口放行:
sudo ufw allow 80806.5 虚机内存不足导致编译崩溃
如果你是在虚拟机上操作,内存分配少于 2GB,编译 Cutelyst 时可能出现 “Killed” 提示——这不是代码问题,是 OOM Killer 把编译进程杀了。建议把 swap 加上,临时加个 2GB 的交换分区就行:
sudo fallocate -l 2G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile当然,如果你本来就在编译大型项目,还是建议物理机或内存大一点的 ECS 上操作,省事得多。
7. 我实际使用中的几个经验补充
最后分享几个我这段时间使用 Cutelyst 总结下来的个人习惯。第一,Cutelyst 的官方文档不厚,但代码仓库里的示例工程反而是最靠谱的学习资料,尤其examples/目录下的聊天室和博客示例,数据结构简单、路由清晰,适合作为第二个练手项目。第二,Cutelyst 本身就是基于 Qt 的异步网络框架,如果你的服务要处理长时间任务,建议先把 Qt 的线程池机制理清楚,这决定了后面会不会遇到事件循环阻塞。第三,如果你只是想在现有 Qt 项目里快速加一层 Web API,不一定非要把整个架构切到 Cutelyst——它内置了一个可以嵌入现有 Qt 应用的机制,单片机和嵌入式领域也常拿它做设备管理后台,这个用法比单独部署一个完整 web 服务要轻量得多。
写这篇笔记的时候正好是本地新装了一台 64GB 内存的办公机,编译 Cutelyst 从 configure 到 install 只花了不到三分钟,想想自己第一次在虚拟机里等了十几分钟才成功,确实还是硬件底子决定了起步体验。不管用什么环境,把依赖搞对、把流程理顺,Cutelyst 编译这件事其实就这么大点事。