Qt案例 创建使用QNetworkReply,QNetworkRequest下载http/https资源的输出进度的控制台程序

embedded/2024/10/18 12:32:11/

文章目录

    • 概要
    • 整体架构流程
      • 1. 设计控制台传递参数的字符串格式
      • 2. 定义下载数据结构并且能通过main获取数据
      • 3. 创建下载类Dal_QtDownEngine实现使用QNetworkReply下载数据相关功能
      • 4. 使用QTimer 定时计算下载速度,下载大小,倒计时等
    • 技术细节
      • - 临时下载文件效验
      • - 绑定QNetworkReply::readyRead信号将数据写入文件
      • - 定时器计算下载速度,下载倒计时,下载进度等
      • - 初始化QNetworkReply下载
    • 小结

概要

根据上篇文章中的Aria2.exe下载程序,简单创建一个使用QNetworkReplyQNetworkRequest下载http/https资源的包含输出进度的控制台程序。
在这里插入图片描述
Aria2.exe 相关可以查看

整体架构流程

1. 设计控制台传递参数的字符串格式

如:v 输出所有参数说明

void OutVersionINFO()
{std::cout<<"QT 下载工具:用来下载http/https资源,命令行空格隔开"<<std::endl;std::cout<<"================================"<<std::endl;std::cout<<" -V                         显示命令行参数"<<std::endl;std::cout<<" -url:?                     http/https资源链接(必须) 如(-url:https://xxx//x/x.txt)"<<std::endl;std::cout<<" -filename:                 完整文件名(可选) 如(-filename:x.txt)"<<std::endl;std::cout<<" -outdir:                   输出到指定目录(必须) 如(-outdir:c:/Temp)"<<std::endl;std::cout<<" -md5:                      文件完整性md5验证(可选) 如(-md5:78abd5ed0d2faa66749ad59a4c869def)"<<std::endl;std::cout<<" -recount:                  下载失败后重连次数(可选,默认10次) 如(-recount:5)"<<std::endl;std::cout<<" 完整示例:"<<std::endl;std::cout<<" \"C:\\Users\\admin\\Desktop\\text1\\QtDownEngine.exe\" -url:\"http://xxx/xxxpe.xxx\" -outdir:\"C:\\Users\\admin\\Desktop\\MSCV2017 RELEASE DLL\\text\" -md5:\"a78406f15a3c4da746c0f335c109519f\" -recount:20"<<std::endl;std::cout<<"================================"<<std::endl;
}

2. 定义下载数据结构并且能通过main获取数据

定义个下载的数据结构DownParameter,包括http/https资源链接,输出到指定目录,文件完整性md5字符串等信息

//! 下载文件参数结构
struct DownParameter
{QString httpurl; //! -url:QString filename; //! -filename:QString outdir; //! -outdir:QString Md5; //! -md5://! 重连次数int Reconnection=10; //! -recount:10bool isvalid(){return (httpurl.trimmed()!="" && outdir.trimmed()!="");}//! 打印/调试void Print(){std::cout<<"Down Parameter  -->"<<std::endl;std::cout<<"httpurl: "<<httpurl.toStdString().c_str()<<std::endl;std::cout<<"filename: "<<filename.toStdString().c_str()<<std::endl;std::cout<<"outdir: "<<outdir.toStdString().c_str()<<std::endl;std::cout<<"Md5: "<<Md5.toStdString().c_str()<<std::endl;std::cout<<"Reconnection: "<<Reconnection<<std::endl;}
};

将main传入的参数绑定到结构体上:
注意使用setlocale(LC_ALL, "zh_CN.utf8") 设置控制台输出为utf-8格式,否则乱码!

int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);setlocale(LC_ALL, "zh_CN.utf8");  // 设置本地化为简体中文 UTF-8DownParameter Parameter;//! 解析命令行for(int i=0;i<argc;i++){QString val=QString::fromStdString(argv[i]).trimmed();std::cout <<"[i] "<<i<<" [val] "<<val.toStdString().c_str() << std::endl;if(val.toUpper()=="-V"){goto Print_Format;}if(val.mid(0,5).toLower()=="-url:"){Parameter.httpurl=val.mid(5);continue;}if(val.mid(0,10).toLower()=="-filename:"){Parameter.filename=val.mid(10);continue;}if(val.mid(0,8).toLower()=="-outdir:"){Parameter.outdir=val.mid(8);continue;}if(val.mid(0,5).toLower()=="-md5:"){Parameter.Md5=val.mid(5);continue;}if(val.mid(0,9).toLower()=="-recount:"){Parameter.Reconnection=fmax(val.mid(9).toInt(),1);continue;}}if(!Parameter.isvalid())goto Print_Format;//! ----//! ----//! 其他操作
}

