前言

最近了解到qt-solutions这个开源项目,仔细研究一番,发现其中的QtServer项目能在Windows系统中创建系统服务,Linux/Unix系统中能作为守护进程使用,之前一直以为编写服务需要使用Windows api来实现,没想到这么简单。
本来之前就想把Aria2.exe封装成后台服务,然后内网穿透,在配和使用AriaNG Web界面访问,实现远程控制下载,迫于工作量直接放弃,
仔细研究发现QtServer库实现极其简单,于是就又尝试了一番,半天就搞定了实现Aria2.exe的系统服务,又仔细研究了下Aria2.exe的Json-Rpc访问接口。

正文导读

    • 前言
    • QtServer开源库
      • 在Qt Creator5.15.2 开发环境中使用
      • 在Visual Studio 2019 开发环境中使用
      • 功能实现
        • Start()
        • Stop()
    • Http访问Json-RPC接口
      • HTTP GET请求
      • aria2.getVersion 方法获取Aria2版本
      • aria2.forceShutdown方法强制结束Aria2程序
    • 安装服务

QtServer开源库

QtServer库:https://github.com/MliesMoT/qt-solutions/tree/master/qtservice

qt-solutions/qtservice 是 Qt 官方提供的一个跨平台服务(守护进程)开发库,用于将 Qt 应用程序转换为系统服务(Windows 服务或 Linux/Unix 守护进程)。该库属于 Qt Solutions 系列(现已归档,但仍可独立使用)。

下载源码发现,代码量也不高,其中examples包含了多个实例,
编译后使用windeployqt打包就能直接安装到控制面板->服务上,
examples 示例中的代码量也不高,甚至改改名字就能直接用的程度,
在这里插入图片描述

  • 在Qt Creator5.15.2 开发环境中使用

在Qt Creator中使用时直接拷贝src到项目目录下
再.pro文件中添加 include(src/qtservice.pri)
在引用 #include "qtservice.h" 就能直接使用了,简单好用。

  • 在Visual Studio 2019 开发环境中使用

在Visual Studio 2019 开发环境中使用就有点要老命了,可能纯属个例,当我直接使用 Qt->Improt .pri File To Project引入qtservice.pri 的时候直接一堆异常,无法解析的外部命令 浪费一段时间无果,
于是我只能先用Qt Creater编译QtServer库,在Vs中引用头文件和lib,DLL。
首先加载buildlib项目:
在这里插入图片描述
修改buildlib.pro文件中的输出目录,同时添加Lib输出,如下示例:

TEMPLATE=lib
CONFIG += qt dll qtservice-buildlib
mac:CONFIG += absolute_library_soname
win32|mac:!wince*:!win32-msvc:!macx-xcode:CONFIG += debug_and_release build_all
include(../src/qtservice.pri)
TARGET = $$QTSERVICE_LIBNAME
DESTDIR = $$QTSERVICE_LIBDIR
win32 {DLLDESTDIR = $$PWDQMAKE_DISTCLEAN += $$PWD\\$${QTSERVICE_LIBNAME}.dll
}
target.path = $$DESTDIR
INSTALLS += target
# 在构建库时定义 QT_QTSERVICE_EXPORT
DEFINES += QT_QTSERVICE_EXPORT

重新编译得到lib和DLL文件
在这里插入图片描述
在Vs中附加包含目录 src中的头文件目录 ,DLL复制到生成目录下
添加引用,完成QtServer服务的调用

#include "qtservice.h"
#pragma comment(lib, "qtServer/lib/QtSolutions_Service-head.lib")
  • 功能实现

通过继承QtService<QApplication> 实现start()stop()pause()resume()方法就实现了系统服务的启动停止暂停重启功能。

通过 setServiceDescription() 方法实现服务具体描述
如: setServiceDescription("一个启动Aria2.exe的后台服务,可通过修改aria2.conf文件重启服务来修改默认配置,默认使用RPC令牌和6800端口");
会在服务的描述信息中展示上述文本内容。

