写FTP客户端

QFtp类在Qt中实现了FTP协议的客户端程序,它提供了非常多的函数来执行多数常见的FTP操作,同时还可以执行任意的FTP指令。

QFtp类是异步工作的。若调用一个像get()或者put()这样的函数,它会立即返回并且仅在控制权回到Qt的事件循环时才发生数据传输。这样就确保了在执行FTP指令时,用户界面可以保持响应。

我们将从如何使用get()函数获取一个单一文件的例子 开始讲起。该例子是一个名为ftpget的控制台应用程序,它可以下载指定命令行上的远程文件。首先看看main()主函数:

int main(int argc, char *argv[])
{QCoreApplication app(argc, argv);QStringList args = QCoreApplication::arguments();if (args.count() != 2) {std::cerr << "Usage: ftpget url" << std::endl<< "Example:" << std::endl<< "    ftpget ftp://ftp.trolltech.com/mirrors"<< std::endl;return 1;}FtpGet getter;if (!getter.getFile(QUrl(args[1])))return 1;QObject::connect(&getter, SIGNAL(done()), &app, SLOT(quit()));return app.exec();
}

我们要创建一个QCoreApplication而不是它的子类QAplication,以避免连接到QtGui库。QCoreApplication::arguments()函数返回命令行参数作为一个QStingList列表,采用第一项作为程序被调用时的名称,同时删除掉诸如-style型的所有Qt指定的参数。main()函数的核心是构建FtpGet对象和getFile()调用。如果该函数调用成功,就会让事件循环一直运行下去,直到下载完毕。

FtpGet子类完成了所有的工作,其子类的定义如下:

class FtpGet : public QObject
{Q_OBJECTpublic:FtpGet(QObject *parent = 0);bool getFile(const QUrl &url);signals:void done();private slots:void ftpDone(bool error);private:QFtp ftp;QFile file;
};

这个类有一个公有函数getFile(),可用来获取由URL指定的文件。QUrl 类提供了一个高级接口,用来提取URL的不同部分,如文件名称、路径、协议和端口。

FtpGet有一个私有槽fitpDone(),当文件传输完成时,就会调用它;而当文件下载完成后,它将发送一个done()信号。这个类还有两个私有变量:ftp变量和file变量,前者的类型为QFtp,封装一个到FTP服务器的连接;后者用来向磁盘写入所下载的文件。

FtpGet::FtpGet(QObject *parent): QObject(parent)
{connect(&ftp, SIGNAL(done(bool)), this, SLOT(ftpDone(bool)));
}

在构造函数中,我们把QFtp::done( bool)信号与ftpDone( bool)私有槽连接起来。当所有的请求都已处理完时,QFtp就发射done( bool)信号,bool参数指明是否有错误发生。

bool FtpGet::getFile(const QUrl &url)
{if (!url.isValid()) {std::cerr << "Error: Invalid URL" << std::endl;return false;}if (url.scheme() != "ftp") {std::cerr << "Error: URL must start with 'ftp:'" << std::endl;return false;}if (url.path().isEmpty()) {std::cerr << "Error: URL has no path" << std::endl;return false;}QString localFileName = QFileInfo(url.path()).fileName();if (localFileName.isEmpty())localFileName = "ftpget.out";file.setFileName(localFileName);if (!file.open(QIODevice::WriteOnly)) {std::cerr << "Error: Cannot write file "<< qPrintable(file.fileName()) << ": "<< qPrintable(file.errorString()) << std::endl;return false;}ftp.connectToHost(url.host(), url.port(21));ftp.login();ftp.get(url.path(), &file);ftp.close();return true;
}

getFile()函数首先检查传人的URL。如果遇到问题,该函数将打印出一条出错信息到cerr,同时返回false以表明下载失败。

通过对ftpget.out的备份,我们可以试图利用URL自身创建一个合理的本地文件名而不是让用户自己虚构一个文件名。如果打开文件失败,就会打印出错信息并返回false。

接着,利用QFtp对象执行一个含4个FTP指令的序列。url.port(21)调用返回在URL中指定的端口号;如果在URL中并未指定任何端口号,则返回端口21。因为没有给login()函数任何用户名或者密码,所以此处需要一个匿名登录。get()中的第二个参数指定了输出信号的I/O设备。

