一、QT实现后台服务
QT使用开源项目QtService实现后台服务,支持windows和linux跨平台使用,下载地址:
https://github.com/qtproject/qt-solutions/tree/master/qtservice
QT新建控制台程序"Qt Console Application"
,把 qtservice 文件夹拷贝到QT工程目录,在pro文件
添加:
include(qtservice/src/qtservice.pri)
添加新文件:ubuntuservice.h
和ubuntuservice.cpp
,服务功能主要由重写的几个虚函数完成:start、stop、pause、resume
,在启动、停止、暂停、重启服务
时会自动调用这些虚函数。
修改 main.cpp
#include "ubuntuservice.h"
int main(int argc, char *argv[])
{
UbuntuService service(argc, argv);
return service.exec();
}
ubuntuservice.h
内容如下:
#ifndef UBUNTUSERVICE_H
#define UBUNTUSERVICE_H
#include <QCoreApplication>
#include "qtservice.h"
class UbuntuService : public QObject ,public QtService<QCoreApplication>
{
Q_OBJECT
public:
explicit UbuntuService(int argc, char **argv);
~UbuntuService();
protected:
void start()override; //开始服务
void stop()override; //停止服务
void pause()override; //暂停服务
void resume()override; //重启服务
signals:
private:
};
#endif // UBUNTUSERVICE_H
ubuntuservice.cpp
内容如下:
#include "ubuntuservice.h"
#include <QDebug>
UbuntuService::UbuntuService(int argc, char **argv)
: QtService<QCoreApplication>(argc, argv, "QtDbusSensorService")
{
setServiceDescription("QtDbusSensorService");
setServiceFlags(QtServiceBase::CanBeSuspended);
}
UbuntuService::~UbuntuService()
{
}
///开始服务
void UbuntuService::start()
{
qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<"ubuntuService";
}
///停止服务
void UbuntuService::stop()
{
qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<"ubuntuService";
}
///暂停服务
void UbuntuService::pause()
{
qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<"ubuntuService";
}
///暂停服务
void UbuntuService::resume()
{
qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<"ubuntuService";
}
服务启动后会自动进入到 start()
这个虚函数中,其他几个函数同理,那么在实际项目中, 只需要将自己要执行的内容放到 start()
中就可以了。
二、systemd使用说明
Systemd
是 Linux 的系统和服务的管理器,systemd 即为 system daemon ,是linux下的一种init
软件,是一种系统守护进程,其提供更优秀的框架以表示系统服务间的依赖关系,并依此实现系统初始化时服务的并行启动,同时达到降低Shell的系统开销的效果。
Systemd
管理 unit
,unit代表系统资源和服务,unit有很多种类型,我们使用其中的service
:系统上的一项服务,包括启动、重新启动和停止服务。
1、systemctl常用命令
systemd对应的进程管理命令是systemctl
。
systemctl start my-demo.service#启动
systemctl stop my-demo.service#停止
systemctl restart my-demo.service#重启
systemctl status my-demo.service#查看服务状态,包含日志
systemctl list-unit-files#列出所有可用单元
systemctl list-units#列出所有运行中单元
systemctl --failed#列出所有失败单元
systemctl kill network.service #使用systemctl命令杀死服务
systemctl daemon-reload#修改service文件后,重新加载unit配置
systemctl enable my-demo.service #设置开机启动
systemctl disable my-demo.service #取消开机启动
systemctl is-enabled my-demo.service #查看是否开机启动
2、unit配置文件
unit
配置文件主要分为三部分:[Unit]、[Service] 和 [Install]
[Unit]部分详解
此部分中使用的参数不仅限于 service 类型的 unit,对其它类型 unit 也是通用的,有关这些参数及其说明的完整列表,可运行命令 man systemd.unit 或访问 systemd.unit 中文手册
注意:在 [unit]
块中的每个参数后都可以指定一个以空格分隔
的列表
- Description :当前 unit 的描述
- Documentation :文档地址,仅接受类型为:http://、https://、file:、info:、man: 的URI
- Requires :表示本 unit 与其它 unit 之间存在强依赖关系,如果本 unit 被激活,此处列出的 unit 也会被激活,如果其中一个依赖的 unit 无法激活,systemd 都不会启动本 unit
- Wants :与 Requires 类似,区别在于如果依赖的 unit 启动失败,不影响本 unit 的继续运行
- After :表示本 unit 应该在某服务之后启动,选项可参考 systemd.special 中文手册
- Before :表示本 unit 应该在某服务之前启动,After和Before 字段只涉及启动顺序,不涉及依赖关系
- BindsTo :与 Requires 类似,当指定的 unit 停止时,也会导致本 unit 停止
- PartOf :与 Requires 类似,当指定的 unit 停止或重启时,也会导致本 unit 停止或重启
- Conflicts:如果指定的 unit 正在运行,将导致本 unit 无法运行
- OnFailure :当本 unit 进入故障状态时,激活指定的 unit
[Service]部分详解
service
专有参数
只有 service 类型的 unit 才有这些参数,参数完整列表请访问 systemd.service
中文手册 或 systemd.service
- simple(默认值) :服务为主进程启动,systemd 认为该服务将立即启动,服务进程不会 fork ,如果该服务要启动其他服务,则不要使用此类型启动,除非该服务是 socket 激活型。
- forking :服务将以fork分叉的方式启动,此时父进程将会退出,子进程将成为主进程。systemd 认为当该服务进程 fork,且父进程退出后服务启动成功。对于常规的守护进程(daemon)除非你确定此启动方式无法满足需求,否则使用此类型启动即可。使用此启动类型应同时指定 PIDFile= 以便 systemd 能够跟踪服务的主进程
- oneshot :类似于simple,但只执行一次,Systemd会等它执行完,才启动其它服务。这一选项适用于只执行一项任务,随后立即退出的服务。可能需要同时设置 RemainAfterExit=yes 使得 systemd 在服务进程退出之后仍然认为服务处于激活状态
- dbus :类似于simple,但会等待D-Bus信号后启动。当指定的 BusName 出现在DBus系统总线上时,systemd 认为服务就绪。
- notify :类似于simple,启动完毕后会通知 Systemd ,然后继续往下执行
- idle :类似于simple,但是要等到其它所有任务都执行完,才会启动该服务
[Install]部分详解
[install]
定义了 unit 的安装信息,此部分配置仅在 systemctl enable
或 systemctl disable
时使用,在 unit 运行时不解释此部分,相当于是配置如何开机启动
此部分中使用的参数不仅限于service类型的unit,对其它类型的 unit也是通用的,有关这些参数及其说明的完整列表,可运行命令 man systemd.unit 或访问 systemd.unit 中文手册
- Alias :当前 unit 可用于启动的别名,此处列出的名称必须与服务文件名具有相同的后缀(即类型),在执行
systemctl enable
时将创建从这些名称到 unit 文件名的符号链接 - RequiredBy :表示该服务所在的Target,它的值是一个或多个Target,当
systemctl enable
时 unit 符号链接会放入/etc/systemd/system
目录下面以 Target名 + .required 后缀构成的子目录中 - WantedBy :表示该服务所在的Target,它的值是一个或多个Target,当前
systemctl enable
时 unit符号链接会放入/etc/systemd/system
目录下面以 Target名 + .wants 后缀构成的子目录中 - Also :当
systemctl enable
或systemctl disable
时会同时 enable 和 disable 的其它 unit 列表
三、systemd管理Qt写的后台程序
新建一个服务 unit
文件
cd /usr/lib/systemd/system/
sudo vim test_systemd.service
编写unit
文件
[Unit]
Description=test_systemd Service
[Service]
Type=forking
ExecStart=/bin/bash /home/fl/release/test_qtservice.sh
ExecStop=/bin/bash /home/fl/release/test_qtservice_quit.sh
StandardOutput=syslog
StandardError=inherit
[Install]
WantedBy=multi-user.target
将服务注册到系统
systemctl enable test_systemd.service#将服务注册到系统
Created symlink /etc/systemd/system/multi-user.target.wants/test_systemd.service → /lib/systemd/system/test_systemd.service.
cd /etc/systemd/system/multi-user.target.wants/#上一步注册是在这个文件夹创建软链接
ExecStart
和ExecStop
是两个脚本文件,分别对应启动服务和停止服务
test_qtservice.sh
#!/bin/sh
workdir=$(cd $(dirname $0); pwd)
export LD_LIBRARY_PATH=$workdir
cd $workdir
./test_qtservice -s
test_qtservice_quit.sh
#!/bin/sh
workdir=$(cd $(dirname $0); pwd)
export LD_LIBRARY_PATH=$workdir
cd $workdir
./test_qtservice -t
test_qtservice是使用QtService写的服务程序,后面跟-s
代表启动服务,-t
代表停止服务,也就是会调用QtService的start
和stop
虚函数。
其他含义:
-[i|u|e|t|p|r|c|v|h]\n"
"\t-i(nstall) [account] [password]\t: Install the service, optionally using given account and password\n"
"\t-u(ninstall)\t: Uninstall the service.\n"
"\t-e(xec)\t\t: Run as a regular application. Useful for debugging.\n"
"\t-t(erminate)\t: Stop the service.\n"
"\t-p(ause)\t: Pause the service.\n"
"\t-r(esume)\t: Resume a paused service.\n"
"\t-c(ommand) num\t: Send command code num to the service.\n"
"\t-v(ersion)\t: Print version and status information.\n"
"\t-h(elp) \t: Show this help\n"
"\tNo arguments\t: Start the service.\n"
四、测试效果
测试程序环境是基于Ubuntu的 openKylin 0.9.5
版本系统虚拟机
启动服务,并查看日志,显示服务已运行, 查看进程可以看到服务程序test_qtservice
使用 sudo systemctl stop test_systemd.service
,停止服务。
五、报错记录
systemd
服务不支持
启动界面程序,如果启动的外部程序是带界面的则会启动失败,status
日志报错:
qt.qpa.xcb: could not connect to display
is inaccessible within this context,qt报错
public QObject,public QtService //如果继承多个类,则分别使用
public
。public、private、protected三种继承方式的区别。
/下面的报错是直接运行导致,或者win系统下直接运行,建议直接构建出来即可,不直接运行的可不看
如果想直接通过QtCreator运行服务,你会看到如下报错:
The service QtServiceDemo could not
别着急,这不是代码的问题,这是因为服务的启动需要添加一个启动参数,-exec 或者更简单点 -e
如果要在Qt Creator中直接启动的话,那只需要配置一下启动参数即可,如下:
再次运行就可以了。
通常,后台服务程序都是由一个主程序去调用启动,如果是在Qt主程序中调用,可以使用QProcess完成,也是要添加启动参数才行。
在Win系统
下如果要直接双击运行的话,由于需要运行参数,所以不能直接双击打开执行程序,可以通过一个脚本调用来打开:
autorun.bat
@echo off
start QtServiceDemo.exe -exec
exit