这里只需要启动和停止两个功能,就只需要实现start()和stop()两个方法就可以了。

  • Start()

使用QProcess通过命令行启动Aria2.exe程序,通过aria2.conf设置默认参数,
如果需要更改端口或值RPC令牌,直接修改aria2.conf配置文件重启服务就行了。

 QString dirs=QCoreApplication::applicationDirPath();Aria2FilePath=dirs+"/aria2c.exe";ConfFilePath=dirs+"/aria2.conf";if(!QFileInfo::exists(Aria2FilePath) || !QFileInfo::exists(ConfFilePath))return ConfLost;process=new QProcess();//重载信号,需要使用 QOverload 或 qOverload(Qt5.7+):QObject::connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),[this](int exitCode, QProcess::ExitStatus exitStatus) {if (exitStatus == QProcess::CrashExit) {q_ptr->logMessage(QString("QProcess 进程意外结束,错误码:%1 ").arg(exitCode), QtServiceBase::Error);ErrorOver();}});QString CommentStr=QString("\"%1\" --conf-path=\"%2\" \r\n").arg(Aria2FilePath).arg(ConfFilePath);process->start(CommentStr);if(!process->waitForStarted(10000)){process->terminate();if (!process->waitForFinished(1000)) {process->kill();}delete process;process=nullptr;return NG;}//! 通过RPC服务判断是否启动Aria2,没有启动就停止服务if(Lib_NetWork::NetWorkGlobal()->getVersion()=="")return NG;return OK;

使用QCoreApplication中断服务的运行,
这在服务未能正确启动时终止后续服务启动状态,非常适用。

void Aria2InteractiveServerPrivate::ErrorOver()
{QCoreApplication *app = q_ptr->application();app->quit();
}
  • Stop()

关闭服务,先使用json-Rpc接口通知Aria2.exe程序强制退出,在通过QProcess进程的强制结束,关闭服务。如果只关闭QProcess,很容易随机 出现Aria2.exe程序挂起的后台程序

//! 强制结束Lib_NetWork::NetWorkGlobal()->forceShutDown();//! 关闭 QProcessif(process!=nullptr){process->terminate();if (!process->waitForFinished(1000)) {process->kill();}delete process;process=nullptr;return NG;}

Http访问Json-RPC接口

通过 Aria2的手册 可以查询到Json-RPC接口的调用传参,
这其中需要注意的是编码格式和JSON字符串的拼接,特别是有RPC令牌和没有RPC令牌两种情况下的拼接字符串。

  • HTTP GET请求

在QT中执行HTTP请求的时候,基本都会封装使用这个方法;
使用QEventLoop堵塞事件等待HTTP请求完成,
使用QTimer 防止HTTP请求超时。

bool HttpGet(QString httpUrl,QString& Error,QByteArray& result)
{//! 超时时间QTimer * out_timer = new QTimer();QNetworkRequest request;//设置请求头---浏览器request.setHeader(QNetworkRequest::UserAgentHeader,"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36 Edg/93.0.961.38");request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");request.setUrl(QUrl(httpUrl));QNetworkAccessManager* manager = new QNetworkAccessManager();QNetworkReply *reply = manager->get(request);//! 堵塞线程QEventLoop eventLoop;//! 请求结束QObject::connect(reply, SIGNAL(finished()),&eventLoop, SLOT(quit()));QObject::connect(reply, SIGNAL(aboutToClose()),&eventLoop, SLOT(quit()));//! 线程超时QObject::connect(out_timer,&QTimer::timeout,reply,&QNetworkReply::abort);out_timer->start(5000); //等待5秒 5秒内无响应自动中断//! 堵塞线程等待响应eventLoop.exec(QEventLoop::ExcludeUserInputEvents);//! 清除计时器out_timer->stop();delete out_timer;out_timer=nullptr;result=reply->readAll();if(reply->error() != QNetworkReply::NoError){Error="Http请求异常:"+QString(reply->error());if(result!=""){//"{\"id\":\"qwer\",\"jsonrpc\":\"2.0\",\"error\":{\"code\":1,\"message\":\"Unauthorized\"}}"QJsonParseError Jsonerror;QJsonDocument resultDoc=QJsonDocument::fromJson(result,&Jsonerror);if(Jsonerror.error==QJsonParseError::NoError){Error+=",message:"+resultDoc["error"].toObject()["message"].toString();}}return false;}return true;
}
  • aria2.getVersion 方法获取Aria2版本