这些FTP指令在Qt的事件循环中排队并等待执行。QFtp 的done(bool)信号表明这些指令的完成情况,构造函数中已经把这个信号与ftpDone(bool)连接起来了。

void FtpGet::ftpDone(bool error)
{if (error) {std::cerr << "Error: " << qPrintable(ftp.errorString())<< std::endl;} else {std::cerr << "File downloaded as "<< qPrintable(file.fileName()) << std::endl;}file.close();emit done();
}

这些FTP指令一旦得以执行,就可以关闭相应的文件并且发射我们自己的done()信号。在此处关闭文件而不在getFile()函数最后通过调用ftp.close()来关闭文件,显得有些不同寻常;但是请牢记:在返回getFile()函数之后,FTP指令会异步执行并且整个执行过程会非常顺利。

QFtp提供了一些FTP指令,包括connectToHost()、login()、close()、list()、cd()、get()、put()、remove()、mkdir()、rmdir()和rename()。所有这些函数都可以调度一个 FTP指令,并且可以返回一个标识这个指令的ID号。对于传输模式(默认为被动模式)和传输类型(默认为二进制数据)的控制,也是可能的。

使用rawCommand()可以执行任意的FTP指令。例如,以下是如何执行一个SITE CHMOD指令的代码:
ftp. rawCommand(“SITE CHMOD 755 fortune”);
当QFtp开始执行一个指令的时候,它发射commandStarted(int)信号,而当这个指令完成的时候,它发射commandFinished(int, bool)信号。int 参数是标识一个指令的ID号。如果对个别指令的结果感兴趣,则当调用这些指令的时候,可以保存这些ID号。了解并记录这些ID号可以为用户提供详细的反馈信息。例如:

bool FtpGet::getFile(const QUrl &url)
{...connectId = ftp.connectToHost(url.host(), url.port(21));loginId = ftp.login();getId = ftp.get(url.path(), &file);closeId = ftp.close();return true;
}void FtpGet::ftpCommandStarted(int id)
{if(id == connectId){std::cerr << "Connecting..." << std::endl;}else if(id == loginId){std::cerr << "Logging in..." << std::endl;}...
}

ftpget.h

#ifndef FTPGET_H
#define FTPGET_H#include <QFile>
#include <QFtp>class QUrl;class FtpGet : public QObject
{Q_OBJECTpublic:FtpGet(QObject *parent = 0);bool getFile(const QUrl &url);signals:void done();private slots:void ftpDone(bool error);private:QFtp ftp;QFile file;
};#endif

ftpget.cpp

#include <QtCore>
#include <QtNetwork>
#include <iostream>#include "ftpget.h"FtpGet::FtpGet(QObject *parent): QObject(parent)
{connect(&ftp, SIGNAL(done(bool)), this, SLOT(ftpDone(bool)));
}bool FtpGet::getFile(const QUrl &url)
{if (!url.isValid()) {std::cerr << "Error: Invalid URL" << std::endl;return false;}if (url.scheme() != "ftp") {std::cerr << "Error: URL must start with 'ftp:'" << std::endl;return false;}if (url.path().isEmpty()) {std::cerr << "Error: URL has no path" << std::endl;return false;}QString localFileName = QFileInfo(url.path()).fileName();if (localFileName.isEmpty())localFileName = "ftpget.out";file.setFileName(localFileName);if (!file.open(QIODevice::WriteOnly)) {std::cerr << "Error: Cannot write file "<< qPrintable(file.fileName()) << ": "<< qPrintable(file.errorString()) << std::endl;return false;}ftp.connectToHost(url.host(), url.port(21));ftp.login();ftp.get(url.path(), &file);ftp.close();return true;
}void FtpGet::ftpDone(bool error)
{if (error) {std::cerr << "Error: " << qPrintable(ftp.errorString())<< std::endl;} else {std::cerr << "File downloaded as "<< qPrintable(file.fileName()) << std::endl;}file.close();emit done();
}

main.cpp

