开发者社区> 潘志闻> 正文

Qt之QFtp

简介: 简述 QFtp 类提供了一个 FTP 协议的客户端实现。 该类提供了一个到 FTP 的直接接口,允许对请求有更多的控制。但是,对于新的应用程序,建议使用 QNetworkAccessManager 和 QNetworkReply,因为这些类拥有一个更简单、还更强大的 API。 简述 QFtp 工作流程 基本使用 连接并登录 FTP 服务器 切换工作目录 列出目
+关注继续查看

简述

QFtp 类提供了一个 FTP 协议的客户端实现。

该类提供了一个到 FTP 的直接接口允许对请求有更多的控制。但是对于新的应用程序建议使用 QNetworkAccessManager 和 QNetworkReply因为这些类拥有一个更简单、还更强大的 API。

QFtp

QFtp 异步工作因此没有阻塞函数。如果无法立即执行操作函数仍将立即返回并且该操作将被调度以供以后执行。调度操作的结果通过信号报告这种方法依赖于事件循环操作。

可以调度的操作也被称为“命令”有connectToHost()、login()、close()、list()、cd()、get()、put()、remove()、mkdir()、rmdir()、rename() 和 rawCommand()。

所有这些命令都会返回一个唯一的标识符允许跟踪当前正在执行的命令。当命令的执行开始时发出带有命令标识符的 commandStarted() 信号。当命令完成时会发出 commandFinished() 信号并带有命令标识符和一个 bool 参数表明该命令在完成时是否出错。

在某些情况下可能想要执行一系列命令。例如如果要连接并登录到 FTP 服务器简单的实现如下

QFtp *ftp = new QFtp(parent);
ftp->connectToHost("192.168.***.***", 21);
ftp->login("wang", "123456");

在这种情况下调度了两个 FTP 命令。当最后一个调度命令完成时会发出 done() 信号并带有一个 bool 参数告诉你序列在完成时是否出错。

如果命令序列中的某个命令的执行期间发生错误则所有挂起的命令即已调度但尚未执行的命令会被清除并且不为它们发射信号。

一些命令例如 list() 会发出额外的信号listInfo()以报告其结果。

对于文件传输QFtp 可以使用主动或被动模式并且默认使用被动文件传输模式可使用 setTransferMode() 设置。

调用 setProxy() 使 QFtp 通过 FTP 代理服务器连接。

函数 hasPendingCommands() 和 clearPendingCommands() 允许查询和清除挂起的命令列表。

如果你在网络方便比较有经验想要有完整的控制可以使用 rawCommand() 来执行任意 FTP 命令。

警告当前版本的 QFtp 不完全支持非 Unix FTP 服务器。

工作流程

如果要从 FTP 服务器下载 /home/wang/ftp.qdoc 文件可以分为下面几步

ftp->connectToHost("192.168.***.***", 21);     // id == 1
ftp->login("wang", "123456");                  // id == 2
ftp->cd("/home/wang");                         // id == 3
ftp->get("ftp.qdoc");                          // id == 4
ftp->close();                                  // id == 5

流程如下

  1. connectToHost() - 指定主机和端口号连接 FTP 服务器
  2. login() - 指定用户名和密码登录到 FTP 服务器
  3. cd() - 改变服务器的工作目录为 /etc类似于 Linux 下的命令行cd /home/wang
  4. get() - 从服务器上下载文件 passwd绝对路径为/home/wang/ftp.qdoc
  5. close() - 关闭到 FTP 服务器的连接

对于该示例发射以下序列的信号

commandStarted(1)
stateChanged(HostLookup)
stateChanged(Connecting)
stateChanged(Connected)
commandFinished(1, false)

commandStarted(2)
stateChanged(LoggedIn)
commandFinished(2, false)

commandStarted(3)
commandFinished(3, false)

commandStarted(4)
dataTransferProgress(0, 8710)
dataTransferProgress(8192, 8710)
readyRead()
dataTransferProgress(8710, 8710)
readyRead()
commandFinished(4, false)

commandStarted(5)
stateChanged(Closing)
stateChanged(Unconnected)
commandFinished(5, false)

done(false)

如果要显示进度条以通知用户下载进度上述示例中的 dataTransferProgress() 信号就会很有用。readyRead() 信号告诉你有数据准备好被读取然后可以使用 bytesAvailable() 函数查询数据量并且可以使用 read() 或 readAll() 函数读取数据量。

如果上述示例登录失败例如用户名/密码错误信号将如下所示

commandStarted(1)
stateChanged(HostLookup)
stateChanged(Connecting)
stateChanged(Connected)
commandFinished(1, false)

commandStarted(2)
commandFinished(2, true)

done(true)

然后可以使用 error() 和 errorString() 函数获取有关错误的详细信息。

