一,前提介绍
服务器端要支持range,支持断点下载功能。
客户端下载部分分两步:1.获取文件大小。
2.设置range下载。
二,实现代码,直接可以用。
/***********************************************************
! @File : downloadmanager.h
* @Brief : 多段下载
* @Details : 服务器要支持range,断点续传功能才可,通过将文件分成多个range,线程完成range的工作
* @Author : soda
* @Date : 2023-12-05 09:51:09
* @Version : v1.0
* @Copyright : Copyright By soda, All Rights Reserved
***********************************************************/
#ifndef DOWNLOADMANAGER_H
#define DOWNLOADMANAGER_H
#include <QtNetwork>
#include <QtCore>
typedef struct TaskInfo {
int order = 0; // 编号
QUrl url; // url
int startPos = -1; // 起始下载位置
int endPos = -1; // 结束下载位置
int haveBytes = 0; // 下载的大小
bool bFinish = false; // 是否结束
bool bRange = false; // 是否是分段
} TaskInfo;
class DownloadTask : public QObject
{
Q_OBJECT
public:
DownloadTask(QObject* parent = 0)
:QObject(parent){};
~DownloadTask() {};
QNetworkReply* reply() { return m_reply; };
void start(TaskInfo& task) {
m_task = task;
if(m_manager == nullptr)
m_manager = new QNetworkAccessManager(this);
QNetworkRequest request(m_task.url);
if(m_task.bRange) {
request.setRawHeader(QByteArray("Range"), QString("bytes=%1-%2").arg(m_task.startPos).arg(m_task.endPos).toLocal8Bit());
}
m_reply = m_manager->get(request);
connect(m_reply, &QNetworkReply::readyRead, \
this, [this](){ emit readyRead(m_task.order); }, Qt::QueuedConnection);
connect(m_reply, &QNetworkReply::finished,
this, [this](){ emit finished(m_task.order); }, Qt::QueuedConnection);
connect(m_reply, &QNetworkReply::errorOccurred,
this, [this](){ emit errorOccurred(m_task.order); }, Qt::QueuedConnection);
}
signals:
void finished(int order);
void readyRead(int order);
void errorOccurred(int order);
private:
QNetworkAccessManager* m_manager = nullptr;
QNetworkReply* m_reply = nullptr;
TaskInfo m_task;
};
class DownloadManager: public QObject
{
Q_OBJECT
public:
explicit DownloadManager(QObject *parent = nullptr);
int calcFileSize(const QUrl &url);
bool startDownload(const QUrl url, const QString path, int workNumber = 5);
signals:
void finished();
public slots:
void downloadReadyRead(int order);
void downloadFinished(int order);
void downloadError(int order);
private:
QVector<TaskInfo> m_vecData;
QVector<DownloadTask*> m_vecTask;
QElapsedTimer m_downloadTimer;
QFile m_output;
};
#endif
#include "downloadmanager.h"
DownloadManager::DownloadManager(QObject *parent)
: QObject(parent)
{
QTimer::singleShot(1000, this, [this](){
QUrl url = QUrl("https://mirrors.tuna.tsinghua.edu.cn/qt/official_releases/jom/jom_0_6_08.zip");
this->startDownload(url, "D://TestDownload.exe", 4);
});
}
bool DownloadManager::startDownload(const QUrl url, const QString path, int workNumber)
{
int fileSize = calcFileSize(url);
//int fileSize = 19;
qDebug() << "fileSize: " << fileSize;
m_output.setFileName(path);
if (!m_output.open(QIODevice::WriteOnly)) {
qFatal("downlaod path can not wirte!");
return false;
}
if(fileSize < 0) {
TaskInfo info;
info.url = url;
info.startPos = 0;
info.endPos = fileSize;
info.bRange = true;
m_vecData.push_back(info);
DownloadTask* task = new DownloadTask(this);
connect(task, &DownloadTask::readyRead, \
this, &DownloadManager::downloadReadyRead, Qt::QueuedConnection);
connect(task, &DownloadTask::finished, \
this, &DownloadManager::downloadFinished, Qt::QueuedConnection);
connect(task, &DownloadTask::errorOccurred, \
this, &DownloadManager::downloadError, Qt::QueuedConnection);
m_vecTask.push_back(task);
m_vecTask.back()->start(m_vecData.back());
} else {
for(int idx = 0; idx < workNumber; idx++) {
TaskInfo info;
info.order = idx;
info.startPos = fileSize * idx / workNumber;
info.endPos = fileSize * (idx+1) / workNumber;
if( idx != 0 ) info.startPos--;
info.bRange = true;
info.url = url;
qDebug() << "start pos: " << info.startPos;
qDebug() << "end pos: " << info.endPos;
m_vecData.push_back(info);
DownloadTask* task = new DownloadTask(this);
connect(task, &DownloadTask::readyRead, \
this, &DownloadManager::downloadReadyRead, Qt::QueuedConnection);
connect(task, &DownloadTask::finished, \
this, &DownloadManager::downloadFinished, Qt::QueuedConnection);
connect(task, &DownloadTask::errorOccurred, \
this, &DownloadManager::downloadError, Qt::QueuedConnection);
m_vecTask.push_back(task);
m_vecTask.back()->start(m_vecData.back());
}
}
m_downloadTimer.start();
return true;
}
int DownloadManager::calcFileSize(const QUrl &url)
{
int fileSize = -1;
QNetworkAccessManager manager;
QNetworkRequest request;
request.setUrl(url);
QNetworkReply* headReply = manager.head(request);
QEventLoop loop;
QTimer::singleShot(5000, &loop, SLOT(quit()));
connect(headReply, &QNetworkReply::finished, [&loop, &headReply, &fileSize](){
if (headReply->hasRawHeader(QString("Content-Length").toUtf8())) {
QString size = headReply->rawHeader(QString("Content-Length").toUtf8());
qDebug() << "size: " << size;
fileSize = size.toInt();
qDebug() << "fileSize: " << size;
}
loop.quit();
});
loop.exec();
return fileSize;
}
void DownloadManager::downloadReadyRead(int order)
{
qDebug() << "order: " << order;
// if (!m_output.seek(m_vecData[order].startPos + m_vecData[order].haveBytes))
// return;
QByteArray byteData = m_vecTask[order]->reply()->readAll();
m_output.seek(m_vecData[order].startPos + m_vecData[order].haveBytes);
m_output.write(byteData);
m_vecData[order].haveBytes += byteData.size();
// // calculate the download speed
//double speed = bytesReceived * 1000.0 / downloadTimer.elapsed();
// QString unit;
// if (speed < 1024) {
// unit = "bytes/sec";
// } else if (speed < 1024*1024) {
// speed /= 1024;
// unit = "kB/s";
// } else {
// speed /= 1024*1024;
// unit = "MB/s";
// }
}
void DownloadManager::downloadFinished(int order)
{
m_vecData[order].bFinish = true;
auto result = std::all_of(m_vecData.begin(), m_vecData.end(), [](TaskInfo info){
return (info.bFinish == true);
});
if(result) {
m_output.close();
}
}
void DownloadManager::downloadError(int order)
{
qDebug() << m_vecTask[order]->reply()->error();
}