用Qt实现HTTP请求下载文件,能够支持断点续传

0 487
索鸟 2021-03-19
需要:0索币

      最近在研究了一下用Qt 的方法来实现http下载,Qt 中的Http请求主要用到了QNetworkAccessManager、QNetworkReply、QNetworkRequest 这三块。本篇文章主要叙述如何用Qt 的方法进行HTTP 请求下载文件,能够支持断点续传(断点续传即能够手动停止下载,下次可以从已经下载的部分开始继续下载未完成的部分,而没有必要从头开始上传下载),并且实时更新下载信息。整体代码考虑十分周到,对各种情况也做了相应的处理,并且有通俗易懂的注释。好了,代码走起!

代码之路

在讲解代码之前先看一下效果图:

效果:


从图中可以看出点击start按钮,进行下载,stop按钮暂停当前下载,close按钮停止当前下载,并删除已经下载的临时文件,并将所有参数重置, 这里界面中下载链接输入框为空是因为我在代码中默认了url,也可以在输入框中输入url进行下载。


代码主要包含两个部分:


1、DownLoadManager : 用来请求下载,向界面传递下载信息,并将下载的内容保存到文件中

2、MyHttpDownload : 用来接收下载链接,利用DownLoadManager进行下载,更新界面,并对当前下载进行操作(包括:开始、暂停、停止下载)。

1、DownLoadManager

#include "downloadmanager.h"

#include <QFile>

#include <QDebug>

#include <QFileInfo>

#include <QDir>


#define DOWNLOAD_FILE_SUFFIX    "_tmp"


DownLoadManager::DownLoadManager(QObject *parent)

    : QObject(parent)

    , m_networkManager(NULL)

    , m_url(QUrl(""))

    , m_fileName("")

    , m_isSupportBreakPoint(false)

    , m_bytesReceived(0)

    , m_bytesTotal(0)

    , m_bytesCurrentReceived(0)

    , m_isStop(true)

{

    m_networkManager = new QNetworkAccessManager(this);

}


DownLoadManager::~DownLoadManager()

{}


// 设置是否支持断点续传;

void DownLoadManager::setDownInto(bool isSupportBreakPoint)

{

    m_isSupportBreakPoint = isSupportBreakPoint;

}


// 获取当前下载链接;

QString DownLoadManager::getDownloadUrl()

{

    return m_url.toString();

}


// 开始下载文件,传入下载链接和文件的路径;

void DownLoadManager::downloadFile(QString url , QString fileName)

{

    // 防止多次点击开始下载按钮,进行多次下载,只有在停止标志变量为true时才进行下载;

    if (m_isStop)

    {

        m_isStop = false;

        m_url = QUrl(url);


        // 这里可用从url中获取文件名,但不是对所有的url都有效;

//      QString fileName = m_url.fileName();


        // 将当前文件名设置为临时文件名,下载完成时修改回来;

        m_fileName = fileName + DOWNLOAD_FILE_SUFFIX;


        // 如果当前下载的字节数为0那么说明未下载过或者重新下载

        // 则需要检测本地是否存在之前下载的临时文件,如果有则删除

        if (m_bytesCurrentReceived <= 0)

        {

            removeFile(m_fileName);

        }


        QNetworkRequest request;

        request.setUrl(m_url);


        // 如果支持断点续传,则设置请求头信息

        if (m_isSupportBreakPoint)

        {

            QString strRange = QString("bytes=%1-").arg(m_bytesCurrentReceived);

            request.setRawHeader("Range", strRange.toLatin1());

        }


        // 请求下载;

        m_reply = m_networkManager->get(request);


        connect(m_reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(onDownloadProgress(qint64, qint64)));

        connect(m_reply, SIGNAL(readyRead()), this, SLOT(onReadyRead()));

        connect(m_reply, SIGNAL(finished()), this, SLOT(onFinished()));

        connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));

    }   

}


// 下载进度信息;