基本使用

连接并登录 FTP 服务器

在进行任何操作之前首先确保有可用的 FTP 服务器并存在有效的用户例如wang然后连接并登录到 FTP 服务器。

QFtp *ftp = new QFtp(parent);
ftp->connectToHost("192.168.***.***", 21);  // 主机192.168.***.*** 端口号21
ftp->login("wang", "123456");  // 用户名wang 密码123456

切换工作目录

登录成功之后默认情况下服务器的工作目录为用户主目录例如/home/wang可以使用 cd() 来进行更改。例如切换工作目录为 /home/wang/doc

ftp->cd("/home/wang/doc");

在进行其他命令操作之前先一起看看 doc 的树结构。

[wang@localhost doc]$ pwd
/home/wang/doc
[wang@localhost doc]$ tree
.
├── c++
│   └── qt5_cadaques.pdf
├── hello.sh
├── linux
│   └── linux-program.pdf
└── python
    └── hello.py

3 directories, 4 files

里面包含 3 个目录以及 4 个文件。

列出目录中的内容

要列出 dir 目录的内容可以使用 list()如果 dir 为空将列出当前目录的内容。

int QFtp::list(const QString & dir = QString())

对于找到的每个目录条目都会发出 listInfo() 信号。

// 输出文件详细信息
connect(ftp, &QFtp::listInfo, [=](const QUrlInfo &urlInfo) {
    qDebug() << urlInfo.name() << urlInfo.size() << urlInfo.owner() << urlInfo.group()
             << urlInfo.lastModified().toString("MMM dd yyyy") << urlInfo.isDir();
});
ftp->list();

注意这里只列出文件的一部分信息其他更多信息请参考 QUrlInfo 。

输出如下

“c++” 29 “1000” “1000” “十一月 28 2016” true
“hello.sh” 55 “1000” “1000” “十月 20 2016” false
“linux” 30 “1000” “1000” “十一月 28 2016” true
“python” 21 “1000” “1000” “十一月 28 2016” true

可以和服务端比对一下

[wang@localhost doc]$ ls -l
总用量 4
drwxrwxr-x. 2 wang wang 29 11月 28 10:41 c++
-rw-rw-r--. 1 wang wang 55 10月 20 15:59 hello.sh
drwxrwxr-x. 2 wang wang 30 11月 28 10:40 linux
drwxrwxr-x. 2 wang wang 21 11月 28 10:39 python

创建文件夹

要在服务器上创建一个名为 dir 的目录使用 mkdir()。

ftp->mkdir("new_dir");

创建完成之后去服务端验证一下吧

[wang@localhost doc]$ ls -l | grep new*
drwxr-xr-x  2 wang wang  6 1128 11:05 new_dir

显然目录 new_dir 已经创建完成。

删除文件/目录

有创建必然会有删除没错remove() 是删除文件rmdir() 则是删除目录。

删除文件

要从服务器中删除名为 file 的文件使用 remove()。

ftp->remove("hello.sh");  // 删除文件

删除前

[wang@localhost doc]$ ls
c++  hello.sh  linux  new_dir  python

删除后

[wang@localhost doc]$ ls
c++  linux  new_dir  python

删除目录

要从服务器中删除名为 dir 的目录使用 rmdir()。

ftp->rmdir("new_dir");  // 删除空目录
ftp->rmdir("c++");  // 删除非空目录

删除前

[wang@localhost doc]$ tree
.
├── c++
│   └── qt5_cadaques.pdf
├── linux
│   └── linux-program.pdf
├── new_dir
└── python
    └── hello.py

4 directories, 3 files

删除后

[wang@localhost doc]$ tree
.
├── c++
│   └── qt5_cadaques.pdf
├── linux
│   └── linux-program.pdf
└── python
    └── hello.py

3 directories, 3 files

显然只能删除空目录。如果目录下有文件则不能删除。

重命名

如果要对文件进行重命名使用 rename()。

ftp->rename("c++", "c");  // c++ -> c

重命名前

[wang@localhost doc]$ ls
c++  linux  python

重命名后

[wang@localhost doc]$ ls
c  linux  python

上传文件

关于上传文件有两个重载的函数

int QFtp::put(QIODevice * dev, const QString & file, TransferType type = Binary)

从 IO 设备 dev 读取数据并将其写入服务器上名为 file 的文件。从 IO 设备读取数据块因此此重载允许传输大量数据而无需立即将所有数据读入内存。

注意确保 dev 指针在操作期间有效在发出 commandFinished() 时可以安全地删除它

m_file = new QFile("E:/Qt.zip");
ftp->put(m_file, "Qt.zip");

上传完成后去服务端查看。

