Qt Http文件下载功能实现

        最近工作中有个通过Http下载文件的需求,于是参考Qt的官方例程,做了个工具类。可灵活应用于各种Qt项目。文章末尾贴出代码和注释。

1,基础知识

1.1  QNetworkAccessManager

    网络访问 API 是围绕一个 QNetworkAccessManager 对象构建的,该对象包含它发送的请求的通用配置和设置。 它包含代理和缓存配置,以及与此类问题相关的信号,以及可用于监控网络操作进度的回复信号。 一个 QNetworkAccessManager 实例对于整个 Qt 应用程序应该足够了。 由于QNetworkAccessManager是基于QObject的,所以只能从它所属的线程中使用。 一旦创建了 QNetworkAccessManager 对象,应用程序就可以使用它通过网络发送请求。 提供了一组标准函数,它们接受请求和可选数据,每个函数都返回一个 QNetworkReply 对象。 返回的对象用于获取响应相应请求而返回的任何数据。

1.2  QNetworkReply

    QNetworkReply 类包含与使用 QNetworkAccessManager 发布的请求相关的数据和元数据。 与 QNetworkRequest 一样,它包含一个 URL 和标头(解析和原始形式)、有关回复状态的一些信息以及回复本身的内容。 QNetworkReply 是一个顺序访问的 QIODevice,这意味着一旦从对象中读取数据,它就不再由设备保存。 因此,如果需要,应用程序有责任保留这些数据。 每当从网络接收到更多数据并进行处理时,就会发出 readyRead() 信号。 接收到数据时也会发出 downloadProgress() 信号,但如果对内容进行任何转换(例如,解压缩和删除协议开销),则其中包含的字节数可能不代表实际接收到的字节数。

1.3  std::unique_ptr

    std::unique_ptr 是一个智能指针,它通过指针拥有和管理另一个对象,并在 unique_ptr 超出范围时处置该对象。 当发生以下任一情况时,使用关联的删除器处理该对象: 管理 unique_ptr 对象被破坏 管理 unique_ptr 对象通过 operator= 或 reset() 分配另一个指针。 通过调用 get_deleter()(ptr) 使用可能由用户提供的删除器处理该对象。 默认删除器使用删除操作符,它会销毁对象并释放内存。 unique_ptr 也可以不拥有任何对象,在这种情况下,值为空。

    std::unique_ptr 有两个版本: 管理单个对象(例如用新对象分配) 管理动态分配的对象数组(例如,使用 new[] 分配) 该类满足 MoveConstructible 和 MoveAssignable 的要求,但既不满足 CopyConstructible 也不满足 CopyAssignable 的要求。

2,代码

DownloadTool.h

#pragma once

#include <QObject>        // QObject类是Qt对象模型的核心
#include <QUrl>           // QUrl类提供了使用URL的便捷接口
#include <QFile>          // QFile类用于对文件进行读写操作
#include <QDir>           // QDir类用于操作路径名及底层文件系统
#include <QPointer>       // QPointer指针引用的对象被销毁时候,会自动指向NULL,解决指针悬挂问题
#include <QApplication>   // 此处用于获取当前程序绝对路径

#include <QNetworkReply>  // QNetworkReply类封装了使用QNetworkAccessManager发布的请求相关的回复信息。
#include <QNetworkAccessManager>  // QNetworkAccessManager类为应用提供发送网络请求和接收答复的API接口
#include <memory>         // 使用std::unique_ptr需要包含该头文件

#define DOWNLOAD_DEBUG    // 是否打印输出

class DownloadTool : public QObject  // 继承QObject
{
    Q_OBJECT              // 加入此宏,才能使用QT中的signal和slot机制

public:
    // 构造函数参数:  1)http文件完整的url  2)保存的路径
    explicit DownloadTool(const QString& downloadUrl, const QString& savePath, QObject* parent = nullptr);
    ~DownloadTool();

    void startDownload();  // 开始下载文件
    void cancelDownload(); // 取消下载文件

Q_SIGNALS:
    void sigProgress(qint64 bytesRead, qint64 totalBytes, qreal progress);  // 下载进度信号
    void sigDownloadFinished();  // 下载完成信号

private Q_SLOTS:
    void httpFinished();    // QNetworkReply::finished对应的槽函数
    void httpReadyRead();   // QIODevice::readyRead对应的槽函数

    void networkReplyProgress(qint64 bytesRead, qint64 totalBytes);  // QNetworkReply::downloadProgress对应的槽函数

private:
    void startRequest(const QUrl& requestedUrl);
    std::unique_ptr<QFile> openFileForWrite(const QString& fileName);

private:
    QString m_downloadUrl;  // 保存构造时传入的下载url
    QString m_savePath;     // 保存构造时传入的保存路径

    const QString defaultFileName = "tmp";  // 默认下载到tmp文件夹

    QUrl url;
    QNetworkAccessManager qnam;
    QPointer<QNetworkReply> reply;
    std::unique_ptr<QFile> file;
    bool httpRequestAborted;
};

 DownloadTool.cpp

#include "DownloadTool.h"

