使用QNetworkAccessManager实现Qt的FTP下载服务

从Qt5开始,官方推荐使用QNetworkAccessManager进行Ftp和http的上传和下载操作;Qt4中使用的QtFtp模块即作为独立模块,需要自己从github上进行下载编译后使用(官方地址:https://github.com/qt/qtftp)。

官方的QtFtp最后一次更新为2014年,根据搜索的资料,其尚存在若干bug。不过有人对此代码在Github上进行维护和更新,如果需要使用的话,可以搜索一下。

QNetworkAccessManager的相关API比较丰富,但是相应也比较低级。如果需要对Ftp进行较为复杂的操作,在缺少资料的基础上就会很麻烦,需要较好的功底。

因为个人对Ftp的操作仅限于下载或者上传,因此使用`QNetworkAccessManager`即可满足要求。此处仅对下载进行示范,上传基本一致。

 #ifndef FTPGETWINDOW_H
#define FTPGETWINDOW_H #include <QWidget>
#include <QUrl>
#include <QDir>
#include <QNetworkReply> class QFile;
class QLabel;
class QLineEdit;
class QTextEdit;
class QPushButton;
class QProgressBar;
class QGridLayout; class QTimer;
class QNetworkAccessManager; class FtpgetWindow : public QWidget
{
Q_OBJECT public:
FtpgetWindow(QWidget *parent = );
~FtpgetWindow(); private slots:
void timeOut();
void updateSelectSaveDir();
void updateTaskRunningState();
void slotReadyRead();
void readReplyError(QNetworkReply::NetworkError error);
void downloadFinishReply(QNetworkReply* reply);
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); private:
bool checkUrl();
bool checkSaveDir();
bool createDownloadFile();
void startDownloadFile(); private:
qint64 fileDownloadSize;
qint64 lastDownloadSize;
QUrl url;
QDir saveDir;
QFile *file;
QTimer *timer;
QNetworkReply *downloadReply;
QNetworkAccessManager *downloadManager; QLabel *urlLabel;
QLabel *dirLoactionLabel;
QLabel *downlaodInfoLabel;
QLabel *runningTipLabel;
QLineEdit *urlTextEdit;
QLineEdit *dirTextEdit;
QTextEdit *downloadInfoTextEdit;
QPushButton *runningTaskButton;
QPushButton *dirLocationButton;
QProgressBar *progressBar;
QGridLayout *mainLayout;
}; #endif // FTPGETWINDOW_H

头文件无需赘述。

 #include "ftpgetwindow.h"

 #include <QLabel>
