使用QNetworkAccessManager實現Qt的FTP下載服務


 從Qt5開始,官方推薦使用QNetworkAccessManager進行Ftp和http的上傳和下載操作;Qt4中使用的QtFtp模塊即作為獨立模塊,需要自己從github上進行下載編譯后使用(官方地址:https://github.com/qt/qtftp)。

官方的QtFtp最后一次更新為2014年,根據搜索的資料,其尚存在若干bug。不過有人對此代碼在Github上進行維護和更新,如果需要使用的話,可以搜索一下。

 

QNetworkAccessManager的相關API比較豐富,但是相應也比較低級。如果需要對Ftp進行較為復雜的操作,在缺少資料的基礎上就會很麻煩,需要較好的功底。

因為個人對Ftp的操作僅限於下載或者上傳,因此使用`QNetworkAccessManager`即可滿足要求。此處僅對下載進行示范,上傳基本一致。

 1 #ifndef FTPGETWINDOW_H
 2 #define FTPGETWINDOW_H
 3 
 4 #include <QWidget>
 5 #include <QUrl>
 6 #include <QDir>
 7 #include <QNetworkReply>
 8 
 9 class QFile;
10 class QLabel;
11 class QLineEdit;
12 class QTextEdit;
13 class QPushButton;
14 class QProgressBar;
15 class QGridLayout;
16 
17 class QTimer;
18 class QNetworkAccessManager;
19 
20 class FtpgetWindow : public QWidget
21 {
22     Q_OBJECT
23 
24 public:
25     FtpgetWindow(QWidget *parent = 0);
26     ~FtpgetWindow();
27 
28 private slots:
29     void timeOut();
30     void updateSelectSaveDir();
31     void updateTaskRunningState();
32     void slotReadyRead();
33     void readReplyError(QNetworkReply::NetworkError error);
34     void downloadFinishReply(QNetworkReply* reply);
35     void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
36 
37 private:
38     bool checkUrl();
39     bool checkSaveDir();
40     bool createDownloadFile();
41     void startDownloadFile();
42 
43 private:
44     qint64 fileDownloadSize;
45     qint64 lastDownloadSize;
46     QUrl url;
47     QDir saveDir;
48     QFile *file;
49     QTimer *timer;
50     QNetworkReply *downloadReply;
51     QNetworkAccessManager *downloadManager;
52 
53     QLabel *urlLabel;
54     QLabel *dirLoactionLabel;
55     QLabel *downlaodInfoLabel;
56     QLabel *runningTipLabel;
57     QLineEdit *urlTextEdit;
58     QLineEdit *dirTextEdit;
59     QTextEdit *downloadInfoTextEdit;
60     QPushButton *runningTaskButton;
61     QPushButton *dirLocationButton;
62     QProgressBar *progressBar;
63     QGridLayout *mainLayout;
64 };
65 
66 #endif // FTPGETWINDOW_H

 頭文件無需贅述。

  1 #include "ftpgetwindow.h"
  2 
  3 #include <QLabel>
  4 #include <QLineEdit>
  5 #include <QTextEdit>
  6 #include <QPushButton>
  7 #include <QProgressBar>
  8 #include <QGridLayout>
  9 #include <QFileDialog>
 10 
 11 #include <QUrl>
 12 #include <QDir>
 13 #include <QFile>
 14 #include <QTimer>
 15 #include <QFileInfo>
 16 #include <QMetaEnum>
 17 #include <QNetworkAccessManager>
 18 
 19 FtpgetWindow::FtpgetWindow(QWidget *parent)
 20     : QWidget(parent),
 21       fileDownloadSize(0),
 22       lastDownloadSize(0),
 23       file(Q_NULLPTR)
 24 {
 25     downloadManager = new QNetworkAccessManager(this);
 26     connect(downloadManager, SIGNAL(finished(QNetworkReply*)),SLOT(downloadFinishReply(QNetworkReply*)));
 27 
 28     //初始化超時檢查定時器,30秒查詢一次
 29     timer = new QTimer;
 30     connect(timer, SIGNAL(timeout()), SLOT(timeOut()));
 31 
 32     urlLabel = new QLabel;
 33     urlLabel->setText(tr("Url:"));
 34 
 35     urlTextEdit = new QLineEdit;
 36     urlLabel->setBuddy(urlTextEdit);
 37 
 38     runningTaskButton = new QPushButton;
 39     runningTaskButton->setText("Run");
 40     connect(runningTaskButton, SIGNAL(clicked(bool)), SLOT(updateTaskRunningState()));
 41 
 42     dirLoactionLabel = new QLabel;
 43     dirLoactionLabel->setText(tr("Save Dir:"));
 44 
 45     dirTextEdit = new QLineEdit;
 46     dirTextEdit->setReadOnly(true);
 47     dirLoactionLabel->setBuddy(dirTextEdit);
 48 
 49     dirLocationButton = new QPushButton;
 50     dirLocationButton->setText("Select Save Dir");
 51     connect(dirLocationButton, SIGNAL(clicked(bool)), SLOT(updateSelectSaveDir()));
 52 
 53     runningTipLabel = new QLabel;
 54     runningTipLabel->setText(tr("Runing task:"));
 55 
 56     progressBar = new QProgressBar;
 57     runningTipLabel->setBuddy(progressBar);
 58 
 59     downlaodInfoLabel = new QLabel;
 60     downlaodInfoLabel->setText(tr("Download Info:"));
 61 
 62     downloadInfoTextEdit = new QTextEdit;
 63     downloadInfoTextEdit->setReadOnly(true);
 64     downlaodInfoLabel->setBuddy(downloadInfoTextEdit);
 65 
 66     mainLayout = new QGridLayout;
 67     mainLayout->setColumnStretch(0, 1);
 68     mainLayout->setColumnStretch(1, 3);
 69     mainLayout->setColumnStretch(2, 1);
 70     mainLayout->setMargin(15);
 71     mainLayout->setColumnMinimumWidth(2, 15);
 72 
 73     mainLayout->addWidget(urlLabel, 0, 0);
 74     mainLayout->addWidget(urlTextEdit, 0, 1);
 75     mainLayout->addWidget(runningTaskButton, 0, 2);
 76     mainLayout->addWidget(dirLoactionLabel, 1, 0);
 77     mainLayout->addWidget(dirTextEdit, 1, 1);
 78     mainLayout->addWidget(dirLocationButton, 1, 2);
 79     mainLayout->addWidget(runningTipLabel, 2, 0, 1, 1);
 80     mainLayout->addWidget(progressBar, 2, 1, 1, 1);
 81     mainLayout->addWidget(downlaodInfoLabel, 3, 0, 1, 1);
 82     mainLayout->addWidget(downloadInfoTextEdit, 4, 0, 3, 3);
 83     setLayout(mainLayout);
 84 
 85     setFixedWidth(800);
 86     setWindowTitle(tr("FpGet Window"));
 87 }
 88 
 89 FtpgetWindow::~FtpgetWindow()
 90 {
 91     if(file != Q_NULLPTR)
 92     {
 93         file->deleteLater();
 94         file = Q_NULLPTR;
 95     }
 96     //downloadManager的父對象是窗體,會自動進行析構
 97 }
 98 
 99 /**
100  * @brief 進行下載超時判斷,錯誤則發送超時信號
101  */
102 void FtpgetWindow::timeOut()
103 {
104     if(lastDownloadSize != fileDownloadSize)
105         lastDownloadSize = fileDownloadSize;
106     else
107         emit downloadReply->error(QNetworkReply::TimeoutError); //下載超時,發送超時錯誤信號
108 }
109 
110 /**
111  * @brief 檢查Url地址合法性
112  * @return
113  */
114 bool FtpgetWindow::checkUrl()
115 {
116     url = QUrl(urlTextEdit->text());
117     if(!url.isValid())
118     {
119         downloadInfoTextEdit->append("Error: Invalid URL");
120         return false;
121     }
122 
123     if(url.scheme() != "ftp")
124     {
125         downloadInfoTextEdit->append("Error: URL must start with 'ftp:'");
126         return false;
127     }
128 
129     if (url.path().isEmpty()) {
130         downloadInfoTextEdit->append("Error: URL has no path");
131         return false;
132     }
133     return true;
134 }
135 
136 /**
137  * @brief 檢查文件下載地址
138  * @return
139  */
140 bool FtpgetWindow::checkSaveDir()
141 {
142     QString dir = dirTextEdit->text();
143     if(dir.isEmpty())
144         dir = QDir::currentPath() + "/Download/";
145     saveDir = QDir(dir);
146 
147     if(!saveDir.exists())
148     {
149         auto ok = saveDir.mkdir(dir);
150         if(!ok) return false;
151     }
152     return true;
153 }
154 
155 bool FtpgetWindow::createDownloadFile()
156 {
157     auto localFileName = QFileInfo(url.path()).fileName();
158     if (localFileName.isEmpty())
159         localFileName = "ftpget.out";
160 
161     file = new QFile;
162     file->setFileName(saveDir.absoluteFilePath(localFileName));
163     if(!file->open(QIODevice::WriteOnly))
164     {
165         auto info = "Error: Cannot write file " + file->fileName()
166                 + ": " + file->errorString();
167         downloadInfoTextEdit->append(info);
168         return false;
169     }
170     return true;
171 }
172 
173 /**
174  * @brief 開始下載文件操作
175  */
176 void FtpgetWindow::startDownloadFile()
177 {
178     if(!createDownloadFile()) return;
179 
180     if(timer->isActive())
181         timer->stop();
182     fileDownloadSize = lastDownloadSize = 0; //重新設置定時器以及相關變量
183 
184     downloadInfoTextEdit->append("Download file: " + url.fileName());
185 
186     downloadReply = downloadManager->get(QNetworkRequest(url));
187 
188     //分塊獲取文件信息,並寫入文件中
189     connect(downloadReply, SIGNAL(readyRead()), SLOT(slotReadyRead()));
190 
191     //獲取下載進度信息
192     connect(downloadReply, SIGNAL(downloadProgress(qint64,qint64)),
193             SLOT(downloadProgress(qint64,qint64)));
194 
195     //下載過程出錯,進行報錯處理(超時處理也是丟出超時信號,交由此槽函數進行處理)
196     connect(downloadReply, SIGNAL(error(QNetworkReply::NetworkError)),
197             SLOT(readReplyError(QNetworkReply::NetworkError)));
198 
199     timer->start(30 * 1000); //啟動超時檢查定時器,每30秒查詢下載情況
200 }
201 
202 void FtpgetWindow::updateSelectSaveDir()
203 {
204     dirTextEdit->setText("");
205     QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"),
206                                                     "C://",
207                                                     QFileDialog::ShowDirsOnly
208                                                     | QFileDialog::DontResolveSymlinks);
209     if(!dir.isEmpty())
210         dirTextEdit->setText(dir);
211 }
212 
213 void FtpgetWindow::updateTaskRunningState()
214 {
215     if(!checkUrl() || !checkSaveDir())
216         return;
217 
218     downloadInfoTextEdit->clear(); //清空信息欄
219 
220     runningTaskButton->setEnabled(false);
221     dirLocationButton->setEnabled(false);
222     startDownloadFile();
223 }
224 
225 /**
226  * @brief 文件下載完成的清尾操作
227  * @param reply
228  */
229 void FtpgetWindow::downloadFinishReply(QNetworkReply *reply)
230 {
231     file->waitForBytesWritten(5 * 1000); //等待文件寫入結束
232     if(0 == file->size())
233         //此處下載失敗,不再進行重新下載操作
234         downloadInfoTextEdit->append("Nothing be download.");
235     else
236         downloadInfoTextEdit->append("Download file success.");
237 
238     if(timer->isActive())
239         timer->stop(); //停止超時計時器
240 
241     file->deleteLater();
242     file = Q_NULLPTR;
243 
244     reply->deleteLater();
245     reply = Q_NULLPTR;
246 
247     runningTaskButton->setEnabled(true);
248     dirLocationButton->setEnabled(true);
249 }
250 
251 void FtpgetWindow::slotReadyRead()
252 {
253     file->write(downloadReply->readAll());
254     fileDownloadSize = file->size(); //更新下載字節數
255 }
256 
257 /**
258  * @brief 下載異常,重新進行下載
259  * @param error
260  */
261 void FtpgetWindow::readReplyError(QNetworkReply::NetworkError error)
262 {
263     auto metaEnum = QMetaEnum::fromType<QNetworkReply::NetworkError>();
264     //PS:字符串轉換為枚舉值
265     //Qt::Alignment alignment = (Qt::Alignment)metaEnum.keyToValue("Qt::AlignLeft");
266     //alignment = (Qt::Alignment)metaEnum.keysToValue("Qt::AlignLeft | Qt::AlignVCenter");
267     //枚舉值轉換為字符串
268     auto errStr = metaEnum.valueToKey(error);
269     downloadInfoTextEdit->append("Download file occur error: " + QString(errStr));
270 
271     file->deleteLater();
272     file = Q_NULLPTR;
273 
274     downloadReply->deleteLater();
275     downloadReply = Q_NULLPTR;
276 
277     startDownloadFile(); //重新嘗試下載文件
278 }
279 
280 void FtpgetWindow::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
281 {
282     if(0 != bytesTotal)
283     {
284         progressBar->setMaximum(bytesTotal);
285         progressBar->setValue(bytesReceived);
286     }
287 }

