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

来源:http://www.cnblogs.com/jason1990/archive/2017/11/14/7830271.html
-Advertisement-
Play Games

使用QNetworkAccessManager實現Qt的FTP下載操作,此外包含以下功能:(1)添加下載超時操作;(2)大文件分割下載。 附加C++實現命令行輸出進度條實現代碼。 ...


 從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 }

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Tomcat 伺服器是一個免費的開放源代碼的Web 應用伺服器,屬於輕量級應用伺服器,在中小型系統和併發訪問用戶不是很多的場合下被普遍使用,是開發和調試JSP 程式的首選。對於一個初學者來說,可以這樣認為,當在一臺機器上配置好Apache 伺服器,可利用它響應HTML(標準通用標記語言下的一個應用) ...
  • Ubuntu 17.10 安裝 "愛壁紙" 的 deb 包時,缺失了 python-support 依賴。使用 sudo apt-get -f install 也沒修複。查了下官方源,結果 python-support 被棄用了。 但 "愛壁紙" 還是要用滴,python-support 還是要裝滴 ...
  • 在win+R運行框中: cmd:進入命令行界面 msconfig:可以查看“系統配置” msinfo32:查看系統信息 services.msc打開"服務"視窗 mspaint:打開畫圖工具 notepad:打開記事本 notepad++:打開notepad++這個文本編輯軟體(前提是你已經安裝了n ...
  • 1、獲取信息 2、篩選信息 3、整理數據 例如用Excel整理記憶體使用情況,這裡把獲取的時間和記憶體信息放在Excel內部,並把記憶體列用Excel分列,用時間和使用的記憶體大小列可以製作出一張記憶體使用趨勢圖;同理也可以製作CPU、cached及各個微服務的CPU和記憶體趨勢圖。 ...
  • 今天時開通博客的第一天,呃,第二天了,昨晚收到郵件,犯懶了,沒有實踐自己每日記錄的諾言,自打臉【尷尬】。 最近以及很長一段時間,我的博客將主要是記錄我在Java 前臺和後臺學習實踐中遇到的一些錯誤和經驗,已經自己在各路資料上遇到的各種問題和自己的總結。所以我會在每篇的末尾立下flag ,進行提醒以便 ...
  • 本節內容 - 開篇 - 棧(Stack) - 隊列(Queue) - 緩衝區(Pool) - 鏈表(Linked List) ...
  • 一、關於 Python Python 是全球使用人數增長最快的編程語言!它易於入門、功能強大,從 Web 後端 到 數據分析、人工智慧,到處都能看到 Python 的身影。 Python 有兩個主要的版本 Python 2.x 和 Python 3.x。咪博士推薦大家學習 Python 3.x。本系 ...
  • 上次我們學習了環形鏈表的數據結構,那麼接下來我們來一起看看下麵的問題, 判斷一個單向鏈表是否是環形鏈表? 看到這個問題,有人就提出了進行遍歷鏈表,記住第一元素,當我們遍歷後元素再次出現則是說明是環形鏈表,如果沒有這是一個單向非環形鏈表。 我們來分析下上述的解決方法,我們分析這個程式的時間複雜度則是O ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...