void DownLoadManager::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)

{

    if (!m_isStop)

    {

        m_bytesReceived = bytesReceived;

        m_bytesTotal = bytesTotal;

        // 更新下载进度;(加上 m_bytesCurrentReceived 是为了断点续传时之前下载的字节)

        emit signalDownloadProcess(m_bytesReceived + m_bytesCurrentReceived, m_bytesTotal + m_bytesCurrentReceived);

    }   

}


// 获取下载内容,保存到文件中;

void DownLoadManager::onReadyRead()

{

    if (!m_isStop)

    {

        QFile file(m_fileName);

        if (file.open(QIODevice::WriteOnly | QIODevice::Append))

        {

            file.write(m_reply->readAll());

        }

        file.close();

    }   

}


// 下载完成;

void DownLoadManager::onFinished()

{

    m_isStop = true;

    // http请求状态码;

    QVariant statusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);


    if (m_reply->error() == QNetworkReply::NoError)

    {

        // 重命名临时文件;

        QFileInfo fileInfo(m_fileName);

        if (fileInfo.exists())

        {

            int index = m_fileName.lastIndexOf(DOWNLOAD_FILE_SUFFIX);

            QString realName = m_fileName.left(index);

            QFile::rename(m_fileName, realName);

        }

    }

    else

    {

        // 有错误输出错误;

        QString strError = m_reply->errorString();

        qDebug() << "__________" + strError;

    }


    emit signalReplyFinished(statusCode.toInt());

}


// 下载过程中出现错误,关闭下载,并上报错误,这里未上报错误类型,可自己定义进行上报;

void DownLoadManager::onError(QNetworkReply::NetworkError code)

{

    QString strError = m_reply->errorString();

    qDebug() << "__________" + strError;


    closeDownload();

    emit signalDownloadError();

}


// 停止下载工作;

void DownLoadManager::stopWork()

{

    m_isStop = true;

    if (m_reply != NULL)

    {

        disconnect(m_reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(onDownloadProgress(qint64, qint64)));

        disconnect(m_reply, SIGNAL(readyRead()), this, SLOT(onReadyRead()));

        disconnect(m_reply, SIGNAL(finished()), this, SLOT(onFinished()));

        disconnect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));

        m_reply->abort();

        m_reply->deleteLater();

        m_reply = NULL;

    }

}


// 暂停下载按钮被按下,暂停当前下载;

void DownLoadManager::stopDownload()

{

    // 这里m_isStop变量为了保护多次点击暂停下载按钮,导致m_bytesCurrentReceived 被不停累加;

    if (!m_isStop)

    {

        //记录当前已经下载字节数

        m_bytesCurrentReceived += m_bytesReceived;

        stopWork();

    }   

}


// 重置参数;

void DownLoadManager::reset()

{

    m_bytesCurrentReceived = 0;

    m_bytesReceived = 0;

    m_bytesTotal = 0;

}


// 删除文件;

void DownLoadManager::removeFile(QString fileName)

{

    // 删除已下载的临时文件;

    QFileInfo fileInfo(fileName);

    if (fileInfo.exists())

    {

        QFile::remove(fileName);

    }

}


// 停止下载按钮被按下,关闭下载,重置参数,并删除下载的临时文件;

void DownLoadManager::closeDownload()

{

    stopWork();

    reset();

    removeFile(m_fileName);

}


2、MyHttpDownload

#include "myhttpdownload.h"

#include "downloadmanager.h"

#include <QDebug>


#define UNIT_KB 1024            //KB

#define UNIT_MB 1024*1024       //MB

#define UNIT_GB 1024*1024*1024  //GB


#define TIME_INTERVAL 300       //0.3s


MyHttpDownload::MyHttpDownload(QWidget *parent)

    : QWidget(parent)

    , m_downloadManager(NULL)

    , m_url("")

    , m_timeInterval(0)

    , m_currentDownload(0)

    , m_intervalDownload(0)

