更新目录结构
This commit is contained in:
73
Components/MultiplethreadDownload/.gitignore
vendored
Normal file
73
Components/MultiplethreadDownload/.gitignore
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
# This file is used to ignore files which are generated
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
*~
|
||||
*.autosave
|
||||
*.a
|
||||
*.core
|
||||
*.moc
|
||||
*.o
|
||||
*.obj
|
||||
*.orig
|
||||
*.rej
|
||||
*.so
|
||||
*.so.*
|
||||
*_pch.h.cpp
|
||||
*_resource.rc
|
||||
*.qm
|
||||
.#*
|
||||
*.*#
|
||||
core
|
||||
!core/
|
||||
tags
|
||||
.DS_Store
|
||||
.directory
|
||||
*.debug
|
||||
Makefile*
|
||||
*.prl
|
||||
*.app
|
||||
moc_*.cpp
|
||||
ui_*.h
|
||||
qrc_*.cpp
|
||||
Thumbs.db
|
||||
*.res
|
||||
*.rc
|
||||
/.qmake.cache
|
||||
/.qmake.stash
|
||||
|
||||
# qtcreator generated files
|
||||
*.pro.user*
|
||||
|
||||
# xemacs temporary files
|
||||
*.flc
|
||||
|
||||
# Vim temporary files
|
||||
.*.swp
|
||||
|
||||
# Visual Studio generated files
|
||||
*.ib_pdb_index
|
||||
*.idb
|
||||
*.ilk
|
||||
*.pdb
|
||||
*.sln
|
||||
*.suo
|
||||
*.vcproj
|
||||
*vcproj.*.*.user
|
||||
*.ncb
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.vcxproj
|
||||
*vcxproj.*
|
||||
|
||||
# MinGW generated files
|
||||
*.Debug
|
||||
*.Release
|
||||
|
||||
# Python byte code
|
||||
*.pyc
|
||||
|
||||
# Binaries
|
||||
# --------
|
||||
*.dll
|
||||
*.exe
|
||||
|
||||
28
Components/MultiplethreadDownload/MultiplethreadDownload.pro
Normal file
28
Components/MultiplethreadDownload/MultiplethreadDownload.pro
Normal file
@@ -0,0 +1,28 @@
|
||||
QT += core gui network concurrent
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
CONFIG += c++17
|
||||
|
||||
# You can make your code fail to compile if it uses deprecated APIs.
|
||||
# In order to do so, uncomment the following line.
|
||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||
|
||||
SOURCES += \
|
||||
downloadworker.cpp \
|
||||
main.cpp \
|
||||
utils.cpp \
|
||||
widget.cpp
|
||||
|
||||
HEADERS += \
|
||||
downloadworker.h \
|
||||
utils.h \
|
||||
widget.h
|
||||
|
||||
FORMS += \
|
||||
widget.ui
|
||||
|
||||
# Default rules for deployment.
|
||||
qnx: target.path = /tmp/$${TARGET}/bin
|
||||
else: unix:!android: target.path = /opt/$${TARGET}/bin
|
||||
!isEmpty(target.path): INSTALLS += target
|
||||
103
Components/MultiplethreadDownload/REAMDE.md
Normal file
103
Components/MultiplethreadDownload/REAMDE.md
Normal file
@@ -0,0 +1,103 @@
|
||||
文件大小为: 2521709
|
||||
开始下载文件
|
||||
开始下载数据: 1891281~2521709 -> writePos Start 1891281
|
||||
开始下载数据: 0~630426 -> writePos Start 0
|
||||
开始下载数据: 630427~1260853 -> writePos Start 630427
|
||||
开始下载数据: 1260854~1891280 -> writePos Start 1260854
|
||||
|
||||
|
||||
文件大小为: 89937570
|
||||
开始下载文件
|
||||
开始下载数据: " 0~22484391 -> writePos Start 0"
|
||||
开始下载数据: " 22484392~44968783 -> writePos Start 22484392"
|
||||
开始下载数据: " 44968784~67453175 -> writePos Start 44968784"
|
||||
开始下载数据: " 67453176~89937570 -> writePos Start 67453176
|
||||
|
||||
|
||||
# 星火商店多线程下载说明
|
||||
现在的这个多线程下载已经可以了,但是暂停恢复不太好控制,所以还是用 QThread 来做暂停控制,即使线程数超过CPU
|
||||
核数问题也不大,因为下载并不是CPU密集操作,完全没问题的。总体的精细的线程池控制后续技术强大了再来封装好了。
|
||||
|
||||
先做好这个功能,后续再来研究DTK库,各个组件的使用,已经如何自己封装漂亮的控件,摆脱DTK的依赖。
|
||||
再后面,写一个Qt开发Linux应用的教程,先建立起星火商店单独的开发者博客,专门用来发技术文章。
|
||||
|
||||
星火商店的几个源服务器,源列表写在
|
||||
```sh
|
||||
$ cat /etc/apt/sources.list.d/sparkstore.list
|
||||
deb [by-hash=force] https://sucdn.jerrywang.top /
|
||||
```
|
||||
|
||||
服务器源列表 http://dcstore.shenmo.tech/store/server.list
|
||||
```js
|
||||
https://sucdn.jerrywang.top/
|
||||
国内推荐 China Lines
|
||||
https://sucdn.jerrywang.top/
|
||||
https://store.deepinos.org.cn/
|
||||
国外推荐 Global Lines
|
||||
开发者模式 Dev only
|
||||
http://localhost:8080/
|
||||
用户贡献 Community lines
|
||||
```
|
||||
|
||||
也就是目前的就两个服务器了
|
||||
* https://sucdn.jerrywang.top/
|
||||
* https://store.deepinos.org.cn/
|
||||
|
||||
|
||||
原来的单线程相关代码
|
||||
```cpp
|
||||
// 在处理下载进度的代码中,会一直检测下载是否取消
|
||||
void Widget::updateDataReadProgress(qint64 bytesRead, qint64 totalBytes)
|
||||
{
|
||||
if(totalBytes <= 0) return;
|
||||
download_list[nowDownload-1].setMax(10000); // 最大值
|
||||
download_list[nowDownload-1].setValue((bytesRead*10000)/totalBytes); // 当前值
|
||||
download_size=bytesRead;
|
||||
if(download_list[nowDownload-1].close){ // 随时检测下载是否被取消
|
||||
download_list[nowDownload-1].closeDownload();
|
||||
httpFinished();
|
||||
}
|
||||
}
|
||||
// 但是这里的 download_list[nowDownload-1].close 是在哪里修改的呢?
|
||||
downloadlist download_list[LIST_MAX];
|
||||
download_list 是一个下载进度组件,维持动画的,还不错。
|
||||
|
||||
// 星火商店记录下载进度的代码
|
||||
void Widget::updateDataReadProgress(qint64 bytesRead, qint64 totalBytes)
|
||||
{
|
||||
if(totalBytes <= 0) return;
|
||||
download_list[nowDownload-1].setMax(10000); // 最大值
|
||||
download_list[nowDownload-1].setValue((bytesRead*10000)/totalBytes); // 当前值
|
||||
download_size=bytesRead;
|
||||
if(download_list[nowDownload-1].close){ // 随时检测下载是否被取消
|
||||
download_list[nowDownload-1].closeDownload();
|
||||
httpFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void Widget::httpFinished() // 完成下载
|
||||
{
|
||||
file->flush();
|
||||
file->close();
|
||||
reply->deleteLater();
|
||||
reply = nullptr;
|
||||
delete file;
|
||||
file = nullptr;
|
||||
isdownload=false;
|
||||
isBusy=false;
|
||||
}
|
||||
```
|
||||
|
||||
然后那个多线程下载组件需要的几个功能点:
|
||||
* 下载进度,以及下载速度记录
|
||||
* 下载取消功能
|
||||
* 读取设置,允许设置单线程下载 这个可以放到后面做,优先实现上面两个
|
||||
|
||||
多域名测试下载,现在单个域名至多5M,DNS解析均衡有延迟,因为DNS会有缓存。
|
||||
```url
|
||||
sucdn1.jerrywang.top
|
||||
sucdn2.jerrywang.top
|
||||
sucdn3.jerrywang.top
|
||||
sucdn4.jerrywang.top
|
||||
sucdn5.jerrywang.top
|
||||
```
|
||||
183
Components/MultiplethreadDownload/downloadworker.cpp
Normal file
183
Components/MultiplethreadDownload/downloadworker.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
#include "downloadworker.h"
|
||||
#include <QIODevice>
|
||||
#include <QEventLoop>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QDebug>
|
||||
#include <QThread>
|
||||
|
||||
DownloadWorker::DownloadWorker(QObject *parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void DownloadWorker::setIdentifier(int identifier)
|
||||
{
|
||||
this->identifier = identifier;
|
||||
}
|
||||
|
||||
void DownloadWorker::setParamter(const QString &url, QPair<qint64, qint64> range, QFile *file)
|
||||
{
|
||||
this->url = url;
|
||||
this->startPos = range.first;
|
||||
this->endPos = range.second;
|
||||
this->file = file;
|
||||
}
|
||||
|
||||
qint64 DownloadWorker::getReceivedPos()
|
||||
{
|
||||
return receivedPos;
|
||||
}
|
||||
|
||||
void DownloadWorker::doWork()
|
||||
{
|
||||
mgr = new QNetworkAccessManager(this);
|
||||
QNetworkRequest request;
|
||||
request.setUrl(url);
|
||||
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
request.setRawHeader("Range", QString("bytes=%1-%2").arg(startPos)
|
||||
.arg(endPos).toLocal8Bit());
|
||||
reply = mgr->get(request);
|
||||
qDebug() << "开始下载数据:" << QString(" %1~%2 -> writePos Start %3")
|
||||
.arg(startPos).arg(endPos).arg(receivedPos);
|
||||
connect(reply, &QNetworkReply::errorOccurred, [this](QNetworkReply::NetworkError error){
|
||||
if (error != QNetworkReply::NoError) {
|
||||
qDebug() << "出错了:" << reply->errorString();
|
||||
}
|
||||
});
|
||||
connect(reply, &QNetworkReply::finished, mgr, &QNetworkAccessManager::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::readyRead, this, &DownloadWorker::dataReady);
|
||||
connect(reply, &QNetworkReply::finished, this, &DownloadWorker::slotFinish);
|
||||
connect(reply, &QNetworkReply::downloadProgress, this, &DownloadWorker::handleProcess);
|
||||
|
||||
}
|
||||
|
||||
void DownloadWorker::doStop()
|
||||
{
|
||||
reply->deleteLater();
|
||||
reply = nullptr;
|
||||
mgr->deleteLater();
|
||||
mgr = nullptr;
|
||||
}
|
||||
|
||||
void DownloadWorker::dataReady()
|
||||
{
|
||||
qDebug() << QString("测试是否执行: %1 %2").arg(startPos).arg(receivedPos);
|
||||
QByteArray data = reply->readAll();
|
||||
file->seek(startPos + receivedPos);
|
||||
file->write(data);
|
||||
receivedPos += data.size();
|
||||
}
|
||||
|
||||
void DownloadWorker::slotFinish()
|
||||
{
|
||||
file->flush();
|
||||
qDebug() << "数据块下载完毕:" << QString(" %1~%2 -> writePos Start %3")
|
||||
.arg(startPos).arg(endPos).arg(receivedPos);
|
||||
emit workFinished();
|
||||
}
|
||||
|
||||
void DownloadWorker::handleProcess(qint64, qint64)
|
||||
{
|
||||
emit this->downloadProcess();
|
||||
}
|
||||
|
||||
|
||||
DownloadController::DownloadController(QObject *parent)
|
||||
{
|
||||
this->threadNum = 4;
|
||||
}
|
||||
|
||||
void DownloadController::setFilename(QString filename)
|
||||
{
|
||||
this->filename = filename;
|
||||
}
|
||||
|
||||
void DownloadController::setThreadNum(int threadNum)
|
||||
{
|
||||
this->threadNum = threadNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 开始下载
|
||||
*/
|
||||
void DownloadController::startDownload(const QString &url, qint64 fileSize)
|
||||
{
|
||||
finish = 0;
|
||||
|
||||
// 下载任务等分,计算每个线程的下载数据
|
||||
this->fileSize = fileSize;
|
||||
qint64 segmentSize = fileSize / threadNum;
|
||||
ranges.resize(threadNum);
|
||||
QVector<qint64> receivedBytes;
|
||||
receivedBytes.resize(threadNum);
|
||||
for (int i = 0; i < threadNum; i++) {
|
||||
ranges[i].first = i * segmentSize;
|
||||
ranges[i].second = i * segmentSize + segmentSize - 1;
|
||||
receivedBytes[i] = 0;
|
||||
}
|
||||
ranges[threadNum-1].second = fileSize; // 余数部分加入最后一个
|
||||
|
||||
// 打开文件
|
||||
file = new QFile;
|
||||
file->setFileName(filename);
|
||||
if (file->exists())
|
||||
file->remove();
|
||||
if (!file->open(QIODevice::WriteOnly)) {
|
||||
delete file;
|
||||
file = nullptr;
|
||||
emit errorOccur(file->errorString());
|
||||
return;
|
||||
}
|
||||
file->resize(fileSize);
|
||||
|
||||
// 创建下载线程
|
||||
for(int i = 0; i < ranges.size(); i++) {
|
||||
qDebug() << QString("第%1个下载请求:%2-%3").arg(i).arg(ranges.at(i).first).arg(ranges.at(i).second);
|
||||
auto worker = new DownloadWorker(this);
|
||||
auto range = ranges.at(i);
|
||||
worker->setIdentifier(i);
|
||||
worker->setParamter(url, range, file);
|
||||
workers.append(worker);
|
||||
connect(worker, &DownloadWorker::downloadProcess, this, &DownloadController::handleProcess);
|
||||
connect(worker, &DownloadWorker::workFinished, this, &DownloadController::chunkDownloadFinish);
|
||||
worker->doWork();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 停止下载
|
||||
*/
|
||||
void DownloadController::stopDownload()
|
||||
{
|
||||
for(int i = 0; i < workers.size(); i++) {
|
||||
workers.at(i)->doStop();
|
||||
}
|
||||
file->flush();
|
||||
file->close();
|
||||
delete file;
|
||||
file = nullptr;
|
||||
}
|
||||
|
||||
|
||||
void DownloadController::handleProcess()
|
||||
{
|
||||
qint64 bytesReceived = 0;
|
||||
for(int i = 0; i < workers.size(); i++) {
|
||||
bytesReceived += workers.at(i)->getReceivedPos();
|
||||
}
|
||||
qDebug() << QString("下载进度 %1-%2").arg(bytesReceived).arg(fileSize);
|
||||
emit downloadProcess(bytesReceived, fileSize);
|
||||
}
|
||||
|
||||
void DownloadController::chunkDownloadFinish()
|
||||
{
|
||||
finish++;
|
||||
if (finish == threadNum) {
|
||||
emit downloadFinished();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
71
Components/MultiplethreadDownload/downloadworker.h
Normal file
71
Components/MultiplethreadDownload/downloadworker.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#ifndef DOWNLOADWORKER_H
|
||||
#define DOWNLOADWORKER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QFile>
|
||||
#include <QNetworkReply>
|
||||
|
||||
class DownloadWorker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DownloadWorker(QObject *parent = nullptr);
|
||||
void setIdentifier(int identifier);
|
||||
void setParamter(const QString &url, QPair<qint64, qint64> range, QFile *flle);
|
||||
qint64 getReceivedPos();
|
||||
|
||||
public slots:
|
||||
void doWork();
|
||||
void doStop();
|
||||
void dataReady();
|
||||
void slotFinish();
|
||||
void handleProcess(qint64, qint64);
|
||||
|
||||
signals:
|
||||
void resultReady(int identifier, QByteArray data);
|
||||
void testSignals();
|
||||
void workFinished();
|
||||
void downloadProcess();
|
||||
|
||||
private:
|
||||
int identifier;
|
||||
QString url;
|
||||
qint64 startPos;
|
||||
qint64 endPos;
|
||||
qint64 receivedPos = 0;
|
||||
QNetworkReply *reply;
|
||||
QNetworkAccessManager *mgr;
|
||||
QFile *file;
|
||||
};
|
||||
|
||||
class DownloadController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DownloadController(QObject *parent = nullptr);
|
||||
void setFilename(QString filename);
|
||||
void setThreadNum(int threadNum);
|
||||
void startDownload(const QString &url, qint64 fileSize);
|
||||
void stopDownload();
|
||||
|
||||
public slots:
|
||||
void handleProcess();
|
||||
void chunkDownloadFinish();
|
||||
|
||||
signals:
|
||||
void errorOccur(const QString& msg);
|
||||
void downloadProcess(qint64, qint64);
|
||||
void downloadFinished();
|
||||
|
||||
private:
|
||||
int threadNum;
|
||||
QString filename;
|
||||
qint64 fileSize;
|
||||
QVector<QPair<qint64, qint64>> ranges;
|
||||
QFile *file;
|
||||
QList<DownloadWorker*> workers;
|
||||
int finish = 0;
|
||||
};
|
||||
|
||||
#endif // FILEDOWNLOADWORKER_H
|
||||
13
Components/MultiplethreadDownload/main.cpp
Normal file
13
Components/MultiplethreadDownload/main.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#include "widget.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QScreen>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
Widget w;
|
||||
w.show();
|
||||
w.move(QApplication::screens().at(0)->geometry().center() - w.frameGeometry().center());
|
||||
return a.exec();
|
||||
}
|
||||
2104
Components/MultiplethreadDownload/process.csv
Normal file
2104
Components/MultiplethreadDownload/process.csv
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Components/MultiplethreadDownload/screenshot.png
Normal file
BIN
Components/MultiplethreadDownload/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
21
Components/MultiplethreadDownload/utils.cpp
Normal file
21
Components/MultiplethreadDownload/utils.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "utils.h"
|
||||
#include <QStringList>
|
||||
#include <QLocale>
|
||||
|
||||
|
||||
QString Utils::sizeFormat(qint64 bytes)
|
||||
{
|
||||
qreal size = bytes;
|
||||
QStringList list;
|
||||
list << "KB" << "MB" << "GB" << "TB";
|
||||
|
||||
QStringListIterator i(list);
|
||||
QString unit("bytes");
|
||||
|
||||
while (size >= 1024.0 && i.hasNext()) {
|
||||
unit = i.next();
|
||||
size /= 1024.0;
|
||||
}
|
||||
return QString().setNum(size, 'f', 2) + " " + unit;
|
||||
}
|
||||
|
||||
12
Components/MultiplethreadDownload/utils.h
Normal file
12
Components/MultiplethreadDownload/utils.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
class Utils
|
||||
{
|
||||
public:
|
||||
static QString sizeFormat(qint64 bytes);
|
||||
};
|
||||
|
||||
#endif // UTILS_H
|
||||
262
Components/MultiplethreadDownload/widget.cpp
Normal file
262
Components/MultiplethreadDownload/widget.cpp
Normal file
@@ -0,0 +1,262 @@
|
||||
#include "widget.h"
|
||||
#include "ui_widget.h"
|
||||
#include "utils.h"
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QMessageBox>
|
||||
#include <QRegExp>
|
||||
#include <QRegExpValidator>
|
||||
#include <QFileDialog>
|
||||
#include <QDir>
|
||||
#include <QThread>
|
||||
#include <QEventLoop>
|
||||
#include <QFuture>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QMutex>
|
||||
#include <downloadworker.h>
|
||||
|
||||
Widget::Widget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, ui(new Ui::Widget)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
// 设置URL链接验证器
|
||||
QRegExp regex(R"(https?:\/\/.+)");
|
||||
auto *validator = new QRegExpValidator(regex, nullptr);
|
||||
ui->urlInput->setValidator(validator);
|
||||
|
||||
// 设置最大的线程并发数
|
||||
int processer_num = QThread::idealThreadCount();
|
||||
processer_num = processer_num > maxThreadNum ? maxThreadNum : processer_num;
|
||||
ui->threadCountSpinbox->setMaximum(processer_num);
|
||||
ui->threadCountSpinbox->setValue(processer_num);
|
||||
|
||||
// 设置保存路径为当前程序运行路径
|
||||
ui->savePathInput->setText(QDir::currentPath());
|
||||
|
||||
requestManager = new QNetworkAccessManager(this);
|
||||
}
|
||||
|
||||
Widget::~Widget()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief 展示错误信息
|
||||
* @param msg
|
||||
*/
|
||||
void Widget::showError(const QString& msg)
|
||||
{
|
||||
auto msgBox = new QMessageBox(this);
|
||||
msgBox->setWindowTitle(tr("请求出错"));
|
||||
msgBox->setIcon(QMessageBox::Critical);
|
||||
msgBox->setText(msg);
|
||||
msgBox->exec();
|
||||
ui->downloadBtn->setEnabled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取要下载的文件大小
|
||||
* @param url
|
||||
*/
|
||||
qint64 Widget::getFileSize(const QString& url)
|
||||
{
|
||||
QEventLoop event;
|
||||
QNetworkRequest request;
|
||||
request.setUrl(QUrl(url));
|
||||
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
QNetworkReply *reply = requestManager->head(request);
|
||||
connect(reply, &QNetworkReply::errorOccurred, [this, reply](QNetworkReply::NetworkError error){
|
||||
if (error != QNetworkReply::NoError) {
|
||||
return showError(reply->errorString());
|
||||
}
|
||||
});
|
||||
connect(reply, &QNetworkReply::finished, &event, &QEventLoop::quit);
|
||||
event.exec();
|
||||
qint64 fileSize = 0;
|
||||
if (reply->rawHeader("Accept-Ranges") == QByteArrayLiteral("bytes")
|
||||
&& reply->hasRawHeader(QString("Content-Length").toLocal8Bit())) {
|
||||
fileSize = reply->header(QNetworkRequest::ContentLengthHeader).toUInt();
|
||||
}
|
||||
qDebug() << "文件大小为:" << fileSize;
|
||||
reply->deleteLater();
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief 开始下载文件
|
||||
* @param url
|
||||
* @param filename
|
||||
*/
|
||||
void Widget::singleDownload(const QString& url, const QString& filename)
|
||||
{
|
||||
QFile file(filename);
|
||||
if (file.exists())
|
||||
file.remove();
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
return showError(file.errorString());
|
||||
}
|
||||
|
||||
QEventLoop event;
|
||||
QNetworkAccessManager mgr;
|
||||
QNetworkRequest request;
|
||||
request.setUrl(QUrl(url));
|
||||
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
QNetworkReply *reply = mgr.get(request);
|
||||
connect(reply, &QNetworkReply::readyRead, this, [&file, reply](){
|
||||
file.write(reply->readAll());
|
||||
});
|
||||
connect(reply, &QNetworkReply::finished, &event, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::downloadProgress, this, &Widget::download_progress_change);
|
||||
connect(reply, &QNetworkReply::errorOccurred, [this, reply](QNetworkReply::NetworkError error){
|
||||
if (error != QNetworkReply::NoError) showError(reply->errorString());
|
||||
});
|
||||
event.exec();
|
||||
file.flush();
|
||||
file.close();
|
||||
ui->downloadBtn->setEnabled(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief 点击开始下载
|
||||
*/
|
||||
void Widget::on_downloadBtn_clicked()
|
||||
{
|
||||
QString url = ui->urlInput->text().trimmed();
|
||||
if (url.isEmpty()) {
|
||||
return showError(tr("请求链接不允许为空!"));
|
||||
return ;
|
||||
}
|
||||
|
||||
// 选择保存路径
|
||||
QString savePath = ui->savePathInput->text().trimmed();
|
||||
if (savePath.isEmpty()) {
|
||||
savePath = QFileDialog::getExistingDirectory();
|
||||
if (savePath.isEmpty()) return;
|
||||
} else {
|
||||
if(!QDir(savePath).exists()) {
|
||||
return showError(tr("路径不存在!"));
|
||||
}
|
||||
}
|
||||
|
||||
ui->downloadBtn->setEnabled(false);
|
||||
ui->downProgressBar->setValue(0);
|
||||
qint64 fileSize = getFileSize(url);
|
||||
QString sizeText = fileSize == 0 ? "未知大小" : Utils::sizeFormat(fileSize);
|
||||
ui->filesizeLabel->setText(sizeText);
|
||||
|
||||
int process_num = ui->threadCountSpinbox->text().toInt();
|
||||
|
||||
QDir::setCurrent(savePath);
|
||||
QString filename = QFileInfo(url).fileName();
|
||||
if (fileSize == 0 || process_num == 1) {
|
||||
singleDownload(url, filename);
|
||||
} else {
|
||||
multiDownloadWithQThread(url, fileSize, filename, process_num);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置保存下载文件的路径
|
||||
*/
|
||||
void Widget::on_brwoserPathBtn_clicked()
|
||||
{
|
||||
QString savePath = QFileDialog::getExistingDirectory();
|
||||
if (!savePath.isEmpty()) {
|
||||
ui->savePathInput->setText(savePath);
|
||||
}
|
||||
}
|
||||
|
||||
void Widget::download_progress_change(qint64 bytesReceived, qint64 bytesTotal)
|
||||
{
|
||||
if (bytesTotal <= 0)
|
||||
return;
|
||||
ui->downProgressBar->setMaximum(10000); // 最大值
|
||||
ui->downProgressBar->setValue((bytesReceived * 10000) / bytesTotal); // 当前值
|
||||
ui->downProgressBar->setTextVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 多线程下载
|
||||
* @param url
|
||||
* @param fileSize
|
||||
* @param filename
|
||||
* @param threadCount
|
||||
*/
|
||||
void Widget::multiDownloadWithQtConcurrent(const QString &url, qint64 fileSize, const QString &filename, int threadCount)
|
||||
{
|
||||
QFile file(filename);
|
||||
if (file.exists())
|
||||
file.remove();
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
return showError(file.errorString());
|
||||
}
|
||||
file.resize(fileSize);
|
||||
|
||||
qDebug() << "开始下载文件";
|
||||
// 任务等分
|
||||
qint64 segmentSize = fileSize / threadCount;
|
||||
QVector<QPair<qint64, qint64>> vec(threadCount);
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
vec[i].first = i * segmentSize;
|
||||
vec[i].second = i * segmentSize + segmentSize - 1;
|
||||
}
|
||||
vec[threadCount-1].second = fileSize; // 余数部分加入最后一个
|
||||
|
||||
qint64 bytesReceived = 0; // 下载接收的总字节数
|
||||
QMutex lock;
|
||||
// 任务队列
|
||||
auto mapCaller = [&, this](const QPair<qint64, qint64>& pair) -> qint64 {
|
||||
QEventLoop event;
|
||||
QNetworkAccessManager mgr;
|
||||
QNetworkRequest request;
|
||||
request.setUrl(url);
|
||||
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
request.setRawHeader("Range", QString("bytes=%1-%2").arg(pair.first).arg(pair.second).toLocal8Bit());
|
||||
QNetworkReply *reply = mgr.get(request);
|
||||
qint64 writePos = pair.first;
|
||||
qDebug() << "开始下载数据:" << QString(" %1~%2 -> writePos Start %3")
|
||||
.arg(pair.first).arg(pair.second).arg(writePos);
|
||||
connect(reply, &QNetworkReply::readyRead, [&lock, &writePos, &file, &bytesReceived, reply](){
|
||||
QByteArray data = reply->readAll();
|
||||
{
|
||||
QMutexLocker mLock(&lock);
|
||||
// qDebug() << "调整文件的数据指针:" << writePos;
|
||||
file.seek(writePos);
|
||||
file.write(data);
|
||||
bytesReceived += data.size();
|
||||
qDebug() << QString("%1, %2, %3")
|
||||
.arg(writePos).arg(bytesReceived).arg(data.size());
|
||||
}
|
||||
writePos += data.size();
|
||||
});
|
||||
connect(reply, &QNetworkReply::finished, &event, &QEventLoop::quit);
|
||||
event.exec();
|
||||
return writePos - pair.first;
|
||||
};
|
||||
QFuture<void> future = QtConcurrent::map(vec, mapCaller);
|
||||
QFutureWatcher<void> futureWatcher;
|
||||
QEventLoop loop;
|
||||
connect(&futureWatcher, &QFutureWatcher<void>::finished, &loop, &QEventLoop::quit, Qt::QueuedConnection);
|
||||
futureWatcher.setFuture(future);
|
||||
if (!future.isFinished()) {
|
||||
loop.exec();
|
||||
}
|
||||
file.close();
|
||||
qDebug() << "下载完毕!!";
|
||||
ui->downloadBtn->setEnabled(true);
|
||||
}
|
||||
|
||||
void Widget::multiDownloadWithQThread(const QString& url, qint64 fileSize, const QString& filename, int threadCount)
|
||||
{
|
||||
DownloadController *download = new DownloadController(this);
|
||||
download->setFilename(filename);
|
||||
download->startDownload(url, fileSize);
|
||||
connect(download, &DownloadController::downloadProcess, this, &Widget::download_progress_change);
|
||||
}
|
||||
38
Components/MultiplethreadDownload/widget.h
Normal file
38
Components/MultiplethreadDownload/widget.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef WIDGET_H
|
||||
#define WIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace Ui { class Widget; }
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class Widget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Widget(QWidget *parent = nullptr);
|
||||
~Widget();
|
||||
|
||||
protected:
|
||||
void showError(const QString& msg);
|
||||
qint64 getFileSize(const QString& url);
|
||||
void singleDownload(const QString& url, const QString& filename);
|
||||
void multiDownloadWithQtConcurrent(const QString& url, qint64 fileSize, const QString& filename, int threadCount);
|
||||
void multiDownloadWithQThread(const QString& url, qint64 fileSize, const QString& filename, int threadCount);
|
||||
|
||||
|
||||
private slots:
|
||||
void on_downloadBtn_clicked();
|
||||
void on_brwoserPathBtn_clicked();
|
||||
void download_progress_change(qint64 bytesReceived, qint64 bytesTotal);
|
||||
|
||||
private:
|
||||
QNetworkAccessManager *requestManager;
|
||||
const int maxThreadNum = 8;
|
||||
|
||||
Ui::Widget *ui;
|
||||
};
|
||||
#endif // WIDGET_H
|
||||
169
Components/MultiplethreadDownload/widget.ui
Normal file
169
Components/MultiplethreadDownload/widget.ui
Normal file
@@ -0,0 +1,169 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Widget</class>
|
||||
<widget class="QWidget" name="Widget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>635</width>
|
||||
<height>209</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>209</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>多线程下载文件Demo</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">font-family: "Microsoft Yahei";</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetDefaultConstraint</enum>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>URL:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="urlInput">
|
||||
<property name="text">
|
||||
<string>http://sucdn.jerrywang.top/store/network/microsoft-edge-dev/microsoft-edge-dev_89.0.731.0-1_amd64.deb </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="downloadBtn">
|
||||
<property name="text">
|
||||
<string>下载</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>保存路径:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="savePathInput"/>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="brwoserPathBtn">
|
||||
<property name="text">
|
||||
<string>浏览</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>原始线程数:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="threadCountSpinbox">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QPushButton" name="pauseBtn">
|
||||
<property name="text">
|
||||
<string>暂停</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>下载进度:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QProgressBar" name="downProgressBar">
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="textVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="textDirection">
|
||||
<enum>QProgressBar::TopToBottom</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QCheckBox" name="dwnFinishedOpenChbox">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Microsoft Yahei</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>下载完成是否打开</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>下载速度:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="downloadSpeedLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>文件大小:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="filesizeLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
Reference in New Issue
Block a user