文章目录
1.简述2.QFtp编译与部署
2.1 下载2.2 修改2.3 编译2.4 部署2.5 使用 3.QFtp运用
3.1 Ftp客户端3.2 树莓派搭建FTP服务器3.3 测试效果
1.简述
有时在windows环境下通过远程访问操作另一台linux主机。如使用putty和xshell来实现远程终端,使用Xftp来相互传输文件。现在想自己使用Qt开发一个文件传输软件。
FTP是基于TCP/IP的一种应用层文件传输协议,通过建立FTP服务器(默认端口21)-客服端的形式在各个主机间传输文件。
SFTP是基于FTP服务和ssh协议实现的一种加密文件传输协议,数据传输更加安全,但是相比喻FTP会损失一点效率。
开发者一般通过ssh工具来访问目标linux主机,linux系统一般集成ssh服务(默认端口为22)。用来传输文件的SFTP协议是一ssh的子协议,一般有ssh服务的主机都会自动开启sftp服务。但是linux主机并不一定会安装FTP服务器,需要自行安装。
Qt5.0之后移除了QFtp类(基于FTP协议实现的一个类),使用 QNetworkAccessManager 可以实现 Ftp 的上传/下载功能,后者在性能上和稳定性上有所提升。但有些原本 QFtp 有的功能 QNetworkAccessManager 却没有提供,例如:list、cd、remove、mkdir、rmdir、rename 等,前者的功能更加完善,提供的API更多,更加便于开发者使用。最为新手,开发一般的应用,当然选择QFtp更加有利,值得庆幸的是 QFtp 一直在维护,只需要下载源码自行编译即可使用。
本人的开发环境为Qt 5.12.0,MSVC2017 64bits编译器。下面将总结QFtp在Qt5以上版本编译和使用的经历,开对官方提供的示例进行了重构,写了个Ftp客户端,最终效果见下。
到github,下载QFtp源码,https://github.com/qt/qtftp
git clone https://github.com/qt/qtftp.git2.2 修改
打开qt工程,修改qftp.pro文件中框选的部分,修改为下图所示。修改qftp.h文件的qurlinfo.h头文件,改为下图,该头文件路径有问题。
只构建src,构建需要perl环境,如果之前装qt时一起装了就可以正常编译,如果没装会报错,去官网下载安装就行。
编译完成后见下图,将编译出来的文件分别放到Qt安装路径对应的目录下,使得其和Qt自带的模块一样使用。放置文件的标准就是生成的bin文件夹中的文件放置到Qt对应编译器目录下的bin文件夹中,其他类似。我的是mscv2017_64。
- bin目录下的两个dll文件复制到Qt5.12.15.12.1msvc2017_64bin目录下;lib目录下的.lib文件和.prl文件复制到Qt5.12.15.12.1msvc2017_64bin目录下(我的没有.lib文件,不知道为什么);include目录下的QtFtp文件夹整个复制到Qt5.12.15.12.1msvc2017_64include目录下。这里有个坑,include中的qftp.h和qurlinfo.h不是头文件实体,里面只有一句include将源码的头文件分别包含进来,因此还需要把src文件夹下qftp.h和qurlinfo.h两个文件复制覆盖到Qt5.12.15.12.1msvc2017_64include中;mkspecsmodules-inst目录下的两个.pri文件复制到Qt5.12.15.12.1msvc2017_64mkspecsmodules目录下。
通过上述四步,相当于给Qt新增了一个QtFtp的组件。第一步和第二步准备该组件动态库,第三步准备头文件,第四步相当于定义QtFtp组件,配置IDE,下面是自带的QtNetwork组件(左)和QtFtp组件的对比。
上面配置好后,在一个独立的工程里,需要先在.pro中添加组件,然后包含头文件就行。
QT += ftp
#include3.QFtp运用 3.1 Ftp客户端
参考官方提供的例子,将其独立出来,重构了一下,继承QWidget写了一个类FtpWidget,方便以后在其他的综合项目里直接提升。
先用Qt Designer绘制一个出ui,结构比较简单。
然后,头文件ftpwidget.h
#ifndef FTPWIDGET_H #define FTPWIDGET_H #include#include #include #include #include namespace Ui { class FtpWidget; } class FtpWidget : public QWidget { Q_OBJECT public: explicit FtpWidget(QWidget *parent = nullptr); ~FtpWidget(); private: Ui::FtpWidget *ui; private slots: void downloadFile(); void cancelDownload(); void connectToFtp(); void ftpCommandFinished(int commandId, bool error); void addToList(const QUrlInfo &urlInfo); void processItem(QTreeWidgetItem *item, int column); void cdToParent(); void updateDataTransferProgress(qint64 readBytes, qint64 totalBytes); void enableDownloadButton(); void connectOrDisconnect(); private: QHash isDirectory; QString currentPath; QFtp *ftp = nullptr; QFile *file = nullptr; }; #endif // FTPWIDGET_H
然后,ftpwidget.cpp 源文件
#include "ftpwidget.h" #include "ui_ftpwidget.h" #includeFtpWidget::FtpWidget(QWidget *parent) : QWidget(parent), ui(new Ui::FtpWidget) { ui->setupUi(this); this -> ui -> label_status -> setText("Please enter the name of an FTP server."); this -> ui -> treeWidget_fileList -> setEnabled(false); this -> ui -> treeWidget_fileList -> setRootIsDecorated(false); this -> ui -> treeWidget_fileList -> setHeaderLabels(QStringList() << tr("Name") << tr("Size") << tr("Owner") << tr("Group") << tr("Time")); this -> ui -> treeWidget_fileList -> header() ->setStretchLastSection(false); this -> ui ->pushButton_cdToParent -> setIcon(QPixmap(":/images/images/cdtoparent.png")); this -> ui ->pushButton_cdToParent -> setEnabled(false); this -> ui ->pushButton_download -> setEnabled(false); connect(ui->treeWidget_fileList, &QTreeWidget::itemActivated,this, &FtpWidget::processItem); connect(ui->treeWidget_fileList, &QTreeWidget::currentItemChanged,this, &FtpWidget::enableDownloadButton); connect(ui->pushButton_connect, &QPushButton::clicked, this, &FtpWidget::connectOrDisconnect); connect(ui->pushButton_cdToParent, &QPushButton::clicked, this, &FtpWidget::cdToParent); connect(ui->pushButton_download, &QPushButton::clicked, this, &FtpWidget::downloadFile); setWindowTitle("FTPWidget"); } FtpWidget::~FtpWidget() { delete ui; } void FtpWidget::connectOrDisconnect() { if (ftp) { ftp -> abort(); ftp -> deleteLater(); ftp = nullptr; this -> ui -> treeWidget_fileList -> clear(); this -> ui -> treeWidget_fileList -> setEnabled(false); this -> ui ->pushButton_cdToParent -> setEnabled(false); this -> ui ->pushButton_download -> setEnabled(false); this -> ui ->pushButton_connect -> setEnabled(true); this -> ui ->pushButton_connect -> setText(tr("Connect")); this -> ui -> label_status -> setText("Please enter the name of an FTP server."); return; } connectToFtp(); } void FtpWidget::connectToFtp() { ftp = new QFtp(this); connect(ftp, &QFtp::commandFinished,this, &FtpWidget::ftpCommandFinished); connect(ftp, &QFtp::listInfo,this, &FtpWidget::addToList); connect(ftp, &QFtp::dataTransferProgress,this, &FtpWidget::updateDataTransferProgress); this -> ui -> treeWidget_fileList ->clear(); currentPath.clear(); isDirectory.clear(); QString ftpServer = this->ui->lineEdit_ftpServer->text(); ftp -> connectToHost(ftpServer,21); ftp -> login("pi","302302302"); this -> ui -> label_status ->setText(tr("Connecting to FTP server %1...").arg(this->ui->lineEdit_ftpServer->text())); } void FtpWidget::downloadFile() { QString fileName = this -> ui -> treeWidget_fileList->currentItem()->text(0); if (QFile::exists(fileName)) { QMessageBox::information(this, tr("FTP"), tr("There already exists a file called %1 in " "the current directory.") .arg(fileName)); return; } file = new QFile(fileName); if (!file->open(QIODevice::WriteOnly)) { QMessageBox::information(this, tr("FTP"), tr("Unable to save the file %1: %2.") .arg(fileName).arg(file->errorString())); delete file; return; } ftp->get(this -> ui -> treeWidget_fileList->currentItem()->text(0), file); } void FtpWidget::cancelDownload() { ftp->abort(); if (file->exists()) { file->close(); file->remove(); } delete file; } void FtpWidget::ftpCommandFinished(int, bool error) { if (ftp->currentCommand() == QFtp::ConnectToHost) { if (error) { QMessageBox::information(this, tr("FTP"), tr("Unable to connect to the FTP server " "at %1. Please check that the host " "name is correct.") .arg(this->ui->lineEdit_ftpServer->text())); connectOrDisconnect(); return; } else { this -> ui -> label_status -> setText(tr("Logged onto %1.").arg(this->ui->lineEdit_ftpServer->text())); this -> ui -> treeWidget_fileList-> setFocus(); this -> ui -> pushButton_download->setDefault(true); this -> ui -> pushButton_connect-> setText("Disconnect"); return; } } if (ftp->currentCommand() == QFtp::Login) ftp->list(); if (ftp->currentCommand() == QFtp::Get) { if (error) { this->ui->label_status->setText(tr("Canceled download of %1.") .arg(file->fileName())); file->close(); file->remove(); } else { this->ui->label_status->setText(tr("Downloaded %1 to current directory.") .arg(file->fileName())); this -> ui -> progressBar -> setValue(100); file->close(); } delete file; enableDownloadButton(); } else if (ftp->currentCommand() == QFtp::List) { if (isDirectory.isEmpty()) { this -> ui -> treeWidget_fileList -> addTopLevelItem(new QTreeWidgetItem(QStringList() << tr(" "))); this -> ui -> treeWidget_fileList -> setEnabled(false); } } } void FtpWidget::addToList(const QUrlInfo &urlInfo) { QTreeWidgetItem *item = new QTreeWidgetItem; item->setText(0, urlInfo.name()); item->setText(1, QString::number(urlInfo.size())); item->setText(2, urlInfo.owner()); item->setText(3, urlInfo.group()); item->setText(4, urlInfo.lastModified().toString("MMM dd yyyy")); QPixmap pixmap(urlInfo.isDir() ? ":/images/images/dir.png" : ":/images/images/file.png"); item->setIcon(0, pixmap); isDirectory[urlInfo.name()] = urlInfo.isDir(); this -> ui -> treeWidget_fileList->addTopLevelItem(item); if (!this -> ui -> treeWidget_fileList->currentItem()) { this -> ui -> treeWidget_fileList->setCurrentItem(this -> ui -> treeWidget_fileList->topLevelItem(0)); this -> ui -> treeWidget_fileList->setEnabled(true); } } void FtpWidget::processItem(QTreeWidgetItem *item, int ) { QString name = item->text(0); if (isDirectory.value(name)) { this -> ui -> treeWidget_fileList->clear(); isDirectory.clear(); currentPath += '/'; currentPath += name; ftp->cd(name); ftp->list(); this-> ui -> pushButton_cdToParent->setEnabled(true); return; } } void FtpWidget::cdToParent() { this -> ui -> treeWidget_fileList -> clear(); isDirectory.clear(); currentPath = currentPath.left(currentPath.lastIndexOf('/')); if (currentPath.isEmpty()) { this-> ui -> pushButton_cdToParent ->setEnabled(false); ftp->cd("/"); } else { ftp->cd(currentPath); } ftp->list(); } void FtpWidget::updateDataTransferProgress(qint64 readBytes,qint64 totalBytes) { this -> ui -> progressBar -> setValue(static_cast (readBytes/totalBytes)); } void FtpWidget::enableDownloadButton() { QTreeWidgetItem *current = this -> ui -> treeWidget_fileList->currentItem(); if (current) { QString currentFile = current->text(0); this->ui->pushButton_download->setEnabled(!isDirectory.value(currentFile)); } else { this->ui->pushButton_download->setEnabled(false); } }
完整的源码分享出来放在github上,也放一份在CSDN上。
3.2 树莓派搭建FTP服务器在树莓派上搭建一个FTP服务器用于测试
很简单,很多参考。
文章最前面的视频,目前只实现了下载功能。



