简述
为了方便网络编程,Qt 提供了 Network 模块。该模块包含了许多类,例如:QFtp - 能够更加轻松使用 FTP 协议进行网络编程。
但是,从 Qt5.x 之后,Qt Network 发生了很大的变化,助手中关于此部分描述如下:
The QFtp and QUrlInfo classes are no longer exported. Use QNetworkAccessManager instead. Programs that require raw FTP or HTTP streams can use the Qt FTP and Qt HTTP compatibility add-on modules that provide the QFtp and QHttp classes as they existed in Qt 4.
意思是说:不再导出 QFtp 和 QUrlInfo 类,改用 QNetworkAccessManager。
开启 FTP 服务
Linux 下实现 FTP 服务的软件很多,最常见的有:vsftpd、Wu-ftpd 和 Proftp 等。
访问 FTP 服务器时需要经过验证,只有经过了 FTP 服务器的相关验证,用户才能访问和传输文件。
首先,服务器需要安装 FTP 软件,以 vsftpd 为例:
[root@localhost wang]# which vsftpd
/sbin/vsftpd
这说明服务器已经安装了 vsftpd,再进行一系列配置即可使用。
关于 FTP 服务的搭建、配置属于 Linux 范畴,这里就不过多赘述了,请自行查看资料。
效果
实现效果如下:
如果要获取更多关于:文件剩余大小、平均速度、瞬时速度 、剩余时间等相关信息,请参考:Qt之HTTP上传/下载
FtpManager
为了便于使用,封装一个简单的 FtpManager 管理类,用于上传、下载文件。
FTPManager.h
#ifndef FTP_MANAGER
#define FTP_MANAGER
#include <QUrl>
#include <QFile>
#include <QNetworkReply>
#include <QNetworkAccessManager>
class FtpManager : public QObject
{
Q_OBJECT
public:
explicit FtpManager(QObject *parent = 0);
// 设置地址和端口
void setHostPort(const QString &host, int port = 21);
// 设置登录 FTP 服务器的用户名和密码
void setUserInfo(const QString &userName, const QString &password);
// 上传文件
void put(const QString &fileName, const QString &path);
// 下载文件
void get(const QString &path, const QString &fileName);
signals:
void error(QNetworkReply::NetworkError);
// 上传进度
void uploadProgress(qint64 bytesSent, qint64 bytesTotal);
// 下载进度
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
private slots:
// 下载过程中写文件
void finished();
private:
QUrl m_pUrl;
QFile m_file;
QNetworkAccessManager m_manager;
};
#endif // FTP_MANAGER
FTPManager.cpp
#include <QFileInfo>
#include "FTPManager.h"
FtpManager::FtpManager(QObject *parent)
: QObject(parent)
{
// 设置协议
m_pUrl.setScheme("ftp");
}
// 设置地址和端口
void FtpManager::setHostPort(const QString &host, int port)
{
m_pUrl.setHost(host);
m_pUrl.setPort(port);
}
// 设置登录 FTP 服务器的用户名和密码
void FtpManager::setUserInfo(const QString &userName, const QString &password)
{
m_pUrl.setUserName(userName);
m_pUrl.setPassword(password);
}
// 上传文件
void FtpManager::put(const QString &fileName, const QString &path)
{
QFile file(fileName);
file.open(QIODevice::ReadOnly);
QByteArray data = file.readAll();
m_pUrl.setPath(path);
QNetworkReply *pReply = m_manager.put(QNetworkRequest(m_pUrl), data);
connect(pReply, SIGNAL(uploadProgress(qint64, qint64)), this, SIGNAL(uploadProgress(qint64, qint64)));
connect(pReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SIGNAL(error(QNetworkReply::NetworkError)));
}
// 下载文件
void FtpManager::get(const QString &path, const QString &fileName)
{
QFileInfo info;
info.setFile(fileName);
m_file.setFileName(fileName);
m_file.open(QIODevice::WriteOnly | QIODevice::Append);
m_pUrl.setPath(path);
QNetworkReply *pReply = m_manager.get(QNetworkRequest(m_pUrl));
connect(pReply, SIGNAL(finished()), this, SLOT(finished()));
connect(pReply, SIGNAL(downloadProgress(qint64, qint64)), this, SIGNAL(downloadProgress(qint64, qint64)));
connect(pReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SIGNAL(error(QNetworkReply::NetworkError)));
}
// 下载过程中写文件
void FtpManager::finished()
{
QNetworkReply *pReply = qobject_cast<QNetworkReply *>(sender());
switch (pReply->error()) {
case QNetworkReply::NoError : {
m_file.write(pReply->readAll());
m_file.flush();
}
break;
default:
break;
}
m_file.close();
pReply->deleteLater();
}
注释很详细,我就不再多做解释了。。。
注意:下载过程中文件写入是在主线程中进行的,如果文件过大,频繁写入会造成主线程卡顿现象。要避免此种情况,请在工作线程中进行。
使用
这里,只贴主要代码:
// 构建需要的控件
QPushButton *pUploadButton = new QPushButton(this);
QPushButton *pDownloadButton = new QPushButton(this);
m_pUploadBar = new QProgressBar(this);
m_pDownloadBar = new QProgressBar(this);
pUploadButton->setText(QString::fromLocal8Bit("上传"));
pDownloadButton->setText(QString::fromLocal8Bit("下载"));
// 接信号槽
connect(pUploadButton, SIGNAL(clicked(bool)), this, SLOT(upload()));
connect(pDownloadButton, SIGNAL(clicked(bool)), this, SLOT(download()));
// 设置 FTP 相关信息
m_ftp.setHostPort("192.168.***.***", 21);
m_ftp.setUserInfo("wang", "123456");
其中,m_ftp 是类变量 FtpManager。
// 上传文件
void MainWindow::upload()
{
m_ftp.put("E:\\Qt.zip", "/home/wang/Qt.zip");
connect(&m_ftp, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(error(QNetworkReply::NetworkError)));
connect(&m_ftp, SIGNAL(uploadProgress(qint64, qint64)), this, SLOT(uploadProgress(qint64, qint64)));
}
// 下载文件
void MainWindow::download()
{
m_ftp.get("/home/wang/Qt.zip", "F:\\Qt.zip");
connect(&m_ftp, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(error(QNetworkReply::NetworkError)));
connect(&m_ftp, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64)));
}
// 更新上传进度
void MainWindow::uploadProgress(qint64 bytesSent, qint64 bytesTotal)
{
m_pUploadBar->setMaximum(bytesTotal);
m_pUploadBar->setValue(bytesSent);
}
// 更新下载进度
void MainWindow::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
m_pDownloadBar->setMaximum(bytesTotal);
m_pDownloadBar->setValue(bytesReceived);
}
// 错误处理
void MainWindow::error(QNetworkReply::NetworkError error)
{
switch (error) {
case QNetworkReply::HostNotFoundError :
qDebug() << QString::fromLocal8Bit("主机没有找到");
break;
// 其他错误处理
default:
break;
}
}
在上传、下载过程中,确保 Server 端的路径存在:
[root@localhost wang]# pwd
/home/wang
[root@localhost wang]# ls
hello.sh
[root@localhost wang]#
上传完成后,可以去 Server 端查看一下:
[root@localhost wang]# ls -l
总用量 52980
-rw-r--r-- 1 root root 20 11月 16 14:01 hello.sh
-rw-r--r-- 1 wang wang 54246299 11月 16 17:36 Qt.zip
[root@localhost wang]# md5sum Qt.zip
8d010354447515d55c65d733bbba2682 Qt.zip
源文件 Qt.zip 的大小为 54,246,299 字节,显然,目标文件也一样(可使用 MD5 比对,看文件是否损坏),这说明已经完全上传成功了。