[wang@localhost doc]$ ls -al | grep Qt*
-rw-r--r--   1 wang wang 54246299 11月 28 11:43 Qt.zip

int QFtp::put(const QByteArray & data, const QString & file, TransferType type = Binary)

将给定数据的副本写入服务器上名为 file 的文件。

ftp->put("Hello World!\nI'am a Qter.", "readMe.txt");

上传完成后去服务端查看。

[wang@localhost doc]$ ls
c  linux  python  readMe.txt
[wang@localhost doc]$ cat readMe.txt 
Hello World!
I'am a Qter.
[wang@localhost doc]$

如果要获取上传的进度可以关联 dataTransferProgress() 信号。

下载文件

要从服务器下载文件使用 get()。

int QFtp::get(const QString & file, QIODevice * dev = 0, TransferType type = Binary)

如果 dev 为 0则当有可用的数据可读时发出 readyRead() 信号。然后可以使用 read() 或 readAll() 函数读取数据。

如果 dev 不为 0则将数据直接写入设备 dev。确保 dev 指针在操作期间有效在发出commandFinished() 信号时可以安全地删除它。在这种情况下readyRead() 信号不发出你不能用read() 或 readAll() 函数读取数据。

如果你不立即读取数据它变得可用即当 readyRead() 信号被发出时它仍然可用直到下一个命令开始。

例如如果要在有可用的数据时向用户提供数据请连接到 readyRead() 信号并立即读取数据。另一方面如果只想使用完整的数据则可以连接到 commandFinished() 信号并在 get() 命令完成后读取数据。

m_file = new QFile("E:/Qt.zip");
if (!m_file->open(QIODevice::WriteOnly)) {
    m_file->remove();
    delete m_file;
    m_file = NULL;
} else {
    ftp->get("Qt.zip", m_file);  // 下载文件
}

连接状态

当前的状态 QFtp::State 由 state() 返回 当状态改变时发出 stateChanged() 信号参数是连接的新状态。该信号通常用于 connectToHost() 或者 close() 命令也可以“自发地”发射例如当服务器意外关闭连接时。

常量 描述
QFtp::Unconnected 0 没有连接到主机
QFtp::HostLookup 1 正在进行主机名查找
QFtp::Connecting 2 正在尝试连接到主机
QFtp::Connected 3 已实现与主机的连接
QFtp::LoggedIn 4 已实现连接和用户登录
QFtp::Closing 5 连接正在关闭但尚未关闭当连接关闭时状态将为 Unconnected

例如当连接 TCP 服务器的时候使用一个 QLabel 显示连接的状态信息。

void FtpWindow::stateChanged(int state)
{
    switch (state) {
    case QFtp::Unconnected: {
        stateLabel->setText(QStringLiteral("没有连接到主机"));
        break;
    }
    case QFtp::HostLookup: {
        stateLabel->setText(QStringLiteral("正在进行主机名查找"));
        break;
    }
    case QFtp::Connecting: {
        stateLabel->setText(QStringLiteral("正在尝试连接到主机"));
        break;
    }
    case QFtp::Connected: {
        stateLabel->setText(QStringLiteral("已实现与主机的连接"));
        break;
    }
    case QFtp::LoggedIn: {
        stateLabel->setText(QStringLiteral("已实现连接和用户登录"));
        break;
    }
    case QFtp::Closing: {
        stateLabel->setText(QStringLiteral("连接正在关闭"));
        break;
    }
    default:
        break;
    }
}

获取当前命令

currentId() 和 currentCommand() 提供了有关当前执行命令。currentCommand() 返回当前 FTP 的命令类型 QFtp::Command如果没有命令正在执行则返回 None。

常量 描述
QFtp::None 0 未执行任何命令
QFtp::SetTransferMode 1 设置传输模式
QFtp::SetProxy 2 切换代理打开或关闭
QFtp::ConnectToHost 3 正在执行 connectToHost()
QFtp::Login 4 正在执行 login()
QFtp::Close 5 正在执行 close()
QFtp::List 6 正在执行 list()
QFtp::Cd 7 正在执行 cd()
QFtp::Get 8 正在执行 get()
QFtp::Put 9 正在执行 put()
QFtp::Remove 10 正在执行 remove()
QFtp::Mkdir 11 正在执行 mkdir()
QFtp::Rmdir 12 正在执行 rmdir()
QFtp::Rename 13 正在执行 rename()
QFtp::RawCommand 14 正在执行 rawCommand()

这允许你对特定命令执行特定操作。例如在 FTP 客户端中可能需要在启动 list() 命令时清除目录视图。在这种情况下可以简单地核查在连接到 commandStarted() 信号的槽函数中 currentCommand() 是否为 List。

