Qt系列文章019-HTTP下载

1 前言

  前面讲解了主流的TCP/UDP请求后,该到用处最多的HTTP请求了,在Qt中要使用HTTP请求,在Qt4的时候有专门的QHttp,QFtp类使用,可惜在Qt5已经全部干掉了,统一封装了接口QNetworkAccessManager ,下面主要就是围绕该类展开一个系统性的介绍和使用!玩转此类,Qt5的网络请求基本就不在话下了!

公众号:Qt实战,各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发。

公众号:Qt入门和进阶,专门介绍Qt/C++相关知识点学习,帮助Qt开发者更好的深入学习Qt。多位Qt元婴期大神,一步步带你从入门到进阶,走上财务自由之路。

官方店:https://shop114595942.taobao.com//

  这里提前放一个动态示意图,可以让读者再开始之前知道是怎么一回事

在这里插入图片描述

2 Qt高层网络操作类介绍

  Qt网络模块提供–些类实现OSI7层网络模型中高层的网络协议,如HTTP、FTP、SNMP等,这些类主要是QNetworkRequestQNetworkReplyQNetworkAccessManager

  QNetworkRequest类通过一个URL地址发起网络协议请求,也保存网络请求的信息,目前支持HTTP、FTP和局部文件URL的下载或上传。

  QNetworkAccessManager 类用于协调网络操作。在QNetworkRequest发起一个网络请求后,QNetworkAccessManager类负责发送网络请求,创建网络响应。

  QNetworkReply 类表示网络请求的响应。由QNetworkAccessManager在发送一个 网络请求后创建-一个网络响应。QNetworkReply 提供的信号finished()、 readyRead()downloadProgress() 可以监测网络响应的执行情况,执行相应操作。

  QNetworkReplyQIODevice的子类,所以QNetworkReply支持流读写功能,也支持异步或同步工作模式。

3 基于HTTP协议的网络文件下载(包含下载进度、当前下载速度、剩余时间)

  基于上述三个类,设计一个基于HTTP协议的网络文件下载程序,实例程序名称HTTP_Demo, 下图是程序运行下载文件时的界面。
在这里插入图片描述

  在URL地址编辑框里输入一个网络文件URL地址,设置下载文件保存路径后,单击下载按钮就可以开始下载文件到设置的目录下。进度条可以显示文件下载进度,当前下载速度可以实时显示当前网速状态,剩余时间显示完成的时间度。URL里的HTTP地址可以是任何类型的文件,如html、pdf、 doc、exe等。

  实例HTTP_Demo主界面是基于QWidget的窗口类HTTP_Demo,使用UI设计器设计界面,删除了主窗口上的工具栏和状态栏。HTTP_Demo类的定义如下:

QT_BEGIN_NAMESPACE
namespace Ui { class HTTP_Demo; }
QT_END_NAMESPACE

class HTTP_Demo : public QWidget
{
    Q_OBJECT

public:
    HTTP_Demo(QWidget *parent = nullptr);
    ~HTTP_Demo();

    //时间格式化输出
    QString timeFormat(qint64  seconds);
    //速度格式化输出
    QString speed(double speed);
  //已经下载的大小格式化输出
    QString size(qint64 bytes);

    //计算下载进度各种数据,实时下载速度,已下载大小,剩余时间等
    void calculateDownloadProgress(int timeDifference);
private slots:
    //使用缺省值默认路径触发
    void on_pushButton_defaultPath_clicked();
    //选择文件夹路径触发
    void on_pushButton_selectFilePath_clicked();
    //下载按钮触发
    void on_pushButton_download_clicked();

    //下载完成后的槽
    void onFinish();
    void onReadyRead();
    void onDownloadProgress(qint64,qint64);
private:
    Ui::HTTP_Demo *ui;

    QNetworkAccessManager mNetworkManager; //网络管理
    QNetworkReply *mPReply = nullptr; //网络响应
    QFile * mPdownloadedFile = nullptr;//下载保存的临时文件
    QTime m_timeRecord; //记录下载实时网速用时
    int m_timeInterval= 0; //时间记录的间隔差距
    
    qint64 m_bytesReceived = 0; ///当前的接收数据大小
    qint64 m_bytesTotal = 0;  ///当前下载的数据总大小
    qint64 m_currentDownloadSize = 0; ///当前已经下载的大小
    qint64 m_intervalDownloadSize = 0; ///计算速度之前下载的大小的累计和,计算速度用
};
#endif // HTTP_DEMO_H

  要下载文件,先在窗口上的URL编辑框里输入下载地址(可以使用Ctrl+V组合键粘贴URL地址),再设置下载文件保存的目录。单击缺省路径按钮会在程序的当前目录下创建一个临时文件夹,代码如下:

void HTTP_Demo::on_pushButton_defaultPath_clicked()
{
    //缺省路径按钮
    QString curPath="C:/Users/Administrator/Downloads";
    QDir dir (curPath) ;
    QString sub="temp";
    dir.mkdir (sub) ;
    ui->lineEdit_fileSavePath->setText (curPath+"/"+sub+"/") ;
}

  单击选择文件路径按钮会在打开一个文件夹路径选择框,在选择的文件夹路径下创建一个临时文件夹,代码如下:

void HTTP_Demo::on_pushButton_selectFilePath_clicked()
{
    QString selectdir = QFileDialog::getExistingDirectory(this,
   										 tr("Open Directory"),
                                         "/home",
                                     QFileDialog::ShowDirsOnly|
                                     QFileDialog::DontResolveSymlinks);
    QDir dir (selectdir) ;
    QString sub="temp";
    dir.mkdir (sub) ;
    ui->lineEdit_fileSavePath->setText(selectdir+"/"+sub+"/");
}

  选择的效果动态图如下:

在这里插入图片描述
  选择自己的保存路径后,然后填写下载URL,单击下载 按钮开始下载过程,下载 按钮的响应代码如下:

void HTTP_Demo::on_pushButton_download_clicked()
{
    //开始下载
    QString urlSpec = ui->lineEdit_downloadURL->text ().trimmed() ;
    if (urlSpec. isEmpty() )
    {
        QMessageBox::information(this, "错误","请指定需要下载的URL") ;
        return;
    }
    bool isHttpsRequest;
    //不区分大小写判断是否是https请求,
    if(urlSpec.startsWith("https:"), Qt::CaseInsensitive);
    {
        isHttpsRequest = true;
    }

    QUrl newUrl = QUrl(urlSpec) ;
    if ( !newUrl. isValid() )
    {
        QMessageBox:: information(this,"错误",QString("无效URL: 81 \n错误信息: 82") .arg (urlSpec, newUrl.errorString())) ;
        return;
    }
    QString tempDir =ui->lineEdit_fileSavePath->text().trimmed() ;
    if (tempDir. isEmpty())
    {
         QMessageBox::information(this, "错误", "请指定保存下载文件的目录") ;
          return;
    }

    QString fileName = newUrl.fileName ();

    //如果找不到文件名称,就默认写一个
    if(fileName.isEmpty())
    {
        fileName = "temp.date";
    }
    QString fullFileName =tempDir+fileName ;
    if (QFile::exists (fullFileName))
    {
         QFile::remove(fullFileName) ;
    }

    mPdownloadedFile =new QFile (fullFileName) ;
    
    if (!mPdownloadedFile->open(QIODevice::Append))
    {
        QMessageBox::information(this, "错误" , "临时文件打开错误") ;
        return;
    }
    ui->pushButton_download->setEnabled(false) ;
    if(mNetworkManager == nullptr)
    {
        mNetworkManager = new QNetworkAccessManager(this);
    }

    QNetworkRequest request2(newUrl);
	//*********
    if(isHttpsRequest)
    {
        //设置SSL,HTTPS协议需要SSL证书
        QSslConfiguration m_sslConfig = QSslConfiguration::defaultConfiguration();
        m_sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone);
        m_sslConfig.setProtocol(QSsl::TlsV1SslV3);
        request2.setSslConfiguration(m_sslConfig);
    }

    request2.setRawHeader("Content-Type", "application/x-www-form-urlencoded");
    request2.setRawHeader("Connection","keep alive");
	//******这一段有没有其实都无所谓,我是后面尝试测试过的,主要是ssl库有就行了

    mPReply = mNetworkManager->get(request2) ;

    connect (mPReply, &QNetworkReply::finished,  this, &HTTP_Demo::onFinish) ;
    connect (mPReply, &QNetworkReply::readyRead, this, &HTTP_Demo::onReadyRead) ;
    connect (mPReply, &QNetworkReply::downloadProgress,  this, &HTTP_Demo::onDownloadProgress) ;

    m_timeRecord.start();
}

  代码在读取URL地址后,将其转换为一个QUrl类变量newUrl,并检查其有效性,再检查临时文件目录,创建临时文件downloadedFile

  这些准备好之后,用QNetworkAccessManager发布网络请求,请求下载URL地址表示的文件,并创建网络响应,关键代码为:

mPReply = mNetworkManager.get(QNetworkRequest(newUrl)) ;

  mPReply为网络响应,将其3个信号与相关的自定义槽函数相关联,实现相应的操作。这3个槽函数的代码如下:

void HTTP_Demo::onFinish()
{
    //网络响应结束
    QFileInfo fileInfo;
    fileInfo.setFile(mPdownloadedFile->fileName() ) ;
    mPdownloadedFile->close() ;
    delete mPdownloadedFile;
    mPdownloadedFile = nullptr;
    mPReply->deleteLater() ;
    mPReply = nullptr; 
    
    ui->pushButton_download->setEnabled(true) ;
}

void HTTP_Demo::onReadyRead()
{
    //实取下载的数据
    mPdownloadedFile->write(mPReply->readAll() ) ;
}

