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 模式传输换行符将转换为本地格式
目录
相关文章
|
存储
Qt之QLCDNumber
Qt之QLCDNumber
328 0
|
Linux Android开发 C++
Qt资料大全
简述 发福利了、发福利了、发福利了,重要的事情说三遍。。。 为了方便更多Qter了解、学习Qt,现将相关资源进行整理,主要内容包括:Qt官网、编码风格、GitHub &amp; Third-Party、社区论坛、博客、书籍等。 满满的都是干货,独乐乐不如众乐乐。。。 简述 Qt官网 编码风格 GitHub Third-Party 社区论坛 博客 书籍 更多
2856 0
|
存储 安全 Windows
Qt之QEvent
简述 QEvent 类是所有事件类的基类,事件对象包含事件参数。 Qt 的主事件循环(QCoreApplication::exec())从事件队列中获取本地窗口系统事件,将它们转化为 QEvents,然后将转换后的事件发送给 QObjects。 一般来说,事件来自底层窗口系统(spontaneous() 返回 true),但也可以使用 QCoreApplication:
1770 0
|
数据安全/隐私保护
Qt之QUrl
简述 QUrl 类提供了一个方便的接口使用 URLs。 它可以解析和构造编码和未编码形式的 URLs。QUrl 也支持国际化域名(IDNs)。 简述 详细描述 错误检查 字符转换 URL格式 scheme Authority user info path query fragment 深入使用 相对路径 用户输入 文件名 主机端口 本地文件 百分比编码
6258 0
|
存储
Qt之QUrlQuery
简述 QUrlQuery 类提供了一种方法来操纵 URL 查询中的 key-value 对。 简述 详细描述 编码 处理空格和加号 全解码 非标准分隔符 使用 QUrlQuery 分隔符 查询 删除 是否为空 详细描述 QUrlQuery 用来解析 URL 中的查询字符串,像下面这样: 上述的查询字符串在 URL 中 被用来传输
2764 0
|
索引
Qt之QToolBox
简述 QToolBox类提供了一个列(选项卡式的)部件条目。 QToolBox可以在一个tab列上显示另外一个,并且当前的item显示在当前的tab下面。每个tab都在tab列中有一个索引位置。tab的item是一个QWidget 。 简述 详细描述 使用 效果 源码 详细描述 每个item都有一个itemText()、一个可选的itemI
2480 0
Qt之QDateEdit和QTimeEdit
简述 QDateEdit类提供了一个部件,用于编辑日期。QTimeEdit类提供了一个部件,用于编辑时间。 简述 详细描述 基本使用 各司其职 莫强求 更多参考 详细描述 QDateEdit和QTimeEdit均继承自QDateTimeEdit,许多特性和功能都有QDateTimeEdit提供。这些都是相关属性: QDateEdit da
2870 0
|
缓存 Unix Windows
Qt之QLocalSocket
简述 QLocalSocket类提供了一个本地socket。 在Windows中,这是一个命名管道;在Unix中,这是一个本地网域socket。 如果发生错误,socketError()会返回错误的类型,errorString()则返回人类可读的错误描述。 虽然QLocalSocket是一个事件循环使用而设计,它也可以不被如此使用。这种情况下,必须使用 waitF
2611 0
|
网络协议
Qt之QHostAddress
简述 QHostAddress类提供一个IP地址。 这个类提供一种独立于平台和协议的方式来保存IPv4和IPv6地址。 QHostAddress通常与QTcpSocket、QTcpServer、QUdpSocket一起使用,来连接到主机或建立一个服务器。 可以通过setAddress()来设置一个主机地址,使用toIPv4Address()、toIPv6Address
3544 0