{

    ui.setupUi(this);

    initWindow();

}


MyHttpDownload::~MyHttpDownload()

{


}


void MyHttpDownload::initWindow()

{

    ui.progressBar->setValue(0);

    connect(ui.pButtonStart, SIGNAL(clicked()), this, SLOT(onStartDownload()));

    connect(ui.pButtonStop, SIGNAL(clicked()), this, SLOT(onStopDownload()));

    connect(ui.pButtonClose, SIGNAL(clicked()), this, SLOT(onCloseDownload()));

    // 进度条设置样式;

    ui.progressBar->setStyleSheet("\

                QProgressBar\

                {\

                    border-width: 0 10 0 10;\

                    border-left: 1px, gray;\

                    border-right: 1px, gray;\

                    border-image:url(:/Resources/progressbar_back.png);\

                }\

                QProgressBar::chunk\

                {\

                    border-width: 0 10 0 10;\

                    border-image:url(:/Resources/progressbar.png);\

                }");

}


// 开始下载;

void MyHttpDownload::onStartDownload()

{

    // 从界面获取下载链接;

    m_url = ui.downloadUrl->text();

    if (m_downloadManager == NULL)

    {

        m_downloadManager = new DownLoadManager(this);

        connect(m_downloadManager , SIGNAL(signalDownloadProcess(qint64, qint64)), this, SLOT(onDownloadProcess(qint64, qint64)));

        connect(m_downloadManager, SIGNAL(signalReplyFinished(int)), this, SLOT(onReplyFinished(int)));

    }


    // 这里先获取到m_downloadManager中的url与当前的m_url 对比,如果url变了需要重置参数,防止文件下载不全;

    QString url = m_downloadManager->getDownloadUrl();

    if (url != m_url)

    {

        m_downloadManager->reset();

    }

    m_downloadManager->setDownInto(true);

    m_downloadManager->downloadFile(m_url, "F:/MyHttpDownload/MyDownloadFile.zip");

    m_timeRecord.start();

    m_timeInterval = 0;

    ui.labelStatus->setText(QStringLiteral("正在下载"));

}


// 暂停下载;

void MyHttpDownload::onStopDownload()

{

    ui.labelStatus->setText(QStringLiteral("停止下载"));

    if (m_downloadManager != NULL)

    {

        m_downloadManager->stopDownload();

    }

    ui.labelSpeed->setText("0 KB/S");

    ui.labelRemainTime->setText("0s");

}


// 关闭下载;

void MyHttpDownload::onCloseDownload()

{

    m_downloadManager->closeDownload();

    ui.progressBar->setValue(0);

    ui.labelSpeed->setText("0 KB/S");

    ui.labelRemainTime->setText("0s");

    ui.labelStatus->setText(QStringLiteral("关闭下载"));

    ui.labelCurrentDownload->setText("0 B");

    ui.labelFileSize->setText("0 B");

}


// 更新下载进度;

void MyHttpDownload::onDownloadProcess(qint64 bytesReceived, qint64 bytesTotal)

{

    // 输出当前下载进度;

    // 用到除法需要注意除0错误;

    qDebug() << QString("%1").arg(bytesReceived * 100 / bytesTotal + 1);

    // 更新进度条;

    ui.progressBar->setMaximum(bytesTotal);

    ui.progressBar->setValue(bytesReceived);


    // m_intervalDownload 为下次计算速度之前的下载字节数;

    m_intervalDownload += bytesReceived - m_currentDownload;

    m_currentDownload = bytesReceived;


    uint timeNow = m_timeRecord.elapsed();


    // 超过0.3s更新计算一次速度;

    if (timeNow - m_timeInterval > TIME_INTERVAL)

    {

        qint64 ispeed = m_intervalDownload * 1000 / (timeNow - m_timeInterval);

        QString strSpeed = transformUnit(ispeed, true);

        ui.labelSpeed->setText(strSpeed);

        // 剩余时间;

        qint64 timeRemain = (bytesTotal - bytesReceived) / ispeed;

        ui.labelRemainTime->setText(transformTime(timeRemain));


        ui.labelCurrentDownload->setText(transformUnit(m_currentDownload));

        ui.labelFileSize->setText(transformUnit(bytesTotal));


        m_intervalDownload = 0;

        m_timeInterval = timeNow;

    }

}


// 下载完成;

void MyHttpDownload::onReplyFinished(int statusCode)

{

    // 根据状态码判断当前下载是否出错;

    if (statusCode >= 200 && statusCode < 400)

    {

        qDebug() << "Download Failed";

    }

    else

    {

        qDebug() << "Download Success";

    }

}


// 转换单位;

QString MyHttpDownload::transformUnit(qint64 bytes , bool isSpeed)

{

    QString strUnit = " B";

    if (bytes <= 0)

    {

        bytes = 0;

    }

    else if (bytes < UNIT_KB)

    {

    }

    else if (bytes < UNIT_MB)

    {

        bytes /= UNIT_KB;

        strUnit = " KB";

    }

    else if (bytes < UNIT_GB)

    {

        bytes /= UNIT_MB;

        strUnit = " MB";

    }

    else if (bytes > UNIT_GB)

    {

        bytes /= UNIT_GB;

        strUnit = " GB";

    }


    if (isSpeed)

    {

        strUnit += "/S";

    }

    return QString("%1%2").arg(bytes).arg(strUnit);

}


// 转换时间;

QString MyHttpDownload::transformTime(qint64 seconds)

{

    QString strValue;

    QString strSpacing(" ");

    if (seconds <= 0)

    {

        strValue = QString("%1s").arg(0);

    }

    else if (seconds < 60)

    {

        strValue = QString("%1s").arg(seconds);

    }

    else if (seconds < 60 * 60)

    {

        int nMinute = seconds / 60;

        int nSecond = seconds - nMinute * 60;


        strValue = QString("%1m").arg(nMinute);


        if (nSecond > 0)

            strValue += strSpacing + QString("%1s").arg(nSecond);

    }

    else if (seconds < 60 * 60 * 24)

    {

        int nHour = seconds / (60 * 60);

        int nMinute = (seconds - nHour * 60 * 60) / 60;

        int nSecond = seconds - nHour * 60 * 60 - nMinute * 60;


        strValue = QString("%1h").arg(nHour);


        if (nMinute > 0)

            strValue += strSpacing + QString("%1m").arg(nMinute);


        if (nSecond > 0)

            strValue += strSpacing + QString("%1s").arg(nSecond);

    }

    else

    {

        int nDay = seconds / (60 * 60 * 24);

        int nHour = (seconds - nDay * 60 * 60 * 24) / (60 * 60);

        int nMinute = (seconds - nDay * 60 * 60 * 24 - nHour * 60 * 60) / 60;

        int nSecond = seconds - nDay * 60 * 60 * 24 - nHour * 60 * 60 - nMinute * 60;


        strValue = QString("%1d").arg(nDay);


        if (nHour > 0)

            strValue += strSpacing + QString("%1h").arg(nHour);


        if (nMinute > 0)

            strValue += strSpacing + QString("%1m").arg(nMinute);


        if (nSecond > 0)

            strValue += strSpacing + QString("%1s").arg(nSecond);

    }


    return strValue;

}


标注: 代码注释中提到可以根据url来获取文件名,下方给予解释说明。

QString QUrl::fileName(ComponentFormattingOptions options = FullyDecoded) const

Returns the name of the file, excluding the directory path.

Note that, if this QUrl object is given a path ending in a slash, the name of the file is considered empty.

If the path doesn’t contain any slash, it is fully returned as the fileName.


在Qt助手中我们找到此方法,根据加粗的字段可以看出fileName()方法也可能返回为空,所以不是都有效。


好了,代码到此也就结束了,整体代码不算很难理解,也算是比较简单的一个http请求下载的小实例,后续我将会继续对http下载进行分析,具体包括将当前下载信息保存到本地、对http请求的文件进行分块下载等,欢迎大家一起交流。


代码下载

http://download.csdn.net/detail/goforwardtostep/9643460

看代码其实实现起来不是那么难,但是一步想把功能做全做完善不是那么容易的事,我希望的是能够尽善尽美。在编码过程中也遇到了很多的问题包括下载速度的计算,下载信息的更新,与界面的交互这些都需要处理好。涉及到计算就要把握计算的值是否正确(或者说准确)以及是否计算结果越界导致程序崩溃。就如何控制下载速度的计算,我想不同的人有不同的思路,算出来的结果也不一定完全一致,但是大致时间段的速度应该是相近的,也可以用360等工具进行大致的测试。整篇文章的代码是经过不断地测试,改进之后的,代码中可能存在问题或者有一些不适当,如有还请高人指出。


这两个月来写博客的经历,让我觉得认认真真写一篇博客就像写一篇精美的文章一样,需要经过深思熟虑,前后推敲,反复修改。在写此篇博客之前,我对代码进行了整理,同时也加上了一些注释。其实有时候过多的注释也显得多余,好的代码需要足够精简但在一定程度上又需要一定的可阅读性。而可阅读性的代码胜过大量的注释,所以在编码实现功能的同时需要对自己的代码进行自我审视。子曰: 吾日三省吾身,不亦说乎。因此,在我们飞快地敲击键盘来码代码的同时要放慢速度,留下足够的时间来检测自己的代码。希望在以后的学习过程中,能够养成良好的编程习惯,不快不慢,不急不躁,Keep Moving。


不知不觉,夜已深,前行中的小猪仍在前行中——窗外弥留着虫儿的鸣叫声,在蒙蒙的秋雨中显得更加清脆,悦耳。

————————————————

原文链接:https://blog.csdn.net/goforwardtostep/article/details/52704464

回帖
  • 消灭零回复
相关主题
2020年最新最新Kubernetes视频教程(K8s)教程 2
程序员转型之制作网课变现,月入过万告别996 1
索鸟快传2.0发布啦 1
两个不同网络的电脑怎么实现文件的互相访问呢? 1
网盘多账号登录软件 1
Java实战闲云旅游项目基于vue+element-ui 1
单点登录技术解决方案基于OAuth2.0的网关鉴权RSA算法生成令牌 1
QT5获取剪贴板上文本信息QT设置剪贴板内容 1
springboot2实战在线购物系统电商系统 1
python web实战之爱家租房项目 1
windows COM实用入门教程 1
C++游戏开发之C++实现的水果忍者游戏 1
计算机视觉库opencv教程 1
node.js实战图书管理系统express框架实现 1
C++实战教程之远程桌面远程控制实战 1
相关主题
PHP7报A non well formed numeric value encountered 0
Linux系统下关闭mongodb的几种命令分享 0
mongodb删除数据、删除集合、删除数据库的命令 0
Git&Github极速入门与攻坚实战课程 0
python爬虫教程使用Django和scrapy实现 0
libnetsnmpmibs.so.31: cannot open shared object file 0
数据结构和算法视频教程 0
redis的hash结构怎么删除数据呢? 0
C++和LUA解析器的数据交互实战视频 0
mongodb errmsg" : "too many users are authenticated 0
C++基础入门视频教程 0
用30个小时精通C++视频教程可能吗? 0
C++分布式多线程游戏服务器开发视频教程socket tcp boost库 0
C++培训教程就业班教程 0
layui的util工具格式时间戳为字符串 0
C++实战教程之远程桌面远程控制实战 1
网络安全培训视频教程 0
LINUX_C++软件工程师视频教程高级项目实战 0
C++高级数据结构与算法视频教程 0
跨域问题很头疼?通过配置nginx轻松解决ajax跨域问题 0