#include <QtCore>
#include <iostream>#include "ftpget.h"int main(int argc, char *argv[])
{QCoreApplication app(argc, argv);QStringList args = QCoreApplication::arguments();if (args.count() != 2) {std::cerr << "Usage: ftpget url" << std::endl<< "Example:" << std::endl<< "    ftpget ftp://ftp.trolltech.com/mirrors"<< std::endl;return 1;}FtpGet getter;if (!getter.getFile(QUrl(args[1])))return 1;QObject::connect(&getter, SIGNAL(done()), &app, SLOT(quit()));return app.exec();
}


提供反馈的另一种方式是与QFtp的stateChanged()信号连接,只要连接进入了一个新状态(QFtp::Connecting、QFtp::Connected、QFtp::LoggedIn,等等),就会发射stateChanged()信号。

在绝大多数应用程序中,我们仅仅对整个指令序列的结果而不是对其中某个特别的指令感兴趣。这时,可以只简单地连接done(bool)信号,一旦指令队列变空,就会发射这个信号。

当错误发生时,QFtp会自动清空指令队列。也就是说,如果这次连接或者登录失败,位于队列后面的指令将不会执行。但如果在错误发生之后使用同一个QFtp对象调用新的指令序列,那么这些指令将会排队等待执行。

在应用程序的.pro文件中,需要如下的命令行来连接到QtNetwork数据库:

QT += network

现在来看一个更高级的例子。Spider 命令行程序下载一个FTP目录下的所有文件,它将从所有目录的子目录中递归下载这些文件。在Spider类中体现了网络的逻辑联系:

#ifndef SPIDER_H
#define SPIDER_H#include <QFtp>
#include <QStringList>class QFile;class Spider : public QObject
{Q_OBJECTpublic:Spider(QObject *parent = 0);bool getDirectory(const QUrl &url);signals:void done();private slots:void ftpDone(bool error);void ftpListInfo(const QUrlInfo &urlInfo);private:void processNextDirectory();QFtp ftp;QList<QFile *> openedFiles;QString currentDir;QString currentLocalDir;QStringList pendingDirs;
};#endif

我们将开始目录指定为QUrl,并且使用getDirectory()来设置开始目录:

Spider::Spider(QObject *parent): QObject(parent)
{connect(&ftp, SIGNAL(done(bool)), this, SLOT(ftpDone(bool)));connect(&ftp, SIGNAL(listInfo(const QUrlInfo &)),this, SLOT(ftpListInfo(const QUrlInfo &)));
}

在构造函数中,建立了两个信号-槽的连接。当为所获取的每一个文件请求一个目录列表[在getDirectory()中]时,QFtp 就会发射listInfo(const QUrlInfo &)信号。这个信号被连接到一个名为ftpListInfo()的槽,它会下载与URL相关的给定文件。

bool Spider::getDirectory(const QUrl &url)
{if (!url.isValid()) {std::cerr << "Error: Invalid URL" << std::endl;return false;}if (url.scheme() != "ftp") {std::cerr << "Error: URL must start with 'ftp:'" << std::endl;return false;}ftp.connectToHost(url.host(), url.port(21));ftp.login();QString path = url.path();if (path.isEmpty())path = "/";pendingDirs.append(path);processNextDirectory();return true;
}

当调用getDirectory()函数时,它首先要做一些检查,如果一切正常,就试图建立一个 FTP连接。它对必须要处理的路径进行跟踪,并调用processNextDirectory(),以开始下载根目录中的文件。

void Spider::processNextDirectory()
{if (!pendingDirs.isEmpty()) {currentDir = pendingDirs.takeFirst();currentLocalDir = "downloads/" + currentDir;QDir(".").mkpath(currentLocalDir);ftp.cd(currentDir);ftp.list();} else {emit done();}
}

processNextDirectory()函数从pendingDirs列表中取出第一个远程目录,同时在本地文件系统中创建一个相应的目录。然后它告诉QFtp对象将目录更改为被取出的目录并列出其中的文件。对于list()处理的每一个文件,它都将发射一个促使ftpListInfo()槽被调用的listInfo()信号 。

如果没有需要处理的目录了,该函数将发射done()信号以表明下载完成。

void Spider::ftpListInfo(const QUrlInfo &urlInfo)
{if (urlInfo.isFile()) {if (urlInfo.isReadable()) {QFile *file = new QFile(currentLocalDir + "/"+ urlInfo.name());if (!file->open(QIODevice::WriteOnly)) {std::cerr << "Warning: Cannot write file "<< qPrintable(QDir::toNativeSeparators(file->fileName()))<< ": " << qPrintable(file->errorString())<< std::endl;return;}ftp.get(urlInfo.name(), file);openedFiles.append(file);}} else if (urlInfo.isDir() && !urlInfo.isSymLink()) {pendingDirs.append(currentDir + "/" + urlInfo.name());}
}