#include <QLineEdit>
#include <QTextEdit>
#include <QPushButton>
#include <QProgressBar>
#include <QGridLayout>
#include <QFileDialog> #include <QUrl>
#include <QDir>
#include <QFile>
#include <QTimer>
#include <QFileInfo>
#include <QMetaEnum>
#include <QNetworkAccessManager> FtpgetWindow::FtpgetWindow(QWidget *parent)
: QWidget(parent),
fileDownloadSize(),
lastDownloadSize(),
file(Q_NULLPTR)
{
downloadManager = new QNetworkAccessManager(this);
connect(downloadManager, SIGNAL(finished(QNetworkReply*)),SLOT(downloadFinishReply(QNetworkReply*))); //初始化超时检查定时器,30秒查询一次
timer = new QTimer;
connect(timer, SIGNAL(timeout()), SLOT(timeOut())); urlLabel = new QLabel;
urlLabel->setText(tr("Url:")); urlTextEdit = new QLineEdit;
urlLabel->setBuddy(urlTextEdit); runningTaskButton = new QPushButton;
runningTaskButton->setText("Run");
connect(runningTaskButton, SIGNAL(clicked(bool)), SLOT(updateTaskRunningState())); dirLoactionLabel = new QLabel;
dirLoactionLabel->setText(tr("Save Dir:")); dirTextEdit = new QLineEdit;
dirTextEdit->setReadOnly(true);
dirLoactionLabel->setBuddy(dirTextEdit); dirLocationButton = new QPushButton;
dirLocationButton->setText("Select Save Dir");
connect(dirLocationButton, SIGNAL(clicked(bool)), SLOT(updateSelectSaveDir())); runningTipLabel = new QLabel;
runningTipLabel->setText(tr("Runing task:")); progressBar = new QProgressBar;
runningTipLabel->setBuddy(progressBar); downlaodInfoLabel = new QLabel;
downlaodInfoLabel->setText(tr("Download Info:")); downloadInfoTextEdit = new QTextEdit;
downloadInfoTextEdit->setReadOnly(true);
downlaodInfoLabel->setBuddy(downloadInfoTextEdit); mainLayout = new QGridLayout;
mainLayout->setColumnStretch(, );
mainLayout->setColumnStretch(, );
mainLayout->setColumnStretch(, );
mainLayout->setMargin();
mainLayout->setColumnMinimumWidth(, ); mainLayout->addWidget(urlLabel, , );
mainLayout->addWidget(urlTextEdit, , );
mainLayout->addWidget(runningTaskButton, , );
mainLayout->addWidget(dirLoactionLabel, , );
mainLayout->addWidget(dirTextEdit, , );
mainLayout->addWidget(dirLocationButton, , );
mainLayout->addWidget(runningTipLabel, , , , );
mainLayout->addWidget(progressBar, , , , );
mainLayout->addWidget(downlaodInfoLabel, , , , );
mainLayout->addWidget(downloadInfoTextEdit, , , , );
setLayout(mainLayout); setFixedWidth();
setWindowTitle(tr("FpGet Window"));
} FtpgetWindow::~FtpgetWindow()
{
if(file != Q_NULLPTR)
{
file->deleteLater();
file = Q_NULLPTR;
}
//downloadManager的父对象是窗体,会自动进行析构
} /**
* @brief 进行下载超时判断,错误则发送超时信号
*/
void FtpgetWindow::timeOut()
{
if(lastDownloadSize != fileDownloadSize)
lastDownloadSize = fileDownloadSize;
else
emit downloadReply->error(QNetworkReply::TimeoutError); //下载超时,发送超时错误信号
} /**
* @brief 检查Url地址合法性
* @return
*/
bool FtpgetWindow::checkUrl()
{
url = QUrl(urlTextEdit->text());
if(!url.isValid())
{
downloadInfoTextEdit->append("Error: Invalid URL");
return false;
} if(url.scheme() != "ftp")
{
downloadInfoTextEdit->append("Error: URL must start with 'ftp:'");
return false;
} if (url.path().isEmpty()) {
downloadInfoTextEdit->append("Error: URL has no path");
return false;
}
return true;
} /**
* @brief 检查文件下载地址
* @return
*/
bool FtpgetWindow::checkSaveDir()
{
QString dir = dirTextEdit->text();
if(dir.isEmpty())
dir = QDir::currentPath() + "/Download/";
saveDir = QDir(dir); if(!saveDir.exists())
{
auto ok = saveDir.mkdir(dir);
if(!ok) return false;
}
return true;
} bool FtpgetWindow::createDownloadFile()
{
auto localFileName = QFileInfo(url.path()).fileName();
if (localFileName.isEmpty())
localFileName = "ftpget.out"; file = new QFile;
file->setFileName(saveDir.absoluteFilePath(localFileName));
if(!file->open(QIODevice::WriteOnly))
{
auto info = "Error: Cannot write file " + file->fileName()
+ ": " + file->errorString();
downloadInfoTextEdit->append(info);
return false;
}
return true;
} /**
* @brief 开始下载文件操作
*/
void FtpgetWindow::startDownloadFile()
{
if(!createDownloadFile()) return; if(timer->isActive())
timer->stop();
fileDownloadSize = lastDownloadSize = ; //重新设置定时器以及相关变量 downloadInfoTextEdit->append("Download file: " + url.fileName()); downloadReply = downloadManager->get(QNetworkRequest(url)); //分块获取文件信息,并写入文件中
connect(downloadReply, SIGNAL(readyRead()), SLOT(slotReadyRead())); //获取下载进度信息
connect(downloadReply, SIGNAL(downloadProgress(qint64,qint64)),
SLOT(downloadProgress(qint64,qint64))); //下载过程出错,进行报错处理(超时处理也是丢出超时信号,交由此槽函数进行处理)
connect(downloadReply, SIGNAL(error(QNetworkReply::NetworkError)),
SLOT(readReplyError(QNetworkReply::NetworkError))); timer->start( * ); //启动超时检查定时器,每30秒查询下载情况
} void FtpgetWindow::updateSelectSaveDir()
{
dirTextEdit->setText("");
QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"),
"C://",
QFileDialog::ShowDirsOnly
| QFileDialog::DontResolveSymlinks);
if(!dir.isEmpty())
dirTextEdit->setText(dir);
} void FtpgetWindow::updateTaskRunningState()
{
if(!checkUrl() || !checkSaveDir())
return; downloadInfoTextEdit->clear(); //清空信息栏 runningTaskButton->setEnabled(false);
dirLocationButton->setEnabled(false);
startDownloadFile();
} /**
* @brief 文件下载完成的清尾操作
* @param reply
*/
void FtpgetWindow::downloadFinishReply(QNetworkReply *reply)
{
file->waitForBytesWritten( * ); //等待文件写入结束
if( == file->size())
//此处下载失败,不再进行重新下载操作
downloadInfoTextEdit->append("Nothing be download.");
else
downloadInfoTextEdit->append("Download file success."); if(timer->isActive())
timer->stop(); //停止超时计时器 file->deleteLater();
file = Q_NULLPTR; reply->deleteLater();
reply = Q_NULLPTR; runningTaskButton->setEnabled(true);
dirLocationButton->setEnabled(true);
} void FtpgetWindow::slotReadyRead()
{
file->write(downloadReply->readAll());
fileDownloadSize = file->size(); //更新下载字节数
} /**
* @brief 下载异常,重新进行下载
* @param error
*/
void FtpgetWindow::readReplyError(QNetworkReply::NetworkError error)
{
auto metaEnum = QMetaEnum::fromType<QNetworkReply::NetworkError>();
//PS:字符串转换为枚举值
//Qt::Alignment alignment = (Qt::Alignment)metaEnum.keyToValue("Qt::AlignLeft");
//alignment = (Qt::Alignment)metaEnum.keysToValue("Qt::AlignLeft | Qt::AlignVCenter");
//枚举值转换为字符串
auto errStr = metaEnum.valueToKey(error);
downloadInfoTextEdit->append("Download file occur error: " + QString(errStr)); file->deleteLater();
file = Q_NULLPTR; downloadReply->deleteLater();
downloadReply = Q_NULLPTR; startDownloadFile(); //重新尝试下载文件
} void FtpgetWindow::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
if( != bytesTotal)
{
progressBar->setMaximum(bytesTotal);
progressBar->setValue(bytesReceived);
}
}