需要参考手册上把各个参数按照指定结构拼接成JSON字符串,
在使用.toBase64().toPercentEncoding() 转码,需要注意要不然识别不了。

QString getVersion()
{//! 如果服务启动,那么应该能获取到aria2的状态QString Error;QString Jsonrpc="http://localhost:"+rpc_listen_port+"/jsonrpc?params=";QJsonArray params;params.append("token:"+rpc_secret);QJsonObject object;object.insert("jsonrpc","2.0");object.insert("id","qwer");object.insert("method","aria2.getVersion");object.insert("params",params);QJsonDocument Document(object);QByteArray JsonStr=Document.toJson(QJsonDocument::Compact);QString Dataparams= JsonStr.toBase64().toPercentEncoding();Jsonrpc.append(Dataparams);//qDebug()<<"[Jsonrpc] "<<Jsonrpc;QByteArray result;if(!HttpGet(Jsonrpc,Error,result)){qDebug()<<Error;return "";}QString Version="";QJsonParseError Jsonerror;QJsonDocument resultDoc=QJsonDocument::fromJson(result,&Jsonerror);if(Jsonerror.error==QJsonParseError::NoError){Version =resultDoc["result"]["version"].toString();}qDebug().noquote()<<Version;return Version;
}

输出:
{"id":"qwer","jsonrpc":"2.0","result":{"enabledFeatures":["Async DNS","BitTorrent","Firefox3 Cookie","GZip","HTTPS","Message Digest","Metalink","XML-RPC","SFTP"],"version":"1.37.0"}}

  • aria2.forceShutdown方法强制结束Aria2程序

拼接JSON字符串基本都差不多一个模版,改个方法名称就能直接用

 QString Error;QString Jsonrpc="http://localhost:"+rpc_listen_port+"/jsonrpc?params=";QJsonArray params;params.append("token:"+rpc_secret);QJsonObject object;object.insert("jsonrpc","2.0");object.insert("id","qwer");object.insert("method","aria2.forceShutdown");object.insert("params",params);QJsonDocument Document(object);QByteArray JsonStr=Document.toJson(QJsonDocument::Compact);qDebug().noquote()<<"JsonStr: \n"<<JsonStr;QString Dataparams= JsonStr.toBase64().toPercentEncoding();Jsonrpc.append(Dataparams);QByteArray result;if(!HttpGet(Jsonrpc,Error,result)){qDebug()<<Error;}//    qDebug().noquote()<<"Jsonrpc: \n"<<Jsonrpc;qDebug().noquote()<<"Result: \n" <<result;return ;

安装服务

这样一来基本服务的所有功能都实现了只需要通过windeployqt 打包exe,
在管理员权限的CMD中输入命令行:

Aria2_QtServer.exe -i //! 安装服务
Aria2_QtServer.exe -u //! 卸载服务
Aria2_QtServer.exe -t //! 停止服务
Aria2_QtServer.exe -s //! 开始服务

就安装完成了:
在这里插入图片描述
使用AriaNGg访问,随便找个镜像下载:
请添加图片描述

请添加图片描述
搞定!

Aria2 支持HTTP/HTTPS 链接,FTP 链接,BitTorrent 种子文件或磁力链接,Metalink 文件, SFTP 协议,配合AriaNG的界面化使用,
一个简易版的迅雷下载就完成了!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/91377.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/91377.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/91377.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python中关于数组的常见操作