void HTTP_Demo::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
    m_bytesReceived = bytesReceived;
    m_bytesTotal = bytesTotal;
    //下载进程,设置最大值为100
    ui->progressBar->setMaximum(100) ;
    
    calculateDownloadProgress(300);
   
}

void HTTP_Demo::calculateDownloadProgress(int timeDifference)
{
    //计算您当前的进度
    float currentProgressValue = static_cast<float>(m_bytesReceived)/static_cast<float>(m_bytesTotal)*100;

 
    ui->progressBar-> setValue(currentProgressValue) ;
    
    // 输出当前下载进度;
    // 用到除法需要注意除0错误;
    //qDebug() << QString("%1").arg(bytesReceived * 100 / bytesTotal + 1);


    // m_intervalDownload 为下次计算速度之前的下载字节数;
    m_intervalDownloadSize += m_bytesReceived - m_currentDownloadSize;
    m_currentDownloadSize = m_bytesReceived;

    int timeNow = m_timeRecord.elapsed();

    // 超过0.3s更新计算一次速度;
    if (timeNow - m_timeInterval > timeDifference)
    {
        qint64 ispeed = m_intervalDownloadSize * 1000 / (timeNow - m_timeInterval);
        QString strSpeed = speed(ispeed);
        ui->label_speed->setText("当前下载速度:"+strSpeed);
        
        // 剩余时间;
        qint64 timeRemain = 0;
        if (ispeed != 0)
        {
            timeRemain = (m_bytesTotal - m_bytesReceived) / ispeed;
        }

        //转换单位(下载剩余时间)
        QString strTimeRemain = timeFormat(timeRemain);

        ui->label_time->setText("剩余时间:"+strTimeRemain);
        //当前已下载的数据大小
        QString currentFileDownSize = size(m_currentDownloadSize);
        
        ui->label_downSize->setText("已下载大小:"+currentFileDownSize);
           
        //下载文件的总大小
        QString totalFileSize = size(m_bytesTotal);
        ui->label_totalSize->setText("文件总大小:"+totalFileSize);

        m_intervalDownloadSize = 0;
        m_timeInterval = timeNow;

    }
}

  在缓冲区有新下载的数据等待读取时,会发射readyRead()信号,槽函数  onReadyRead()读取下载缓冲区的数据到临时文件。

  downloadProgress()是表示网络操作进度的信号,传递bytesReceived和bytesTotal两个参数,表示已读取字节数和总的字节数; onDownloadProgress()槽 函数将这两个参数用于进度条的显示,可以显示下载进度。

  finished() 信号在下载结束后发射,槽函数onFinish()的功能是关闭临时文件,删除文件变量和网络响应变量。

4 下载动态示意图

  例如,如果下载的是一个Qt5.12.10文件,不知道为何使用Qt官方的下载链接会报错,下载总大小会出错,后面改为了清华大学的才正常,下面贴出下载链接https://mirrors.tuna.tsinghua.edu.cn/qt/archive/qt/5.12/5.12.10/qt-opensource-windows-x86-5.12.10.exe

  因为是https请求,所以请求需要SSL库,Mingw编译的请在自己的Qt安装库中找到对应的libssllibcrypto库,下面是我的路径:

D:Qt\Qt5.12.10\Tools\QtCreator\bin\libssl-1_1-x64.dll或者是libssl-1_1.dll
D:Qt\Qt5.12.10\Tools\QtCreator\bin\libcrypto-1_1-x64.dll或者是libcrypto-1_1.dll

  MSVC编译的请自行编译,Qt好像是不带MSVC的,如果找不到,可以查看我这边文章
Windows10+VS2017下安装和 编译openssl 1.1版本库

  请记得将两个dll库放到与exe的位置一起

  好了,下面是我的https下载的动态效果图如下:
在这里插入图片描述
  因为下载拉取数据有卡顿,所以到12%的时候进度卡主了很久,这个是和服务器有关系,然后我测试了这个链接http://vfx.mtime.cn/Video/2019/03/21/mp4/190321153853126488.mp4
  下面是下载动态图效果:
在这里插入图片描述

  下载完成后
在这里插入图片描述
  播放是没问题的,所以下载是没问题的
在这里插入图片描述
  为了演示下载大文件的性能,去除服务端问题,就像上面Qt下载总是有问题,这种就说明不了是Qt网络库的问题,所以我找到了一个百M的链接,下载360软件,84M 链接如下:
https://dl.360safe.com/pclianmeng/n/1__4000943.exe?source=%E6%9C%A8%E9%A9%AC%E6%9F%A5%E6%9D%80

  通过360官网拿到的,实在找不到其他更大的链接,电影链接都是磁力的,目前也不支持,我们只说HTTP/HTTPS下载的
在这里插入图片描述
下载完成后如下图:
在这里插入图片描述

  所以从这方便看,基本上下载是没多大问题的,好了有需要的可以在自己再深入了解吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值