添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

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;