QT控件 使用QtServer系统服务实现搭建Aria2下载后台服务,并使用Http请求访问Json-RPC接口调用下载退出

发布于:2025-07-14 ⋅ 阅读:(28) ⋅ 点赞:(0)

前言

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

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 = $$PWD
    QMAKE_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();
 //! 关闭 QProcess
 if(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的界面化使用,
一个简易版的迅雷下载就完成了!


网站公告

今日签到

点亮在社区的每一天
去签到