Qt支持断点续传的HTTP下载实战教程

0 385
索鸟 2021-03-22
需要:0索币

开发过程中遇到了一些坑,花了半天时间搞。出现了很多理论上不应该出现的bug。


做这个的目的,主要是为了巩固一下对Qt网络部分接口的使用,以及http协议的记忆。


思路主要是,解析URL——>获取响应头——>获取响应状态码——>请求下载——>回读当前文件字节数进行断点续传。


需要注意,访问https前,需要添加libcrypto-1_1.dll与libssl-1_1.dll到Qt\5.14.0\mingw73_32\bin或者exe目录下。


以上DLL下载地址:http://slproweb.com/products/Win32OpenSSL.html


需要注意一下32位和64位,应下载与自己Qt编译器位数一致的版本。下载好后直接安装,然后到安装目录拷贝即可。


一、gitHub地址

https://github.com/KindMans/HttpDownLoad


 


二、功能

支持输入url网址进行在线下载,具备断点续传功能。


后期扩展:结合qml树列表与多线程,模拟出迅雷的下载效果。


 


三、目前存在的问题

QEventLoop *loop = new QEventLoop;

connect(m_netWorkManager, SIGNAL(finished(QNetworkReply*)), loop, SLOT(quit()));

m_reply = m_netWorkManager->get(request);

loop->exec();

通过以上get访问到需要跳转的url时,速度明显快于不需要跳转的。具体原因还不清楚。


四、界面效果

粗略的绘制了一下界面,比较丑,主要以实现效果为主。


五、主要代码

启动下载:


void Http::startDownLoad(const QString &url)

{

    m_url = url;

    if(m_url.isEmpty()) return;

 

    if(!m_IsDownloading)

    {

        //获取请求头

        QNetworkRequest request;

        QUrl url = QUrl(m_url);

        request.setUrl(url);

        m_fileName = url.fileName();

        qDebug()<<"fileName = "<<m_fileName;

        m_reply = m_netWorkManager->head(request);

        m_state = requestHead;

        getCurrentFileSize();

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

    }

}

通过setUrl来装载我们请求的地址,随后获取请求头,请求结束后,进入槽函数做处理。


暂停下载:


void Http::stopDownLoad()

{

    if(m_reply == nullptr) return;

 

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

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

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

 

    m_reply->abort();

    m_reply->deleteLater();

 

    m_file.close();

    getCurrentFileSize();

 

    m_IsDownloading = false;

}

暂停下载主要是断开信号槽的连接,然后释放资源和关闭文件操作符。


文件大小回读:


void Http::getCurrentFileSize()

{

    QFileInfo fileInfo(m_fileName);

    if(fileInfo.exists())

    {

        m_currentLoadedBytes = fileInfo.size();

    }

    else

    {

        m_currentLoadedBytes = 0;

    }

}

通过QFileInfo类提供的size()接口,获取文件当前的字节大小。


槽函数onfinishedRequest():


void Http::onfinishedRequest()

{

    if(m_reply==nullptr) return;

 

    if(m_state == requestHead)

    {       

        m_fileSize = m_reply->rawHeader("Content-Length").toInt();

        qDebug()<<"m_fileSize = "<<m_fileSize;

        if(m_currentLoadedBytes == m_fileSize)

        {

            qDebug()<<"文件已经存在!";

            return;

        }

 

        QEventLoop *loop = new QEventLoop;

        connect(m_netWorkManager, SIGNAL(finished(QNetworkReply*)), loop, SLOT(quit()));

        QNetworkRequest request;

        request.setUrl(m_url);

        m_reply = m_netWorkManager->get(request);

        loop->exec();

 

        //获取状态码

        m_statusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();

        qDebug()<<"statusCode="<<m_statusCode;

        m_state = requestBody;

    }

 

    QNetworkRequest request;

    if(m_statusCode==200)

    {

        request.setUrl(m_url);

    }

    else if(m_statusCode == 302)    //存在转调url

    {

        //获取实际下载地址

        QUrl realUrl = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();

        request.setUrl(realUrl);

    }

    else

    {

        return;

    }

 

    QString downLoadSize = QString::number(m_fileSize);

    QString selectSize = QString("bytes=%1-%2").arg(m_currentLoadedBytes).arg(downLoadSize);

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

    m_reply = m_netWorkManager->get(request);

 

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

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

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

}

这里需要注意的是做了一个转调功能,因为有些资源的实际下载地址并不是当前所给的Url。当返回302的时候,说明需要获取背后正确的下载地址。


槽函数onReadyRead():


void Http::onReadyRead()

{

    if(m_reply==nullptr) return;

 

    if(!m_file.isOpen())

    {

        m_file.setFileName(m_fileName);

        m_file.open(QIODevice::WriteOnly|QIODevice::Append);

    }

 

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

 

    m_downLoadedBytes =m_file.size();

 

    emit fileDownloadProgress(m_downLoadedBytes, m_fileSize);

    if(m_file.size() == m_fileSize)

    {

        qDebug()<<"download finished!";

        stopDownLoad();

    }

}

将接收到的数据,写入文件中,并且将当前下载字节数通过信号槽发送出去,提供给进度条对话框使用。


当下载的字节总大小等于请求时服务器返回过来的文件大小时,就认为下载结束。


当然,更严谨的做法是再计算文件的MD5码,与服务器回传的MD5码进行比较,以确保下载的是正确的文件。

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

原文链接:https://blog.csdn.net/c_shell_python/article/details/106294014

回帖
  • 消灭零回复
相关主题
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