QNetworkReply_115">3. 创建下载类Dal_QtDownEngine实现使用QNetworkReply下载数据相关功能

Dal_QtDownEngine.h: 简单实现写入文件流

class Dal_QtDownEngine:public QObject
{Q_OBJECT
public:Dal_QtDownEngine(QObject* parent=nullptr);~Dal_QtDownEngine();//! 开始下载bool StartDown(DownParameter _parameter);//! 返回文件的完整路径QString Filefullpath(){return Filepath;}//! 返回异常信息;QString Error(){return  ErrorStr;}//! 获取md5内容QString GetMd5(QString filepath);
public slots:///已下载void HaveFulfilled(qint64 bytesRead,qint64 totalBytes);///写入文件流void WriteFileData();//! 文件效验 有则继续没有则重头下载void file_Validation();//! 一秒钟执行一次void TimeOutByONE();
protected://! 实现退出下载//! 退出下载必须关闭文件流void QuitDown();private://! 参数结构DownParameter Parameter;//! 文件另存路径QString Filepath;//! 文件另存路径 TEMP缓存路径QString FilepathTEMP;///下载QNetworkReply *reply =nullptr;///文件大小qint64 filesize=0;///文件已写入字节 历史写入qint64 FileDownSize=0;///写入文件指针QFile* fileOpera=nullptr;//! 计时器//! 每一秒计算下载速度等数据QTimer* LTimer=nullptr;//! 累计下载 用于计算下载速度qint64 bytesRead_old_Value;qint64 bytesRead_Value;qint64 totalBytes_Value;//! 异常信息QString ErrorStr;//! 标识 线程idQString Identification;
};

4. 使用QTimer 定时计算下载速度,下载大小,倒计时等

Dal_QtDownEngine下载类中声明一个定时器,每秒计算下载速度

 LTimer= new QTimer();connect(LTimer,&QTimer::timeout,this,&Dal_QtDownEngine::TimeOutByONE);

技术细节

- 临时下载文件效验

检查本地路径下是否有已经下载过的临时文件,
有则追加下载,没有则创建一个临时文件

void Dal_QtDownEngine::file_Validation()
{if(IsNotNULL(fileOpera)){fileOpera->close();delete fileOpera;fileOpera=nullptr;}///文件已下载/已写入效验fileOpera = new QFile(FilepathTEMP);if(QFileInfo::exists(FilepathTEMP)){if(!fileOpera->open(QIODevice::Append)){delete fileOpera;fileOpera = nullptr;throw QString("打开临时写入文件失败!");}}else{if(!fileOpera->open(QIODevice::WriteOnly)){delete fileOpera;fileOpera = nullptr;throw QString("创建临时写入文件失败!");}}//! 获取文件已写入大小作为下一次下载的开始FileDownSize=fileOpera->size();
}

QNetworkReplyreadyRead_237">- 绑定QNetworkReply::readyRead信号将数据写入文件

读取QNetworkReply数据直接写入文件中

#define IsNotNULL(_Parament_) (_Parament_!=NULL && _Parament_!=nullptr)
void Dal_QtDownEngine::WriteFileData()
{if(IsNotNULL(reply) && IsNotNULL(fileOpera)){QByteArray temparray= reply->readAll();if(IsNotNULL(fileOpera) && temparray.length()>0)fileOpera->write(temparray);temparray.clear();}
}

- 定时器计算下载速度,下载倒计时,下载进度等