Python中关于数组的常见操作 1.创建数组 array []2.添加元素 array.append()3.访问元素 print(array[2])通过索引进行数组元素的访问 4.修改元素 array[2] 3直接对想修改的元素位置进行赋值 5.删除元素 array.remove(2) #删除元素2del array[2] #删除索引为2的元素6…

Image 和 IMU 时间戳同步

1 目录 时间戳同步介绍 时间戳同步初探 时间戳获取方式 时间戳延迟估计方法 姿态补偿 匀速模型在 Bundle Adjustment 中的应用 重投影残差 视觉特征匀速运动补偿特征坐标 重投影残差 基于特征匀速模型算法的实验结果 轨迹匀速模型 vs 特征匀速模型 时间戳同步算法扩…

创建linux端口映射连接小网

&#x1f680; 方法 1&#xff1a;在执行机上配置 SSH 服务端转发 这个做法是在 执行机上配置一个常驻 SSH 隧道&#xff0c;把大网的某个端口长期转发到小网单板的 22 端口。 &#x1f468;‍&#x1f4bb; 操作步骤 1️⃣ 在执行机上创建一个 systemd 服务 假设&#xff1a; …

了解Java21

目前还没有实操过从java8/java11直接到java17,java21。 先储备下知识点&#xff0c;写一些简单例子&#xff0c;以便后续的实操。 一些新特性&#xff08;java8之后的&#xff09; var变量 和前端js定义变量一样了&#xff0c;var搞定public static void main(String[] args) {…

【代码】基于CUDA优化的RANSAC实时激光雷达点云地面分割

基于CUDA优化的RANSAC实时激光雷达点云地面分割 摘要&#xff1a; 本文介绍了一个高性能的激光雷达&#xff08;LiDAR&#xff09;地面分割项目。该项目基于RANSAC平面估计算法&#xff0c;并通过深度CUDA并行优化&#xff0c;将核心处理时间从近100ms缩短至10ms以内&#xff…

vuex原理以及实现

vuex官方文档 Vuex是什么&#xff1f; Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化 每一个 Vuex 应用的核心就是 store&#xff08;仓库&#xff09;。“stor…

APIs案例及知识点串讲(上)

