简述
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
流程如下
- connectToHost() - 指定主机和端口号连接 FTP 服务器
- login() - 指定用户名和密码登录到 FTP 服务器
- cd() - 改变服务器的工作目录为 /etc类似于 Linux 下的命令行cd /home/wang
- get() - 从服务器上下载文件 passwd绝对路径为/home/wang/ftp.qdoc
- 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 11月 28 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 模式传输换行符将转换为本地格式 |