计算下载速度,下载倒计时,下载进度并参考Aria2.exe程序以
[#18312 11.96MiB/1.06GiB(1%) CN:1 DL:5.48MiB ETA:03m17s]
的格式输出相关信息
18312 下载控制台程序在任务管理器中的PID,可通过PID结束进程
而字符串的解析也可以参考上篇文章,都是一样的格式!

void Dal_QtDownEngine::TimeOutByONE()
{//! 每秒统计  "[#2d9da7 3.0MiB/1.0GiB(22%) CN:1 DL:7.4MiB ETA:21h23m2s]";if(bytesRead_old_Value==0){//! 第一次不统计bytesRead_old_Value=bytesRead_Value;return;}//std::cout<<"[bytesRead_old_Value] "<<bytesRead_old_Value<<" [bytesRead_Value] "<<bytesRead_Value<<" [totalBytes_Value] "<<totalBytes_Value;qint64  bytesRead_old=bytesRead_old_Value;qint64  bytesRead=bytesRead_Value;qint64  totalBytes=totalBytes_Value;bytesRead_old_Value=bytesRead_Value;qint64 downSpeed= ceil((double)(bytesRead-bytesRead_old));size_t mtimes= ceil((double)(totalBytes-bytesRead)/downSpeed);double v=roundf(((double)(bytesRead+FileDownSize)/(double)(totalBytes+FileDownSize))*100);QString Schedule=QString("[#%1 %2/%3(%4%) CN:1 DL:%5 ETA:%6]").arg(Identification).arg(GetsizeD(bytesRead+FileDownSize)).arg(GetsizeD(totalBytes+FileDownSize)).arg(fmin(v,100)).arg(GetsizeD(downSpeed)).arg(GetCountZero(mtimes));//! 打印下载进度std::cout<<Schedule.toStdString().c_str()<<std::endl;
}

QNetworkReply_290">- 初始化QNetworkReply下载

整个下载功能的相关实现
包括下载失败重新下载,md5验证等。

bool Dal_QtDownEngine::StartDown(DownParameter _parameter)
{//! 进程idDWORD pid = GetCurrentProcessId();Parameter=_parameter;Identification=QString::number(pid);//! 是否下载成功bool ISsucceed=true;filesize=0;FileDownSize=0;bytesRead_old_Value=0;bytesRead_Value=0;totalBytes_Value=0;QString filename="";LTimer= new QTimer();connect(LTimer,&QTimer::timeout,this,&Dal_QtDownEngine::TimeOutByONE);QEventLoop eventLoop;QNetworkRequest request;//! 失败重连 重连次数int Reconnection=0;try {//! 验证远程链接if(!urlFormat_Validation(Parameter.httpurl,filesize,filename)){goto out;}if(Parameter.filename.trimmed()=="")Parameter.filename=filename;Filepath=Parameter.outdir+"/"+Parameter.filename;FilepathTEMP=Filepath+".TEMP";do{Reconnection++;if(Reconnection>1)std::cout<<QString::number(Reconnection).toStdString().c_str()<<".连接异常断开!正在重连...("<<ErrorStr.toStdString().c_str()<<");"<<std::endl;//! 简单的大小判断QFileInfo info(Filepath);if(info.exists()){//! 文件如果已存在 不下载直接进行md5验证if(info.size()==filesize && filesize>0)goto MD5_Verify;elseQFile::remove(Filepath); //大小不一致 删除旧版本}//! 打开 TEMP 临时文件流file_Validation();//! 开始统计LTimer->start(1000);//! 开始下载request=QNetworkRequest();request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);  //启动重定向request.setRawHeader("Range",tr("bytes=%1-").arg(FileDownSize).toUtf8());request.setUrl(QUrl(Parameter.httpurl));QNetworkAccessManager* manager= new QNetworkAccessManager();reply = manager->get(request);QObject::connect(reply, SIGNAL(finished()),&eventLoop, SLOT(quit()));QObject::connect(reply, SIGNAL(aboutToClose()),&eventLoop, SLOT(quit())); //! 异常也会退出connect(reply,&QNetworkReply::readyRead,this,&Dal_QtDownEngine::WriteFileData);connect(reply,&QNetworkReply::downloadProgress,this,&Dal_QtDownEngine::HaveFulfilled);//! 堵塞进程 直到下载完成!eventLoop.exec(QEventLoop::ExcludeUserInputEvents);///关闭文件if( IsNotNULL(fileOpera)){fileOpera->close();}if( IsNotNULL(reply) && (reply->error() != QNetworkReply::NoError)){ISsucceed=false;ErrorStr="下载失败,原因未知!";}if( IsNotNULL(fileOpera)&& fileOpera->size()==filesize ){ISsucceed=fileOpera->rename(Filepath);if(!ISsucceed){ErrorStr="文件重命名失败!";}}//! md5 文件格式校验MD5_Verify:if(Parameter.Md5.trimmed()!=""){if(Parameter.Md5.trimmed().toUpper()!=GetMd5(Filepath).toUpper()){ISsucceed=false;QFile::remove(Filepath); //md5校验失败删除文件ErrorStr="文件["+Parameter.filename+"]Md5校验失败!";}}if(ISsucceed) //成功下载,也跳出循环break;}while(Reconnection<=Parameter.Reconnection);} catch (QString error) {ISsucceed=false;ErrorStr=error;}out:return ISsucceed;
}

小结

之所以要单独测试写一个使用QNetworkReplyQNetworkRequest下载http/https资源的输出进度的控制台程序,就是为了预防一种情况:
那就是手动强行结束下载或暂停下载功能导致软件闪崩。
比如这种异常:
QTdebug提示ASSERT: "bytes <= bufferSize" in file tools\qringbuffer.cpp, line 113
而通过QProcess调用cmd启动控制台下载,完美解决了这个问题。
不需要考虑下载过程中在什么地方插入暂停或结束判断,不用担心线程锁的问题,直接结束整个下载进程!

值得注意两点的是:
1. Qt的控制台程序,使用qDebug,qInfo输出信息在QProcessreadAllStandardOutput() 是获取不到的,
只有使用std::cout才能在QProcess中获取到控制台输出;
#include <iostream>
std::cout<<"Out Text code -->"<<std::endl;
2. QProcess的kill()和close()可能无法关闭 [通过cmd调用的另外程序] ,至少测试的时候是没有退出的,就比如写的这个控制台程序。
后面可以通过任务管理器中PID结束进程:
Identification变量在程序运行的时候就获取了PID,并输出到了控制台,通过获取的PID强制结束下载进程。

//! 用来根据Identification 关闭进程
void KillProvess(){HANDLE hProcess;DWORD exitCode = 0;DWORD th32ProcessID=Identification.toInt();hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, th32ProcessID); // 假设你已经获取了进程IDif (hProcess == NULL) {// 处理错误std::cout<<"OpenProcess is error :"<<Identification.toStdString().c_str()<<" losed !"<<std::endl;}if (!TerminateProcess(hProcess, exitCode)) {// 处理错误std::cout<<"关闭线程:"<<Identification.toStdString().c_str()<<"失败!"<<std::endl;}CloseHandle(hProcess);
}