(1)超时操作:

在下载过程中,经常出现假死操作,因为不清楚如何进行续传操作,现有做法是取消当前下载任务并重新开始。

在启动下载操所时,启动定时器,每隔30秒记录当前下载数值和上一次记录的下载数值比较,如果相同,则可以认为在30秒内无操作,发送超时信号,断开连接重新开始下载任务。

(2)大文件下载:

现有仅测试了上百M的文件,可以在下载结束的时候,一次读取所有字节并写入文件,但是这样的压力比较大。

因此,当QNetworkReply发送信号告知有分段数据可供读取的时候,即读取并写入文件中。

(3)大文件上传:

调用put函数时,主要有两种方式,将文件信息读取出保存至QByteArray中,或者上传文件的操作指针。使用后者即可实现大型文件的上传操作。

(4)下载进度信息:

下载过程中,QNetworkReply会发送下载进度信息,用户可以根据此刷新QProgressBar控件,或者在命令行刷新进度条。

以下代码为在命令行实现进度条刷新操作,关键在于每次输出进度信息的时候,不要添加换行符,并且在输出信息头部添加"\r"即可。

 /**
* @brief 实现命令行下进度条,提示下载进度
* @param bytesReceived
* @param bytesTotal
*/
void FtpGet::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
int barLength = ;
int percent = int(qreal(bytesReceived) / qreal(bytesTotal) * barLength);
QString out = "\rPercent: " + QString(percent, '#') + QString(barLength - percent, ' ');
out += " " + QString::number(bytesReceived) + " / " + QString::number(bytesTotal);
std::cout << qPrintable(out) << std::flush;
}

> PS:
> 如果您觉得我的文章对您有帮助,可以扫码领取下红包或扫码支持(随意多少,一分钱都是爱),谢谢!

支付宝红包 | 支付宝 | 微信
-|-|-
![](https://img2018.cnblogs.com/blog/764719/201906/764719-20190624102014468-193165117.png) | ![](https://img2018.cnblogs.com/blog/764719/201906/764719-20190624102025867-1690450943.png) | ![](https://img2018.cnblogs.com/blog/764719/201906/764719-20190624101626318-627523959.png) |

上一篇:datatables:如何禁用一列的排序


下一篇:MySQL 高级性能优化架构 千万级高并发交易一致性系统基础