ftpListInfo()槽的urlInfo参数提供了有关一个远程文件的详细信息。如果这个文件只是一个可读的普通文件(而不是目录),就调用get()来下载它。这个用于下载的QFile对象是利用new函数以及一个指向它在openedFiles 列表中存储的指针来分配的。

如果QUlInfo持有一个不是符号连接的远程目录的细节信息,就将这个目录加到pendingDirs列表中。之所以要跳过符号连接,是因为它容易导致无穷递归循环。

void Spider::ftpDone(bool error)
{if (error) {std::cerr << "Error: " << qPrintable(ftp.errorString())<< std::endl;} else {std::cout << "Downloaded " << qPrintable(currentDir) << " to "<< qPrintable(QDir::toNativeSeparators(QDir(currentLocalDir).canonicalPath()));}qDeleteAll(openedFiles);openedFiles.clear();processNextDirectory();
}

当所有这些FIP指令都完成后,或者如果有错误发生时,就会调用ftpDone()槽。我们删除QFile对象以防止内存泄漏,同时也关闭每一个文件。最后,调用processNextDirectory()。如果还有需要处理的目录,整个过程将从列表中的下一个目录重新开始;否则,下载过程停止并且发射done()信号。

如果没有错误发生,FTP指令和信号的序列如下:

connectToHost(host, port)
login()
cd(directory_1)
list()emit listInfo(file_1_2)get(file_1_1)emit listInfo(file_1_2)get(file_1_2)...
emit done()
...
cd(directory_N)
list()emit listInfo(file_N_1)get(file_N_1)emit listInfo(file_N_2)get(file_N_2)...
emit done()

如果打开的文件实际上是一个目录,它将被加入到pendingDirs列表中。在当前list()指令中的最后一个文件被下载完时,就将发出一个新的cd()指令以及下一个待处理目录的list()指令,而整个过程将随一个新目录重新开始。这个过程不断重复,既有新文件被下载也有新目录被添加到pendingDirs列表,直到每一目录下的文件都被下载完,这时pendingDirs 列表也最终将变空。

如果要下载一个目录下的20个文件,当下载到第5个文件的时候,网络发生错误,那么剩下的文件将不会被下载。如果想下载尽可能多的文件,一个解决方案是一次只调用一个GET操作来下载一个文件,并且在调用下一个新的GET操作之前等待done( bool)信号。在listInfo()中,将简单地把文件名追加到一个QStringList中,而不是立即调用get(),而且在done(bool)中,我们将对下一个文件调用get()函数以在QStringList中下载。于是,整个执行顺序看起来可以归结如下:

connectToHost(host, port)
login()
cd(directory_1)
list()
...
cd(directory_N)
list()emit listInfo(file_1_2)emit listInfo(file_1_2)...emit listInfo(file_N_1)emit listInfo(file_N_2)...
emit done()
get(file_1_1)
emit done()
get(file_1_2)
emit done()
...
get(file_N_1)
emit done()
get(file_N_2)
...

另外一个解决方案是对每一个文件使用一个QFtp对象。这样可以通过一些单独的FTP连接来并行下载这些文件。

int main(int argc, char *argv[])
{QCoreApplication app(argc, argv);QStringList args = QCoreApplication::arguments();if (args.count() != 2) {std::cerr << "Usage: spider url" << std::endl<< "Example:" << std::endl<< "    spider ftp://ftp.trolltech.com/freebies/"<< "leafnode" << std::endl;return 1;}Spider spider;if (!spider.getDirectory(QUrl(args[1])))return 1;QObject::connect(&spider, SIGNAL(done()), &app, SLOT(quit()));return app.exec();
}

main()函数完成了这个程序。如果用户没有在命令行中指定一个URL,就给出一个出错信息并终止程序。

在这两个FTP实例中,利用get()函数获得的数据都被写入QFile 中。但这样的做法并不是必需的。如果想把这些数据放到内存中,则可以使用QBuffer,它是一个在QByteAray上操作的QIODevice的子类。例如:

QBuffer *buffer = new QBuffer;
buffer->open(QIODevice::WriteOnly);
ftp.get(urlInfo.name(), buffer);

也可以省略get()中的输入/输出(I/O)设备参数,或者只传递一个空指针。然后每次当有新数据可读的时候,QFtp类就会发射一个readyRead()信号,并且可以使用read()或者readAIl()来读取这些数据。

spider.h

#ifndef SPIDER_H
#define SPIDER_H#include <QFtp>
#include <QStringList>class QFile;class Spider : public QObject
{Q_OBJECTpublic:Spider(QObject *parent = 0);bool getDirectory(const QUrl &url);signals:void done();private slots:void ftpDone(bool error);void ftpListInfo(const QUrlInfo &urlInfo);private:void processNextDirectory();QFtp ftp;QList<QFile *> openedFiles;QString currentDir;QString currentLocalDir;QStringList pendingDirs;
};#endif

spider.cpp

#include <QtCore>
#include <QtNetwork>
#include <iostream>#include "spider.h"Spider::Spider(QObject *parent): QObject(parent)
{connect(&ftp, SIGNAL(done(bool)), this, SLOT(ftpDone(bool)));connect(&ftp, SIGNAL(listInfo(const QUrlInfo &)),this, SLOT(ftpListInfo(const QUrlInfo &)));
}bool Spider::getDirectory(const QUrl &url)
{if (!url.isValid()) {std::cerr << "Error: Invalid URL" << std::endl;return false;}if (url.scheme() != "ftp") {std::cerr << "Error: URL must start with 'ftp:'" << std::endl;return false;}ftp.connectToHost(url.host(), url.port(21));ftp.login();QString path = url.path();if (path.isEmpty())path = "/";pendingDirs.append(path);processNextDirectory();return true;
}void Spider::ftpDone(bool error)
{if (error) {std::cerr << "Error: " << qPrintable(ftp.errorString())<< std::endl;} else {std::cout << "Downloaded " << qPrintable(currentDir) << " to "<< qPrintable(QDir::toNativeSeparators(QDir(currentLocalDir).canonicalPath()));}qDeleteAll(openedFiles);openedFiles.clear();processNextDirectory();
}void Spider::ftpListInfo(const QUrlInfo &urlInfo)
{if (urlInfo.isFile()) {if (urlInfo.isReadable()) {QFile *file = new QFile(currentLocalDir + "/"+ urlInfo.name());if (!file->open(QIODevice::WriteOnly)) {std::cerr << "Warning: Cannot write file "<< qPrintable(QDir::toNativeSeparators(file->fileName()))<< ": " << qPrintable(file->errorString())<< std::endl;return;}ftp.get(urlInfo.name(), file);openedFiles.append(file);}} else if (urlInfo.isDir() && !urlInfo.isSymLink()) {pendingDirs.append(currentDir + "/" + urlInfo.name());}
}void Spider::processNextDirectory()
{if (!pendingDirs.isEmpty()) {currentDir = pendingDirs.takeFirst();currentLocalDir = "downloads/" + currentDir;QDir(".").mkpath(currentLocalDir);ftp.cd(currentDir);ftp.list();} else {emit done();}
}

main.cpp

#include <QtCore>
#include <iostream>#include "spider.h"int main(int argc, char *argv[])
{QCoreApplication app(argc, argv);QStringList args = QCoreApplication::arguments();if (args.count() != 2) {std::cerr << "Usage: spider url" << std::endl<< "Example:" << std::endl<< "    spider ftp://ftp.trolltech.com/freebies/"<< "leafnode" << std::endl;return 1;}Spider spider;if (!spider.getDirectory(QUrl(args[1])))return 1;QObject::connect(&spider, SIGNAL(done()), &app, SLOT(quit()));return app.exec();
}