(1)超時操作:

在下載過程中,經常出現假死操作,因為不清楚如何進行續傳操作,現有做法是取消當前下載任務並重新開始。

在啟動下載操所時,啟動定時器,每隔30秒記錄當前下載數值和上一次記錄的下載數值比較,如果相同,則可以認為在30秒內無操作,發送超時信號,斷開連接重新開始下載任務。

(2)大文件下載:

現有僅測試了上百M的文件,可以在下載結束的時候,一次讀取所有字節並寫入文件,但是這樣的壓力比較大。

因此,當QNetworkReply發送信號告知有分段數據可供讀取的時候,即讀取並寫入文件中。

(3)大文件上傳:

調用put函數時,主要有兩種方式,將文件信息讀取出保存至QByteArray中,或者上傳文件的操作指針。使用后者即可實現大型文件的上傳操作。

(4)下載進度信息:

下載過程中,QNetworkReply會發送下載進度信息,用戶可以根據此刷新QProgressBar控件,或者在命令行刷新進度條。

以下代碼為在命令行實現進度條刷新操作,關鍵在於每次輸出進度信息的時候,不要添加換行符,並且在輸出信息頭部添加"\r"即可。

 

 1 /**
 2  * @brief 實現命令行下進度條,提示下載進度
 3  * @param bytesReceived
 4  * @param bytesTotal
 5  */
 6 void FtpGet::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
 7 {
 8     int barLength = 50;
 9     int percent = int(qreal(bytesReceived) / qreal(bytesTotal) * barLength);
10     QString out = "\rPercent: " + QString(percent, '#') + QString(barLength - percent, ' ');
11     out += " " + QString::number(bytesReceived) + " / " + QString::number(bytesTotal);
12     std::cout << qPrintable(out) << std::flush;
13 }

 

> 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) |


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM