1 前言
上一篇文章主要讲解了Http下载的当前实时网速记录,剩余时间,下载大小,唯独没有讲解如何使用断点续传,因为断点续传有点啰嗦和麻烦,所以才单独开辟一章,首先断点续传这个功能不是Http自带的,而是需要服务端那边有相应处理才行,如果服务端没有对应处理 ,你再断也就是断了,翻了天也续不上来,所以不要认为http断点续传是天生就自带的功能,这个主要是看对应服务端是否有这一功能,做了这一处理,一般的大型下载服务端,如Nginx之类的映射下载都是带了此机制的,如果是自定义开发的服务端接口就要看是否带了,一般的约定也是用请求Http的头字段设置Range参数,下面就看具体介绍。
下面是下载的360软件包的断点续传动态效果图,提前放出,知道是咋回事,后面动态效果会有详细说明,这里已下载大小等还有点问题,没有修改,就算是留给大家的问题吧!
公众号:Qt实战,各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发。
公众号:Qt入门和进阶,专门介绍Qt/C++相关知识点学习,帮助Qt开发者更好的深入学习Qt。多位Qt元婴期大神,一步步带你从入门到进阶,走上财务自由之路。
官方店:https://shop114595942.taobao.com//
2 实例讲解
程序还是参考之前的HTTP_Demo 程序,我在其上加上两个按钮,暂停,开始,这样就能很好的诠释断点续传的功能,看下面更改的示意图:(其他相应的功能代码也有所改动,会重新介绍)
HTTP_Demo 头文件新增了一些字段,看代码显示:
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(qint64 bytesReceived, qint64 bytesTotal);
void stopWork();
private slots:
//使用缺省值默认路径触发
void on_pushButton_defaultPath_clicked();
//选择文件夹路径触发
void on_pushButton_selectFilePath_clicked();
//下载按钮触发
void on_pushButton_download_clicked();
//下载完成后的槽
void onFinish();
void onReadyRead();
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void on_pushButton_pause_clicked();
void on_pushButton_start_clicked();
private:
Ui::HTTP_Demo *ui;
QNetworkAccessManager *mNetworkManager = nullptr; //网络管理
QNetworkReply *mPReply = nullptr; //网络响应
QTime m_timeRecord; //记录下载实时网速用时
int m_timeInterval= 0; //时间记录的间隔差距
qint64 m_bytesReceived = 0; ///当前的接收数据大小
qint64 m_bytesTotal = 0; ///当前下载的数据总大小
qint64 m_currentDownloadSize = 0; ///当前已经下载的大小
qint64 m_intervalDownloadSize = 0; ///计算速度之前下载的大小的累计和,计算速度用
//新增---
bool m_isStop = true; //使用点击了暂停按钮
qint64 m_bytesCurrentReceived = 0;
bool m_isSupportBreakPoint = true;
QString m_fileName;
QByteArray m_ReplyData;
};
新增加了开始和暂停按钮,下面看暂停按钮的代码:
void HTTP_Demo::on_pushButton_pause_clicked()
{
// 这里m_isStop变量为了保护多次点击暂停下载按钮,导致m_bytesCurrentReceived 被不停累加;
if (!m_isStop)
{
//记录当前已经下载字节数
m_bytesCurrentReceived += m_bytesReceived;
stopWork();
}
}
// 停止下载工作;
void HTTP_Demo::stopWork()
{
m_isStop = true;
if (mPReply != nullptr)
{
disconnect (mPReply, &QNetworkReply::finished, this, &HTTP_Demo::onFinish) ;
disconnect (mPReply, &QNetworkReply::readyRead, this, &HTTP_Demo::onReadyRead) ;
disconnect (mPReply, &QNetworkReply::downloadProgress, this, &HTTP_Demo::onDownloadProgress) ;
mPReply->abort();
mPReply->deleteLater();
mPReply = nullptr;
}
}
暂停按钮主要是记录了当前下载的所有字节数,然后停止下载工作,后续继续的话就要用到此字段来进行断点续传功能了,下面看开始按钮代码,就会有用到, 开始按钮就是下载按钮的代码,两个共用的,所以主要改动也在这里:
void HTTP_Demo::on_pushButton_download_clicked()
{
if(m_isStop)
{
m_isStop = false;
//开始下载
QString urlSpec = ui->lineEdit_downloadURL->text ().trimmed() ;
if (urlSpec. isEmpty() )
{
QMessageBox::information(this, "错误","请指定需要下载的URL") ;
m_isStop = true;
return;
}
bool isHttpsRequest;
//不区分大小写
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())) ;
m_isStop = true;
return;
}
QString tempDir =ui->lineEdit_fileSavePath->text().trimmed() ;
if (tempDir. isEmpty())
{
QMessageBox::information(this, "错误", "请指定保存下载文件的目录") ;
m_isStop = true;
return;
}
QString fileName = newUrl.fileName ();
//如果找不到文件名称,就默认写一个
if(fileName.isEmpty())
{
fileName = "temp.date";
}
QString fullFileName =tempDir+fileName ;
----新增改动
// 如果当前下载的字节数为0那么说明未下载过或者重新下载
// 则需要检测本地是否存在之前下载的临时文件,如果有则删除
if (m_bytesCurrentReceived <= 0)
{
m_ReplyData.clear();
QFile::remove(fullFileName);
}
else if (QFile::exists (fullFileName))
{
QFile::remove(fullFileName) ;
}
m_fileName = fullFileName;
ui->pushButton_download->setEnabled(false) ;
if(mNetworkManager == nullptr)
{
mNetworkManager = new QNetworkAccessManager(this);
}
QNetworkRequest request2(newUrl);
-----新增改动
// 如果支持断点续传,则设置请求头信息
if (m_isSupportBreakPoint)
{
qInfo()<<"进入断点续传";
QString strRange = QString("bytes=%1-").arg(m_bytesCurrentReceived);
request2.setRawHeader("Range", strRange.toLatin1());
}
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();
}
}
然后就是onReadyRead代码改动,之前是写入文件,后面测试发现写入文件有差异化,可能导致失败,所以改成只记录相关数据:
void HTTP_Demo::onReadyRead()
{
if(!m_isStop)
{
m_ReplyData += mPReply->readAll();
}
}
onDownloadProgress 代码改动如下:
void HTTP_Demo::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
m_bytesReceived = bytesReceived;
m_bytesTotal = bytesTotal;
//下载进程,设置最大值为100
ui->progressBar->setMinimum(0);
ui->progressBar->setMaximum(100) ;
calculateDownloadProgress(m_bytesReceived+m_bytesCurrentReceived, m_bytesTotal + m_bytesCurrentReceived);
}
其中calculateDownloadProgress方法没有大改动,只是将全局变量m_bytesReceived 改成了 局部变量,下面是改动代码:
void HTTP_Demo::calculateDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
//计算您当前的进度
float currentProgressValue = static_cast<float>(bytesReceived)/static_cast<float>(bytesTotal)*100;
ui->progressBar-> setValue (currentProgressValue) ;
// 输出当前下载进度;
// 用到除法需要注意除0错误;
//qDebug() << QString("%1").arg(bytesReceived * 100 / bytesTotal + 1);
// m_intervalDownload 为下次计算速度之前的下载字节数;
m_intervalDownloadSize += bytesReceived - m_currentDownloadSize;
m_currentDownloadSize = bytesReceived;
int timeNow = m_timeRecord.elapsed();
// 超过0.3s更新计算一次速度;
if (timeNow - m_timeInterval > TIME_INTERVAL)
{
qint64 ispeed = m_intervalDownloadSize * 1000 / (timeNow - m_timeInterval);
QString strSpeed = speed(ispeed);
ui->label_speed->setText("当前下载速度:"+strSpeed);
// 剩余时间;
qint64 timeRemain = 0;
if (ispeed != 0)
{
timeRemain = (bytesTotal - bytesReceived) / ispeed;
}
//转换单位(下载剩余时间)
QString strTimeRemain = timeFormat(timeRemain);
ui->label_time->setText("剩余时间:"+strTimeRemain);
//当前已下载的数据大小
QString currentFileDownSize = size(m_currentDownloadSize);
ui->label_downSize->setText("已下载大小:"+currentFileDownSize);
//下载文件的总大小
QString totalFileSize = size(bytesTotal);
ui->label_totalSize->setText("文件总大小:"+totalFileSize);
m_intervalDownloadSize = 0;
m_timeInterval = timeNow;
}
}
最后就是onFinish了,之前的写法onFinish只是析构了mPReply,改动后,是最关键的一步,就是将数据写入文件,代码如下:
void HTTP_Demo::onFinish()
{
mPReply->deleteLater() ;
mPReply = nullptr;
QFile file(m_fileName);
if(file.open(QIODevice::WriteOnly))
{
file.write(m_ReplyData);
}
file.close();
ui ->pushButton_download->setEnabled(true) ;
}
3 动态图显示效果
为了呈现更好的效果,我这边还是使用360安装软件包来做测试,测试链接依然是之前的,从360官网获取的,大家可以自行测试!
https://dl.360safe.com/pclianmeng/n/1__4000943.exe?source=%E6%9C%A8%E9%A9%AC%E6%9F%A5%E6%9D%80
从动态图中可以看到,下载是成功的,可以中途暂停,也可以继续下载,然后下载软件也是能打开的,刚才看了下动图发现显示已下载大小有点问题,这个问题就留给大家解决了,也不是难点, 好了,至于其他更细节的断点续传就靠各位自己去深入研究了,还是那句话,断点续传一定要看对方下载服务端支持不,不过一般的外用下载连接都是支持的,私人服务端接口可能就不一定,好了就到这里结束了!