http://www.ppmy.cn/embedded/28349.html

相关文章

java学习笔记11

20. 字符串类 字符串是指一连串的字符,它是由许多单个字符连接而成。字符串可以包含任意字符,这些字符必须包含在一对双引号""之内,例如:“abc”.java中封装了3个字符串类,分别是String类、StringBuffer类、StringBuilder类,都在java.lang包中。20.1 String类的…

学习使用html中table实现内容滚动下拉表头和左右滑动前四列固定不动的方法代码整理

学习使用html中table实现内容滚动下拉表头和左右滑动前四列固定不动的方法代码整理 效果图代码 效果图 代码 <!DOCTYPE html> <html> <head><meta charset"utf-8"><title></title><style>table {border-collapse: collap…

【maven】pom文件详解和延伸知识

【maven】pom文件详解 【一】maven项目的pom文件详解【1】maven项目的目录结构【2】根元素和必要配置【3】父项目和parent元素【4】项目构建需要的信息【5】项目依赖相关信息&#xff08;1&#xff09;依赖坐标&#xff08;2&#xff09;依赖类型&#xff08;3&#xff09;依赖…

Java_从入门到JavaEE_08

一、Eclipse开发工具的介绍 Eclipse工具简绍 Eclipse 是著名的跨平台的自由集成开发环境&#xff08;IDE&#xff09;。最初主要用来 Java 语言开发&#xff0c;但是目前亦有人通过插件使其作为其他计算机语言比如 C 和 Python 的开发工具。 下载与安装 下载&#xff1a; Ecli…

SpringBoot中阿里OSS简单使用

官方文档:Java跨域设置实现跨域访问_对象存储(OSS)-阿里云帮助中心 1.pom中引入依赖 <dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.15.1</version> </dependency> 如…

vue和react这两大前端框架的优缺点对比

【A】Vue和React是目前最流行的两个前端框架&#xff0c;它们都具有各自的优点和缺点。下面是对Vue和React的优缺点的详细介绍&#xff1a; Vue的优点&#xff1a; 简单易学&#xff1a;Vue的API设计简单&#xff0c;上手较为容易&#xff0c;学习曲线相对较低。文档丰富&…

[1678]旅游景点信息Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 旅游景点信息管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql…

【PHP】安装指定版本Composer

1、下载指定版本composer.phar文件&#xff1a;https://github.com/composer/composer/releases 2、将下载的文件添加到全局路径&#xff1a; sudo mv composer.phar /usr/local/bin/composer 3、赋予权限&#xff1a; sudo chmod x /usr/local/bin/composer 4、查看compos…