无论是 Windows 还是 Linux,管理服务(启动、停止、重启)的核心思路都是一致的:调用系统提供的命令行工具,或使用专门的 API。
下面是不同平台的主流命令及其在 Qt 中的调用方式。
Windows 系统
在 Windows 上,最常用的是 sc 和 net 这两个命令。
1. sc 命令
功能强大、速度较快,推荐使用。基本用法如下:
| 操作 | 命令格式 | 示例 (以 wuauserv 服务为例) |
|---|---|---|
| 启动服务 | sc start <ServiceName> |
sc start wuauserv |
| 停止服务 | sc stop <ServiceName> |
sc stop wuauserv |
| 查询状态 | sc query <ServiceName> |
sc query wuauserv |
| 配置启动类型 | sc config <ServiceName> start= <type> |
sc config wuauserv start= auto |
-
名称说明 :命令中的
<ServiceName>是服务的内部名称 (Service Name),而非在服务管理器中看到的"显示名称"。可以通过sc query命令查看所有服务的内部名称。 -
权限提示 :大多数服务的控制操作需要管理员权限。
2. net 命令
这是一个更简单的命令,功能不如 sc 强大,但足以满足基本的启停需求。
| 操作 | 命令格式 | 示例 |
|---|---|---|
| 启动服务 | net start <ServiceName> |
net start wuauserv |
| 停止服务 | net stop <ServiceName> |
net stop wuauserv |
- 名称注意:同样需要使用服务的内部名称。
在 Qt 中调用 Windows 命令
在 Qt 中,你可以使用 QProcess 来执行上述系统命令,实现对服务的控制。
核心代码示例:
cpp
#include <QProcess>
// 启动服务(以使用 sc 命令为例)
void startService(const QString &serviceName)
{
// 方法1: 使用 sc 命令
QProcess::startDetached("sc", {"start", serviceName});
// 方法2: 使用 net 命令
// QProcess::startDetached("net", {"start", serviceName});
}
// 停止服务
void stopService(const QString &serviceName)
{
QProcess::startDetached("sc", {"stop", serviceName});
}
// 使用示例
int main()
{
const QString serviceName = "wuauserv"; // Windows Update 服务的内部名称
startService(serviceName);
// stopService(serviceName);
return 0;
}
- 注意 :
startDetached会使启动的进程独立于你的 Qt 应用,适合后台执行。你也可以使用QProcess::execute进行同步执行。
更原生的方法:QtServiceController
如果你的 Qt 应用本身是一个服务,且使用了 QtService 库 来开发,那么你还可以获得更高级、更原生的控制方式。
核心代码示例:
cpp
#include <QCoreApplication>
#include "qtservice.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 创建服务控制器,参数为服务内部名称
QtServiceController controller("MyQtService");
// 启动服务
if (controller.start())
qDebug() << "Service started successfully.";
// 停止服务
if (controller.stop())
qDebug() << "Service stopped successfully.";
return 0;
}
这种方式将控制逻辑封装在库中,使得代码更加简洁和模块化。
Linux 系统
服务控制命令
systemctl命令
在现代 Linux 发行版中,systemctl 是管理服务的主力工具。
| 操作 | 命令格式 | 示例 (以 nginx 服务为例) |
|---|---|---|
| 启动服务 | sudo systemctl start <service_name> |
sudo systemctl start nginx |
| 停止服务 | sudo systemctl stop <service_name> |
sudo systemctl stop nginx |
| 重启服务 | sudo systemctl restart <service_name> |
sudo systemctl restart nginx |
| 查看状态 | sudo systemctl status <service_name> |
sudo systemctl status nginx |
| 重载配置 | sudo systemctl reload <service_name> |
sudo systemctl reload nginx |
| 启用开机自启 | sudo systemctl enable <service_name> |
sudo systemctl enable nginx |
| 禁用开机自启 | sudo systemctl disable <service_name> |
sudo systemctl disable nginx |
systemctl 带 --user 和不带 --user 的主要区别
| 特性 | 不带 --user(系统模式) |
带 --user(用户模式) |
|---|---|---|
| 服务范围 | 系统全局服务,所有用户共享 | 仅当前登录用户自己的服务 |
| 运行权限 | 需要 root 权限(大部分操作需 sudo) |
普通用户权限(无需 sudo) |
| 服务配置文件位置 | /etc/systemd/system/ /usr/lib/systemd/system/ |
~/.config/systemd/user/ /etc/systemd/user/ /usr/lib/systemd/user/ |
| 进程管理 | 由 PID 1(systemd 进程)直接管理 | 由当前用户下的 systemd --user 进程管理(该进程是用户登录后由 user@<uid>.service 启动的) |
| 服务生命周期 | 随系统启动/关机而启动/停止 | 随用户登录/登出而启动/停止(可通过 lingering 让服务在用户未登录时也运行) |
| 环境变量 | 继承系统环境(如 PATH 由系统配置决定) |
继承用户登录时的环境(如 .bashrc、.profile 中设置的环境变量) |
| 默认目标 | multi-user.target、graphical.target 等系统目标 |
default.target(用户会话默认目标) |
| 常用命令示例 | sudo systemctl start sshd sudo systemctl enable docker |
systemctl --user start mpd systemctl --user enable syncthing |
**journalctl**查看服务输入的日志
使用 journalctl 查看日志 :这是最直接的方法,尤其是 journalctl -u <service-name> 和 journalctl -u <service-name> -f 命令。
# 查看全部日志(可能非常多,建议加过滤)
journalctl
# 实时跟踪新日志(同 tail -f)
journalctl -f
# 查看本次启动后的所有日志
journalctl -b
# 查看指定 systemd 服务的日志(如 ssh、docker、nginx)
journalctl -u ssh.service
journalctl -u docker
# 查看特定优先级的日志(err=错误, warning=警告, info=信息等)
journalctl -p err
# 查看最近 10 分钟内的日志
journalctl --since "10 minutes ago"
# 查看两个时间点之间的日志
journalctl --since "2026-05-21 10:00:00" --until "2026-05-21 12:00:00"
# 查看内核日志(等同于 dmesg 但更丰富)
journalctl -k
# 查看某个 PID 产生的日志
journalctl _PID=1234
# 显示并跟随特定服务的日志
journalctl -u myapp.service -f
# 查看上一次启动的日志(当本次启动有问题时非常有用)
journalctl -b -1
# 按 JSON 格式输出(便于脚本处理)
journalctl -o json-pretty
//查看服务日志并且过滤与usb有关系信息
journalctl -f | grep usb
- 注意 :大多数操作需要
sudo提升权限。
.service 文件格式
.service 文件是 systemd 中最常用的单元类型,用于定义和管理一个守护进程或应用程序。本文介绍其基本结构、主要指令及示例。
文件位置与优先级
| 作用域 | 路径 | 说明 |
|---|---|---|
| 系统服务 | /etc/systemd/system/ |
管理员自定义,优先级最高 |
| 系统服务 | /run/systemd/system/ |
运行时临时文件 |
| 系统服务 | /usr/lib/systemd/system/ |
软件包安装的默认服务 |
| 用户服务 | ~/.config/systemd/user/ |
用户自定义,优先级最高 |
| 用户服务 | /etc/systemd/user/ |
全系统用户服务覆盖 |
| 用户服务 | /usr/lib/systemd/user/ |
软件包安装的默认用户服务 |
文件基本结构
一个 .service 文件由多个节(section)组成,每节包含若干键值对。
ini
[Unit]
描述和依赖关系
[Service]
服务启动、运行和停止方式
[Install]
如何安装(开机自启等)
1、[Unit] 节 -- 元信息与依赖
| 指令 | 说明 | 示例 |
|---|---|---|
Description= |
服务的简短描述 | Description=My Web Server |
Documentation= |
文档链接(man、http 等) | Documentation=man:sshd(8) |
After= |
在此所列单元之后启动(不强制依赖) | After=network.target |
Before= |
在此所列单元之前启动 | Before=shutdown.target |
Requires= |
强依赖,本单元启动时这些单元必须启动成功,否则本单元失败 | Requires=docker.service |
Wants= |
弱依赖,仅尝试启动这些单元,失败不影响本单元 | Wants=network-online.target |
Conflicts= |
冲突,本单元启动时会停止所列单元 | Conflicts=shutdown.target |
Condition... / Assert... |
条件检查(文件存在、内核参数等) | ConditionFileIsExecutable=/usr/bin/myapp |
2、[Service] 节 -- 核心运行参数
1). 服务类型 (Type=)
| 类型 | 说明 |
|---|---|
simple(默认) |
主进程由 ExecStart 启动,立即认为启动成功(不 fork) |
forking |
主进程会 fork 一次后退出,systemd 等待原进程退出后认为启动成功(常用于传统 daemon) |
oneshot |
一次性任务,启动后立即退出,常与 RemainAfterExit=yes 配合 |
dbus |
进程会在 D-Bus 上注册名称,需要 BusName= |
notify |
进程会在就绪后通过 sd_notify() 发送通知,需要 NotifyAccess= |
idle |
在无其他活动任务时才运行(避免占用 CPU) |
2). 启动与停止命令
| 指令 | 说明 | 示例 |
|---|---|---|
ExecStart= |
启动服务时执行的命令(必须绝对路径) | ExecStart=/usr/bin/python3 /opt/app.py |
ExecStartPre= |
启动前执行的准备命令 | ExecStartPre=/bin/mkdir -p /var/log/myapp |
ExecStartPost= |
启动后执行的命令 | -- |
ExecReload= |
重载服务配置的命令 | ExecReload=/bin/kill -HUP $MAINPID |
ExecStop= |
停止服务的命令 | ExecStop=/bin/kill -TERM $MAINPID |
ExecStopPost= |
停止后执行的命令 | -- |
RestartSec= |
自动重启前的等待秒数 | RestartSec=5s |
TimeoutStartSec= |
启动超时时间 | TimeoutStartSec=30s |
TimeoutStopSec= |
停止超时时间 | TimeoutStopSec=10s |
3). 重启策略 (Restart=)
| 值 | 含义 |
|---|---|
no(默认) |
不自动重启 |
on-success |
仅当退出码为 0 时重启 |
on-failure |
非零退出码、超时、或被信号终止时重启 |
on-abnormal |
信号终止、超时等异常时重启 |
on-abort |
因未捕获信号崩溃时重启 |
always |
总是重启(包括正常退出) |
4). 运行环境与权限
| 指令 | 说明 |
|---|---|
User= |
运行服务的用户名(推荐非 root) |
Group= |
运行服务的用户组 |
WorkingDirectory= |
工作目录 |
Environment= |
设置环境变量,如 Environment=FOO=bar |
EnvironmentFile= |
从文件加载环境变量(每行 KEY=value) |
UMask= |
文件创建掩码,如 UMask=0022 |
Nice= |
调度优先级(-20 最高,19 最低) |
LimitNOFILE= |
最大打开文件数 |
LimitMEMLOCK= |
锁定的内存上限 |
PrivateTmp= |
使用私有 /tmp 和 /var/tmp(默认 false) |
NoNewPrivileges= |
禁止进程获得新权限(推荐 true) |
ProtectSystem= |
保护系统目录(/usr, /boot),可选 full/strict |
ReadOnlyPaths= |
设置只读路径 |
ReadWritePaths= |
设置可写路径(与只读冲突时优先) |
5). 进程管理
| 指令 | 说明 |
|---|---|
PIDFile= |
用于 Type=forking 时指定 PID 文件路径 |
RemainAfterExit= |
Type=oneshot 时使用,退出后仍视为 active |
KillMode= |
如何杀死进程:control-group(默认,杀死全部)、process(仅主进程)、mixed、none |
KillSignal= |
发送的终止信号,默认 SIGTERM |
Restart= |
见上文重启策略 |
RestartPreventExitStatus= |
遇到这些退出码时禁止重启 |
SuccessExitStatus= |
视为正常退出的额外退出码/信号 |
3、[Install] 节 -- 安装/启用配置
| 指令 | 说明 | 示例 |
|---|---|---|
WantedBy= |
指定由哪个目标(target)所需(通常是 multi-user.target 或 default.target) |
WantedBy=multi-user.target |
RequiredBy= |
强依赖目标 | -- |
Also= |
启用/禁用本服务时,同时启用/禁用的其他单元 | -- |
Alias= |
为单元设置别名 | Alias=ssh.service |
注意 :[Install] 节只在执行 systemctl enable/disable 时使用,不会影响运行时状态。
4、完整示例
示例1:简单系统服务(Type=simple)
ini
[Unit]
Description=My Custom App
After=network.target
[Service]
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.ini
Restart=on-failure
RestartSec=5s
User=nobody
Group=nogroup
WorkingDirectory=/var/lib/myapp
Environment=ENV=production
PrivateTmp=yes
NoNewPrivileges=yes
[Install]
WantedBy=multi-user.target
示例2:一次性任务(Type=oneshot)
ini
[Unit]
Description=Cleanup Temp Files
[Service]
Type=oneshot
ExecStart=/usr/local/bin/cleanup.sh
RemainAfterExit=no
[Install]
WantedBy=multi-user.target
示例3:传统 daemon(Type=forking)
ini
[Unit]
Description=Legacy Daemon
After=network.target
[Service]
Type=forking
ExecStart=/usr/sbin/legacyd -d
PIDFile=/run/legacyd.pid
User=legacy
Restart=on-failure
[Install]
WantedBy=multi-user.target
示例4:用户服务(~/.config/systemd/user/myapp.service)
ini
[Unit]
Description=User-level Syncthing
[Service]
ExecStart=/usr/bin/syncthing serve --no-browser
Restart=on-failure
RestartSec=10
[Install]
WantedBy=default.target
用户服务启用:systemctl --user enable myapp.service
并可通过 loginctl enable-linger $USER 使其在未登录时也运行。
5、常用调试命令
| 命令 | 用途 |
|---|---|
systemctl cat service_name |
查看合并后的完整单元文件 |
systemctl show service_name |
显示当前加载的配置(含默认值) |
systemctl edit --full service_name |
编辑或覆盖服务文件 |
systemctl edit service_name |
创建 drop-in 片段,不覆盖原文件 |
systemctl daemon-reload |
重新加载所有单元文件(修改后必须执行) |
6、注意事项
-
绝对路径 :
Exec*中的命令必须使用绝对路径,除非指定$PATH环境(但建议绝对路径)。 -
命令行语法 :空格和特殊字符需适当转义(可用
\或引号),更推荐使用多参数形式:ExecStart=/bin/bash -c "echo hello"。 -
环境变量继承 :服务默认继承 systemd 的干净环境,不会自动继承用户 shell 环境;需要显式设置
Environment或EnvironmentFile。 -
服务依赖循环 :避免
After和Before形成循环,systemd 会检测并报错。 -
修改后生效 :任何
.service文件改动后都需执行systemctl daemon-reload,然后systemctl restart name.service。
在 Qt 中调用 Linux 命令
同样可以使用 QProcess 来执行 systemctl 命令。
核心代码示例:
cpp
#include <QProcess>
void startService(const QString &serviceName)
{
// 使用 systemctl 启动服务
QProcess::startDetached("sudo", {"systemctl", "start", serviceName});
}
void stopService(const QString &serviceName)
{
QProcess::startDetached("sudo", {"systemctl", "stop", serviceName});
}
// 使用示例
int main()
{
const QString serviceName = "nginx";
startService(serviceName);
// stopService(serviceName);
return 0;
}
- 权限处理 :由于
systemctl通常需要sudo,这可能会要求用户输入密码。你可以考虑通过配置sudoers文件来允许特定命令免密执行,以避免交互。
示例(实现软件升级功能)
实现软件升级功能。升级功能部署流程:
一、升级守护服务配置
1、在 /etc/systemd/system/ 目录下创建一个服务文件update-daemon.service
[Unit]
Description=Software Upgrade Daemon
After=multi-user.target
[Service]
# 可执行文件的完整路径(建议用绝对路径)
WorkingDirectory=/home/dq
Environment="LD_LIBRARY_PATH=/home/dq:/home/dq/Qt-5.15.2-arm/lib:/usr/local/cuda/lib64:/usr/lib/aarch64-linux-gnu"
# 将 sleep 5 改为 sleep 1
ExecStart=/bin/bash -c "sleep 10 && /home/dq/upgrade-daemon"
# 重启策略
Restart=always
#减少重启等待时间
RestartSec=5
# 环境变量(如果需要,比如 DISPLAY 或 QT_QPA_PLATFORM)
# Environment=DISPLAY=:0
# Environment=QT_QPA_PLATFORM=wayland # 或 xcb,根据你的系统
# 日志输出(可选,方便调试)
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=default.target
2、将upgrade-daemon可执行程序拷贝到/home/dq/目录
服务中对应的程序(升级守护进程)
updatedaemon.h
#ifndef UPDATEDAEMON_H
#define UPDATEDAEMON_H
#include <QObject>
#include <QLocalServer>
#include <QString>
class UpdateDaemon : public QObject
{
Q_OBJECT
public:
explicit UpdateDaemon(QObject *parent = nullptr);
~UpdateDaemon();
private slots:
void onNewConnection();
private:
void performUpdate(bool success, QString ouputZipFilePath, QString storeFilePath);
QLocalServer *server;
QString socketPath;
bool m_success;
QString m_ouputZipFilePath;
QString m_storeFilePath;
};
#endif // UPDATEDAEMON_H
updatedaemon.cpp
bash
#include "updatedaemon.h"
#include <QLocalSocket>
#include <QProcess>
#include <QThread>
#include <QDebug>
#include <QDateTime>
#include <QDir>
#include <QFileInfo>
#include <QTimer>
#include <QCoreApplication>
#include <QJsonDocument>
#include <QJsonObject>
UpdateDaemon::UpdateDaemon(QObject *parent)
: QObject(parent), socketPath("/tmp/update_trigger.sock")
{
m_success = false;
m_ouputZipFilePath = "";
m_storeFilePath = "";
QLocalServer::removeServer(socketPath);
server = new QLocalServer(this);
// 只允许 root 用户连接(守护进程以 root 运行)
//server->setSocketOptions(QLocalServer::UserAccessOption);
// 允许任何用户连接
server->setSocketOptions(QLocalServer::WorldAccessOption);
if (!server->listen(socketPath)) {
qCritical() << "无法监听 socket:" << server->errorString();
QCoreApplication::exit(1);
}
connect(server, &QLocalServer::newConnection, this, &UpdateDaemon::onNewConnection);
qDebug() << "升级守护进程已启动,监听:" << socketPath;
}
UpdateDaemon::~UpdateDaemon()
{
QLocalServer::removeServer(socketPath);
}
void UpdateDaemon::onNewConnection()
{
QLocalSocket *client = server->nextPendingConnection();
if (!client) return;
m_success = false;
m_ouputZipFilePath = "";
m_storeFilePath = "";
// 读取客户端数据
connect(client, &QLocalSocket::readyRead, [this, client]() {
QByteArray jsonData = client->readAll();
try{
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error);
if (error.error == QJsonParseError::NoError) {
if (doc.isObject()) {
QJsonObject obj = doc.object();
if("upgrade" == obj["action"].toString()){
m_ouputZipFilePath = obj["zipFilePath"].toString();
m_storeFilePath = obj["unzipStorePath"].toString();
m_success = true;
}
else{
qDebug() << "action:" << obj["action"].toString();
}
}
else {
qDebug() << "JSON 不是一个对象.";
}
}
else {
qDebug() << "解析失败:" << error.errorString();
}
}
catch(...){
qDebug() << "解析异常";
}
qDebug() << "收到升级触发信号,开始执行升级流程...";
QJsonObject jsonResponse;
jsonResponse["action"] = "upgrade response";
jsonResponse["errorCode"] = 0;
// 转换为 QJsonDocument
QJsonDocument doc(jsonResponse);
// 转换为字节数组(默认紧凑格式)
QByteArray jsonResponseData = doc.toJson();
// 转为 QString
QString jsonResponseString = QString::fromUtf8(jsonResponseData);
client->write(jsonResponseString.toUtf8());
client->flush();
QTimer::singleShot(100, client, &QLocalSocket::disconnectFromServer);
performUpdate(m_success, m_ouputZipFilePath, m_storeFilePath); // 执行升级(最终会重启系统)
});
connect(client, &QLocalSocket::disconnected, client, &QLocalSocket::deleteLater);
}
void UpdateDaemon::performUpdate(bool success, QString ouputZipFilePath, QString storeFilePath)
{
if(!success || ouputZipFilePath.isEmpty() || storeFilePath.isEmpty()){
return;
}
// 停止进程
{
QString stopFilePath = QDir(storeFilePath).filePath("stop.sh");
if(QFile::exists(stopFilePath )){
// 获取当前权限
QFile::Permissions perms = QFile::permissions(stopFilePath );
// 添加所有者执行权限(必要时可加 Group/Other)
perms |= QFile::ExeUser; // 用户执行
// perms |= QFile::ExeGroup; // 组执行(可选)
// perms |= QFile::ExeOther; // 其他用户执行(可选)
if (!QFile::setPermissions(stopFilePath , perms)) {
qWarning() << "Failed to set executable permission on" << stopFilePath ;
}
QProcess processStop;
processStop.start("/bin/bash", QStringList() << stopFilePath);
// 等待脚本执行完成(最多等待 300 秒)
if (processStop.waitForFinished(300000)) {
QByteArray output = processStop.readAllStandardOutput();
QByteArray error = processStop.readAllStandardError();
qDebug() << "Output:" << output;
qDebug() << "Error:" << error;
qDebug() << "Exit code:" << processStop.exitCode();
}
else {
qDebug() << "stop Process timeout or error:" << processStop.errorString();
}
}
else{
qDebug() << "停止进程 dataCollect...";
QProcess::execute("pkill", {"-f", "dataCollect"});
QThread::sleep(2);
QProcess check;
check.start("pgrep", {"-f", "dataCollect"});
check.waitForFinished();
if (check.exitCode() == 0) {
qDebug() << "进程未响应,强制终止";
QProcess::execute("pkill", {"-9", "-f", "dataCollect"});
QThread::sleep(1);
}
}
}
// 备份指定文件和目录
qDebug() << "备份指定文件和目录...";
QString backupFilePath = QDir(storeFilePath).filePath("backup.sh");
if(QFile::exists(backupFilePath)){
// 获取当前权限
QFile::Permissions perms = QFile::permissions(backupFilePath);
// 添加所有者执行权限(必要时可加 Group/Other)
perms |= QFile::ExeUser; // 用户执行
// perms |= QFile::ExeGroup; // 组执行(可选)
// perms |= QFile::ExeOther; // 其他用户执行(可选)
if (!QFile::setPermissions(backupFilePath, perms)) {
qWarning() << "Failed to set executable permission on" << backupFilePath;
}
}
QProcess processBackup;
processBackup.start("/bin/bash", QStringList() << backupFilePath);
// 等待脚本执行完成(最多等待 300 秒)
if (processBackup.waitForFinished(300000)) {
QByteArray output = processBackup.readAllStandardOutput();
QByteArray error = processBackup.readAllStandardError();
qDebug() << "Output:" << output;
qDebug() << "Error:" << error;
qDebug() << "Exit code:" << processBackup.exitCode();
} else {
qDebug() << "Backup Process timeout or error:" << processBackup.errorString();
}
//可选命令(支持多个)
{
//optional-commands
QString path = QDir(storeFilePath).filePath("optional-commands");
QDir dir(path);
dir.setFilter(QDir::Files); // 只显示文件
dir.setNameFilters(QStringList() << "*.sh"); // 可选:只筛选 .sh 文件
dir.setSorting(QDir::Name); // 按文件名升序排序
QStringList shFiles = dir.entryList(); // 返回已排序的文件名列表
for (const QString &file : shFiles) {
QString cmdFullPath = dir.absoluteFilePath(file);
qDebug() << cmdFullPath;
if(QFile::exists(cmdFullPath)){
// 获取当前权限
QFile::Permissions perms = QFile::permissions(cmdFullPath);
// 添加所有者执行权限(必要时可加 Group/Other)
perms |= QFile::ExeUser; // 用户执行
// perms |= QFile::ExeGroup; // 组执行(可选)
// perms |= QFile::ExeOther; // 其他用户执行(可选)
if (!QFile::setPermissions(cmdFullPath, perms)) {
qWarning() << "Failed to set executable permission on" << cmdFullPath;
}
qInfo() <<"start execute " << cmdFullPath;
QProcess processBackup;
processBackup.start("/bin/bash", QStringList() << cmdFullPath);
// 等待脚本执行完成(最多等待 300 秒)
if (processBackup.waitForFinished(300000)) {
QByteArray output = processBackup.readAllStandardOutput();
QByteArray error = processBackup.readAllStandardError();
qDebug() << "Output:" << output;
qDebug() << "Error:" << error;
qDebug() << "Exit code:" << processBackup.exitCode();
} else {
qDebug() << "Backup Process timeout or error:" << processBackup.errorString();
}
}
}
}
// 替换文件
// copy文件,添加所有者执行权限
QString cpFilePath = QDir(storeFilePath).filePath("upgrade-copy.sh");
if(!QFile::exists(cpFilePath)){
return;
}
// 获取当前权限
QFile::Permissions perms = QFile::permissions(cpFilePath);
// 添加所有者执行权限(必要时可加 Group/Other)
perms |= QFile::ExeUser; // 用户执行
// perms |= QFile::ExeGroup; // 组执行(可选)
// perms |= QFile::ExeOther; // 其他用户执行(可选)
if (!QFile::setPermissions(cpFilePath, perms)) {
qWarning() << "Failed to set executable permission on" << cpFilePath;
}
qDebug() << "替换新文件...";
QProcess processCopy;
processCopy.start("/bin/bash", QStringList() << cpFilePath);
// 等待脚本执行完成(最多等待 300 秒)
if (processCopy.waitForFinished(300000)) {
QByteArray output = processCopy.readAllStandardOutput();
QByteArray error = processCopy.readAllStandardError();
qDebug() << "Output:" << output;
qDebug() << "Error:" << error;
qDebug() << "Exit code:" << processCopy.exitCode();
} else {
qDebug() << "Copy Process timeout or error:" << processCopy.errorString();
}
// 重启系统
qDebug() << "升级完成,3秒后重启...";
QThread::sleep(3);
QProcess::execute("reboot");
}
main.cpp
#include <QCoreApplication>
#include "updatedaemon.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// Set up code that uses the Qt event loop here.
// Call a.quit() or a.exit() to quit the application.
// A not very useful example would be including
// #include <QTimer>
// near the top of the file and calling
// QTimer::singleShot(5000, &a, &QCoreApplication::quit);
// which quits the application after 5 seconds.
// If you do not need a running Qt event loop, remove the call
// to a.exec() or use the Non-Qt Plain C++ Application template.
UpdateDaemon daemon;
return a.exec();
}
客户端(触发升级动作,发送到守护)
cpp
QString cpFilePath = QDir(storeFilePath).filePath("upgrade-copy.sh");
if(QFile::exists(cpFilePath)){
//triger upgrade
QLocalSocket socket;
socket.connectToServer("/tmp/update_trigger.sock");
if (!socket.waitForConnected(3000)) {
qCritical() << "无法连接守护进程:" << socket.errorString();
QFile::remove(ouputZipFilePath);
return ;
}
QJsonObject json;
json["action"] = "upgrade";
json["zipFilePath"] = ouputZipFilePath;
json["unzipStorePath"] = storeFilePath;
// 转换为 QJsonDocument
QJsonDocument doc(json);
// 转换为字节数组(默认紧凑格式)
QByteArray jsonData = doc.toJson();
// 转为 QString
QString jsonString = QString::fromUtf8(jsonData);
socket.write(jsonString.toUtf8());
socket.flush();
qDebug()<<"send data:" << json;
if (socket.waitForReadyRead(2000)) {
qDebug() << "响应:" << socket.readAll().trimmed();
} else {
qDebug() << "未收到响应,但触发已发送";
}
socket.disconnectFromServer();
qDebug()<<"triger upgrade notify";
}
else{
qDebug() << "没有复制脚本文件.";
QFile::remove(ouputZipFilePath);
}
扫描加载u盘中固定目录(比如:upgrade-files)中是否存在升级文件包。存在并解析升级包信息。
3、将stop_and_backup.sh文件放到升级包upgrade-files目录下。
bash
#!/bin/bash
# ============================================
# stop_and_backup.sh
# 功能: 1) 停止主进程 dataCollect
# 2) 备份指定的文件和文件夹到备份目录
# ============================================
set -e
# ---------- 配置区域 ----------
PROCESS_NAME="dataCollect" # 主进程名称
# 备份源列表:可以写文件或目录的绝对路径
BACKUP_SOURCES=(
"/home/dq/dataCollect"
"/home/dq/Config.ini"
"/home/dq/libHCUSBSDK.so"
"/home/dq/hikvision_thermal/" # 整个目录
)
# 备份目标根目录(会在下面创建按时间戳的子目录)
BACKUP_BASE="/mnt/dq_root/asmq_backup"
# -----------------------------
if [ "$EUID" -ne 0 ]; then
echo "请使用 root 权限运行此脚本"
exit 1
fi
# 1. 停止主进程
echo "[1/2] 停止进程: $PROCESS_NAME"
# 尝试优雅终止 (SIGTERM)
pkill -f "$PROCESS_NAME" && echo "已发送 SIGTERM" || echo "未找到进程 $PROCESS_NAME"
sleep 3
# 检查是否还在运行,若有则强制杀死 (SIGKILL)
if pgrep -f "$PROCESS_NAME" > /dev/null; then
echo "进程未响应,强制终止"
pkill -9 -f "$PROCESS_NAME" || true
sleep 1
fi
# 最终确认
if pgrep -f "$PROCESS_NAME" > /dev/null; then
echo "错误: 无法停止进程 $PROCESS_NAME"
exit 1
fi
echo "进程已停止"
# 2. 备份指定文件和文件夹
echo "[2/2] 开始备份指定内容到 $BACKUP_BASE"
# 创建带时间戳的备份目录
#TIMESTAMP=$(date +%Y%m%d_%H%M%S)
#BACKUP_DIR="$BACKUP_BASE/$TIMESTAMP"
BACKUP_DIR="$BACKUP_BASE/asmq_stable"
[ -d "$BACKUP_DIR" ] && rm -rf "$BACKUP_DIR"
mkdir -p "$BACKUP_DIR"
# 遍历备份源列表,逐个复制
for src in "${BACKUP_SOURCES[@]}"; do
if [ -e "$src" ]; then
# 目标路径:保留源路径的最后一级名称(如果是目录则复制整个目录)
# 例如 /opt/myapp/config/settings.ini -> $BACKUP_DIR/settings.ini
# 例如 /opt/myapp/models/ -> $BACKUP_DIR/models/
base_name=$(basename "$src")
dest="$BACKUP_DIR/$base_name"
echo " 复制 $src -> $dest"
# 使用 cp -a 保留权限、时间戳等
cp -a "$src" "$dest"
else
echo " 警告: 源文件/目录不存在,跳过: $src"
fi
done
echo "备份完成,所有内容已保存到: $BACKUP_DIR"
ls -lh "$BACKUP_DIR"
4、配置服务为开机自启
sudo systemctl enable update-daemon
二、配置udev规则,以便实时检测u盘插入/卸载
将99-usb-automount.rules文件拷贝到/etc/udev/rules.d目录中。
bash
ACTION=="add", SUBSYSTEM=="block", ENV{ID_BUS}=="usb", ENV{ID_FS_USAGE}=="filesystem", \
RUN+="/usr/bin/systemd-mount --no-block --collect --options=uid=1000,gid=1000,umask=000 /dev/%k /mnt/usb"
ACTION=="remove", SUBSYSTEM=="block", ENV{ID_BUS}=="usb", ENV{ID_FS_USAGE}=="filesystem", \
RUN+="/usr/bin/systemd-mount --no-block --umount /mnt/usb"
三、制作升级包
利用UpgradeTool工具制作升级包(提前存放文件和文件夹,以及upgrade-copy.sh脚本)
bash
#!/bin/bash
workdir=/mnt/dq_root/upgrade_temp/upgradefiles/
# 复制文件 netmanager.cpp 到 /home/dq/ 目录
cp -f "$workdir"/netmanager.cpp /home/dq/
# 替换文件 netmanager.h(目标已存在则覆盖)
cp -f "$workdir"/netmanager.h /home/dq/netmanager.h
cp -f "$workdir"/dataCollect/home/dq/dataCollect
# 复制 subdir 文件夹及其所有内容到 /home/dq/ 下
cp -rf "$workdir"/subdir/ /home/dq/
四、升级包存放到u盘固定目录中
将做好的升级包文件(比如:upgrade-20260421161205.bin)拷贝到u盘根目录下upgrade-files目录中。