DownloadTool::DownloadTool(const QString& downloadUrl, const QString& savePath, QObject* parent)
	: QObject(parent)
{
	m_downloadUrl = downloadUrl;
	m_savePath    = savePath;
}

DownloadTool::~DownloadTool() {}

void DownloadTool::startDownload()
{
    const QUrl newUrl = QUrl::fromUserInput(m_downloadUrl);

    if (!newUrl.isValid()) {
#ifdef DOWNLOAD_DEBUG
        qDebug() << QString("Invalid URL: %1: %2").arg(m_downloadUrl, newUrl.errorString());
#endif // DOWNLOAD_DEBUG  
        return;
    }

    QString fileName = newUrl.fileName();

    if (fileName.isEmpty()) fileName = defaultFileName;
    if (m_savePath.isEmpty()) { m_savePath = QApplication::applicationDirPath() + "/tmp"; }
    if (!QFileInfo(m_savePath).isDir()) {
        QDir dir;
        dir.mkpath(m_savePath);
    }

    fileName.prepend(m_savePath + '/');
    if (QFile::exists(fileName)) { QFile::remove(fileName); }
    file = openFileForWrite(fileName);
    if (!file) return;

    startRequest(newUrl);
}

void DownloadTool::cancelDownload()
{
    httpRequestAborted = true;
    reply->abort();
}

void DownloadTool::httpFinished()
{
    QFileInfo fi;
    if (file) {
        fi.setFile(file->fileName());
        file->close();
        file.reset();
    }

    if (httpRequestAborted) {
        return;
    }

    if (reply->error()) {
        QFile::remove(fi.absoluteFilePath());
#ifdef DOWNLOAD_DEBUG
        qDebug() << QString("Download failed: %1.").arg(reply->errorString());
#endif // DOWNLOAD_DEBUG 
        return;
    }

    const QVariant redirectionTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);

    if (!redirectionTarget.isNull()) {
        const QUrl redirectedUrl = url.resolved(redirectionTarget.toUrl());
        file = openFileForWrite(fi.absoluteFilePath());
        if (!file) { return; }
        startRequest(redirectedUrl);
        return;
    }

    Q_EMIT sigDownloadFinished();

#ifdef DOWNLOAD_DEBUG
    qDebug() << QString(tr("Downloaded %1 bytes to %2 in %3")
        .arg(fi.size()).arg(fi.fileName(), QDir::toNativeSeparators(fi.absolutePath())));
    qDebug() << "Finished";
#endif // DOWNLOAD_DEBUG 
}

void DownloadTool::httpReadyRead()
{
    if (file) file->write(reply->readAll());
}

void DownloadTool::networkReplyProgress(qint64 bytesRead, qint64 totalBytes)
{
    qreal progress = qreal(bytesRead) / qreal(totalBytes);
    Q_EMIT sigProgress(bytesRead, totalBytes, progress);

#ifdef DOWNLOAD_DEBUG
    qDebug() << QString::number(progress * 100, 'f', 2) << "%    "
        << bytesRead / (1024 * 1024) << "MB" << "/" << totalBytes / (1024 * 1024) << "MB";
#endif // DOWNLOAD_DEBUG   
}

void DownloadTool::startRequest(const QUrl& requestedUrl)
{
    url = requestedUrl;
    httpRequestAborted = false;

    reply = qnam.get(QNetworkRequest(url));
    connect(reply, &QNetworkReply::finished, this, &DownloadTool::httpFinished);
    connect(reply, &QIODevice::readyRead, this, &DownloadTool::httpReadyRead);
    connect(reply, &QNetworkReply::downloadProgress, this, &DownloadTool::networkReplyProgress);

#ifdef DOWNLOAD_DEBUG
    qDebug() << QString(tr("Downloading %1...").arg(url.toString()));
#endif // DOWNLOAD_DEBUG    
}

std::unique_ptr<QFile> DownloadTool::openFileForWrite(const QString& fileName)
{
    std::unique_ptr<QFile> file(new QFile(fileName));
    if (!file->open(QIODevice::WriteOnly)) {
#ifdef DOWNLOAD_DEBUG
        qDebug() << QString("Unable to save the file %1: %2.")
            .arg(QDir::toNativeSeparators(fileName), file->errorString());
#endif // DOWNLOAD_DEBUG  
        return nullptr;
    }
    return file;
}

在代码中调用,这里我们以下载Docker安装包为例:

    EMDownloadTool* dT;
    dT = new EMDownloadTool("https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe", QApplication::applicationDirPath() + "/download");
    dT->startDownload();

 下载效果:

打印输出:

 下载后的文件:

队列下载多个文件:

    QString url = need_download_queue.front();
    need_download_queue.pop();
    downloadTool = new DownloadTool(url, QApplication::applicationDirPath() + "/file");

    connect(downloadTool, &DownloadTool::sigDownloadFinished, [&] {
        if (need_download_queue.size() == 0) return;
        QString url = need_download_queue.front();
        need_download_queue.pop();
        downloadTool->setUrl(url);
        downloadTool->startDownload(false);
        });

    downloadTool->startDownload(false);

  • 8
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值