Qt4_写FTP客户端相关推荐

  1. python编写ftp客户端_用Python写FTP客户端程序

    0 前言: ftp客户端相信大家都用过,那么我们为什么还要用Python写ftp客户端呢? 我想有两个原因: 一是写出更好的ftp客户端应用程序,方便大家使用: 二是定制一些特殊服务,例如每天定时下载 ...

  2. Qt4_写HTTP客户端

    写HTTP客户端 QHttp类在Qt中实现了HTTP协议的客户端程序.它提供了各种各样的函数来执行绝大多数普通HTTP操作,包括get()和post(),并且还提供了一个发送任意HTTP请求指令的方式 ...

  3. 网络编程--ftp客户端的实现(c#版)

    .net2.0对ftp有了一个很好的封装,但是确容易让人忽略ftp的真正内部实现,下面是我实现的ftp客户端的功能,其主要步骤是这样的: 1.创建一个FtpWebRequest对象,指向ftp服务器的 ...

  4. ftp 客户端 上传

    ps: 1.使用netkit-ftp-0.17交叉编译出来的ftp客户端, 2.然后写上传代码,调用ftp传服务器: 3.最好使用bin二进制文件形式传输: 4.ftp客户端和Ubuntu自带的ftp ...

  5. linux上的ftp怎么设置编码格式,linux ftp客户端的编码问题

    准备下载ftp上的文件,记得curl可以实现下载ftp的文件,然后尝试了, curl ftp://username:password@192.168.1.2/平台全量包/package0811.zip ...

  6. 7个免费的Linux FTP客户端工具

    在Dropbox.YouSendIt.idrive以及许多这样云存储和共享工具的帮助下,我们在互联网上发送和共享大型文件变得容易起来.所有这些网站都可以帮助你在互联网上传送文件,但如果你要分享庞大的数 ...

  7. python ftplib_python:使用ftplib编写FTP客户端

    Python中的ftplib模块 Python中默认安装的ftplib模块定义了FTP类,其中函数有限,可用来实现简单的ftp客户端,用于上传或下载文件 FTP的工作流程及基本操作可参考协议RFC95 ...

  8. 应用层——使用 Socket 通信实现 FTP 客户端程序

    转自:http://blog.csdn.net/yixijide/article/details/8280263 简介: FTP 客户端如 FlashFXP,File Zilla 被广泛应用,原理上都 ...

  9. 基于Linux命令行终端的ftp客户端程序

    深入理解FTP(File Transfer Protocol)协议的有关知识,完成一个基于Linux命令行终端的ftp客户端程序专业程序代写大学生程序代写 转载于:https://www.cnblog ...

最新文章

  1. python编程基础知识体系_Python 编程核心知识体系-基础|数据类型|控制流(一)...
  2. [日常] 算法-旋转字符串-暴力移位法
  3. linux命令:xargs
  4. socket python 收 发 队列 线程_对于Python中socket.listen()与多线程结合的困惑?
  5. mysql 取数据 展示_php mysql_fetch_row逐行获取结果集数据并显示在table表格中
  6. gradle构建多模块项目_Gradle入门:创建多项目构建
  7. Android Glide图片加载框架(四)回调与监听
  8. boost知识点查阅
  9. JAVA费罗切测评_暴力美学的终极形态 JAVA Feroce 2评测
  10. RabbitMQ获取队列的消息数目
  11. 新窝开张,自己祝贺一下。
  12. 【算法】排序_计数排序
  13. CRC校验码计算公式
  14. java 异步写_Java异步编程实战
  15. h5游戏引擎有哪些?h5制作模板游戏的教程和流程是什么?
  16. win7+VS2008安装QT、环境配置以及简单实例演示
  17. Lattice系列内存时序
  18. 解读华为的流程与 IT 管理部门
  19. 苹果手机相机九宫格怎么设置_用苹果手机拍照,这3个媲美单反的设置不会用,就别说自己用苹果...
  20. K-means方法总结(附代码)

热门文章

  1. PostMan-使用笔记
  2. “通配符的匹配很全面, 但无法找到元素 ‘utillist‘ 的声明”的问题
  3. android one s5,消费者报告:Galaxy S5比S6更值得购买
  4. wegame饥荒一直登录中_经历网游和单机发行的左右摇摆后,Wegame决定“我全都要”...
  5. linux 内核 发送数据类型,Linux内核数据类型及跨平台
  6. orderquery.php,multiOrderQuery.php
  7. Confluence 6 示例 - https://confluence.atlassian.com/
  8. LeetCode---binary-tree-inorder-traversal
  9. 写在前面--点燃酱爆心中的那团火
  10. 通过资源名称得到资源id