void FtpWindow::commandStarted(int id)
{
    QFtp::Command command = ftp->currentCommand();
    switch (command) {
    case QFtp::List: {  // 正在执行 list() - 列出目录下的文件
        // 清除目录视图 QTreeWidget
        fileListTree->clear();
        break;
    }
    default:
        break;
    }

    qDebug() << "commandStarted " << id;
}

错误处理

通过 error() 和 errorString() 返回最后一次发生的错误。当接收到 commandFinished() 或者 done() 信号时如果标识 error 的 bool 参数 为 true这就非常有用了。

error() 返回的是一个 QFtp::Error 枚举类型用来标识发生的错误。

常量 描述
QFtp::NoError 0 没有发生错误
QFtp::HostNotFound 2 主机名查找失败
QFtp::ConnectionRefused 3 服务器拒绝连接
QFtp::NotConnected 4 尝试发送命令但没有到服务器的连接
QFtp::UnknownError 1 除了以上指定的错误发生

注意如果启动一个新命令错误的状态会被重置为 NoError。

errorString() 返回的是一个人类可读的字符串。通常是但不总是来自服务器的回复因此并不总是可以翻译字符串。如果消息来自 Qt则字符串已经通过 tr()。

void FtpWindow::commandFinished(int id, bool error)
{
    Q_UNUSED(id);

    QFtp::Command command = ftp->currentCommand();
    switch (command) {
    case QFtp::ConnectToHost: {  // 连接 FTP 服务器
        if (error) { // 发生错误
            qDebug() << "Error " << ftp->error() << "ErrorString " << ftp->errorString();
            QMessageBox::information(this, "FTP", QStringLiteral("无法连接到 FTP 服务器请检查主机名是否正确"));

            ftp->abort();
            ftp->deleteLater();
            ftp = NULL;
        } else {
            qDebug() << QStringLiteral("登录 FTP 服务器");
        }
        break;
    }
    default:
        break;
    }
}

文件传输模式

枚举 QFtp::TransferMode

FTP 使用两个套接字连接一个用于命令另一个用于发送数据。 虽然命令连接始终由客户端发起但第二个连接可以由客户端或服务器发起。

此枚举定义客户端被动模式还是服务器活动模式应设置数据连接。

常量 描述
QFtp::Passive 1 客户端连接到服务器以传输其数据
QFtp::Active 0 服务器连接到客户端以传输其数据

数据传输类型

枚举 QFtp::TransferType

此枚举标识使用 get 和 put 命令进行数据传输的类型。

常量 描述
QFtp::Binary 0 数据将以二进制模式传输
QFtp::Ascii 1 数据将以 Ascii 模式传输换行符将转换为本地格式

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
19299 0
如何设置阿里云服务器安全组?阿里云安全组规则详细解说
阿里云安全组设置详细图文教程(收藏起来) 阿里云服务器安全组设置规则分享,阿里云服务器安全组如何放行端口设置教程。阿里云会要求客户设置安全组,如果不设置,阿里云会指定默认的安全组。那么,这个安全组是什么呢?顾名思义,就是为了服务器安全设置的。安全组其实就是一个虚拟的防火墙,可以让用户从端口、IP的维度来筛选对应服务器的访问者,从而形成一个云上的安全域。
16897 0
windows server 2008阿里云ECS服务器安全设置
最近我们Sinesafe安全公司在为客户使用阿里云ecs服务器做安全的过程中,发现服务器基础安全性都没有做。为了为站长们提供更加有效的安全基础解决方案,我们Sinesafe将对阿里云服务器win2008 系统进行基础安全部署实战过程! 比较重要的几部分 1.
11642 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
24290 0
腾讯云服务器 设置ngxin + fastdfs +tomcat 开机自启动
在tomcat中新建一个可以启动的 .sh 脚本文件 /usr/local/tomcat7/bin/ export JAVA_HOME=/usr/local/java/jdk7 export PATH=$JAVA_HOME/bin/:$PATH export CLASSPATH=.
13128 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,云吞铺子总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系统盘、创建快照、配置安全组等操作如何登录ECS云服务器控制台? 1、先登录到阿里云ECS服务器控制台 2、点击顶部的“控制台” 3、通过左侧栏,切换到“云服务器ECS”即可,如下图所示 通过ECS控制台的远程连接来登录到云服务器 阿里云ECS云服务器自带远程连接功能,使用该功能可以登录到云服务器,简单且方便,如下图:点击“远程连接”,第一次连接会自动生成6位数字密码,输入密码即可登录到云服务器上。
32641 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
14307 0
+关注
潘志闻
互联网挖掘者
290
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
OceanBase 入门到实战教程
立即下载
阿里云图数据库GDB,加速开启“图智”未来.ppt
立即下载
实时数仓Hologres技术实战一本通2.0版(下)
立即下载