一.轮播图专题CSS代码<style>* {box-sizing: border-box;}.slider {width: 560px;height: 400px;overflow: hidden;}.slider-wrapper {width: 100%;height: 320px;}.slider-wrapper img {width: 100%;height: 100%;display: block;}.slider-footer {height: 80px;backgro…

华大单片机HC32L110烧录程序方法

1&#xff0c;安装J-flash工具 从SEGGER官网下载J-flash工具&#xff0c;地址&#xff1a;SEGGER - The Embedded Experts - Downloads - J-Link / J-Trace。按向导安装完成。 2&#xff0c;使用如下图JLINK工具SWD模式连接单片机的烧录接口&#xff08;SWDIO,SWCLK,GND&#…

LeetCode|Day15|125. 验证回文串|Python刷题笔记

LeetCode&#xff5c;Day15&#xff5c;125. 验证回文串&#xff5c;Python刷题笔记 &#x1f5d3;️ 本文属于【LeetCode 简单题百日计划】系列 &#x1f449; 点击查看系列总目录 >> &#x1f4cc; 题目简介 题号&#xff1a;125. 验证回文串 难度&#xff1a;简单 题…

项目学习笔记 display从none切换成block

跟着视频学做项目的时候&#xff0c;碰到一个多级联动列表&#xff0c;列表元素的display会从none切换成block&#xff0c;切换过程中可能导致资源渲染过多&#xff0c;从而导致卡顿问题。<div class"all-sort-list2"><div class"item" v-for&quo…

从 “洗澡难” 到 “洗得爽”:便携智能洗浴机如何重塑生活?

洗澡本应是日常生活的简单需求&#xff0c;但对于失能老人、行动不便者而言&#xff0c;却可能成为一项充满挑战甚至危险的“艰巨任务”。中国失能、半失能老年人口超过4200万&#xff0c;传统助浴方式存在搬运风险高、隐私难以保障、效率低下等问题&#xff0c;使得“洗澡难”…

鹧鸪云重构光伏发电量预测的精度标准

在当今全球能源转型的大背景下&#xff0c;光伏发电作为一种清洁、可再生的能源形式&#xff0c;正受到越来越多的关注与应用。然而&#xff0c;光伏发电量的精准预测&#xff0c;一直是行业内亟待攻克的关键难题。尤其是在面对复杂多变的气象条件、不同区域的地理环境以及设备…

每日一题(沉淀中)

文章目录 1、 实现string类的接口&#xff0c;并完成测试&#xff0c;要求利用深拷贝和深赋值实现 MyString.h #pragma once #include<iostream> class MyString { private:char* data;//储存字符串内容 public://默认构造函数MyString(const char* str nullptr);////拷…

深入浅出Kafka Producer源码解析:架构设计与编码艺术

一、Kafka Producer全景架构 1.1 核心组件交互图 #mermaid-svg-L9jc09hRQCHb0ftl {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-L9jc09hRQCHb0ftl .error-icon{fill:#552222;}#mermaid-svg-L9jc09hRQCHb0ftl .erro…

微软AutoGen:多智能体协作的工业级解决方案

微软AutoGen&#xff1a;多智能体协作的工业级解决方案 &#x1f31f; 嗨&#xff0c;我是IRpickstars&#xff01; &#x1f30c; 总有一行代码&#xff0c;能点亮万千星辰。 &#x1f50d; 在技术的宇宙中&#xff0c;我愿做永不停歇的探索者。 ✨ 用代码丈量世界&#xf…

终端安全管理系统为什么需要使用,企业需要的桌面管理软件

在当今数字化高度发展的时代&#xff0c;企业和组织的运营计算机等&#xff0c;是企业业务流程的重要节点。终端安全管理系统正挥着至关重要的作用。保障数据安全终端设备往往存储着企业的核心数据&#xff0c;终端安全管理系统可以保障安&#xff0c;未经授权的人员也无法获取…

补环境基础(一) 原型与原型链

1.创建对象的几种方式 1.对象字面量模式 直接使用{}定义键值对&#xff1a; const obj { key: value }; 2.Object()构造函数模式 使用内置构造函数&#xff08;较少使用&#xff09;&#xff1a; const person new Object(); console.log(person)//输出 {}3.构造函数模…

Qt+yolov8目标识别

这是一个基于ONNX Runtime的YOLOv8目标检测项目&#xff0c;支持CPU和GPU加速&#xff0c;使用Qt框架构建图形化界面。摄像头实时画面识别视频文件识别&#xff0c;能正常识别目标&#xff1a;红绿灯&#xff0c;人&#xff0c;公交&#xff0c;巴士&#xff0c;摩托车 等YOLOv…

NLP分词notes

BPE 贪心提取所有出现频率高的成为词。 BPE的训练流程 1.初始化&#xff1a;将所有单个字符作为初始词汇表的元素。 2.迭代合并&#xff1a; 统计语料中所有相邻符号对&#xff08;包括字符和合并后的符号&#xff09;的出现频率。找到出现频率最高的符号对&#xff0c;将其合并…

【数据结构】栈和队列-----数据结构中的双生花

文章目录[toc]栈与队列&#xff1a;数据结构中的双生花1. 栈&#xff1a;后进先出的有序世界1.1 概念及结构剖析1.2 实现方式深度解析数组 vs 链表实现1.3 动态栈实现详解&#xff08;附程序源码&#xff09;1.定义一个动态栈2.初始化3.销毁4.入栈5.出栈6.取栈顶数据7.判空8.获…