SCXML FTP 客户端
使用状态机实现简单的 FTP 客户端。
FTP 客户端 使用 Qt SCXML 实现了一个 FTP 客户端,可以通过发送从状态机事件转换的 FTP 控制消息,并将服务器响应转换成状态机事件来与 FTP 服务进行通信。从 FTP 服务器接收到的数据将在控制台上打印。
RFC 959 指定了 FTP 客户端的命令处理状态图。它们可以轻松地转换为 SCXML,以利用 SCXML 嵌套状态。客户端和服务器之间的连接以及数据传输是通过 C++ 实现的。此外,还使用了 Qt 信号和槽。
状态机具有以下状态
- I 为初始状态。
- B 用于发送命令。
- S 用于成功。
- F 用于失败。
- W 用于等待回复。
- P 用于在服务器请求时提供密码。
状态机在 simpleftp.scxml 文件中指定,并编译到实现 FTP 协议逻辑的 FtpClient
类中。它通过改变状态和发送外部事件来响应用户输入和控制通道的回复。此外,我们还实现了处理 TCP 套接字和服务器以及转换行结束符的 FtpControlChannel
类和 FtpDataChannel
类。
运行示例
要从 Qt Creator 运行示例,请打开 欢迎 模式并从 示例 中选择示例。有关更多信息,请参阅 构建和运行示例。
编译状态机
通过在项目的构建文件中添加以下行来链接到 Qt SCXML 模块。
使用 qmake,我们将以下内容添加到 ftpclient.pro
QT = core scxml network
然后我们指定要编译的状态机
STATECHARTS += simpleftp.scxml
使用 CMake,我们将以下内容添加到 CMakeLists.txt
find_package(Qt6 REQUIRED COMPONENTS Core Network Scxml) target_link_libraries(ftpclient PRIVATE Qt6::Core Qt6::Network Qt6::Scxml )
然后我们指定要编译的状态机
qt6_add_statecharts(ftpclient simpleftp.scxml )
Qt SCXML 编译器 qscxmlc
将自动运行以生成 simpleftp.h 和 simpleftp.cpp,并将它们作为头文件和源文件适当添加到项目中。
创建状态机实例
我们在 main.cpp 文件中实例化了生成的 FtpClient
类,以及 FtpDataChannel
和 FtpControlChannel
类。
#include "ftpcontrolchannel.h" #include "ftpdatachannel.h" #include "simpleftp.h" ... int main(int argc, char *argv[]) { ... QCoreApplication app(argc, argv); FtpClient ftpClient; FtpDataChannel dataChannel; FtpControlChannel controlChannel; ...
与 FTP 服务器通信
我们将从服务器检索的所有数据打印到控制台上
QObject::connect(&dataChannel, &FtpDataChannel::dataReceived, [](const QByteArray &data) { std::cout << data.constData() << std::flush; });
我们将服务器响应转换为状态机事件
QObject::connect(&controlChannel, &FtpControlChannel::reply, &ftpClient, [&ftpClient](int code, const QString ¶meters) { ftpClient.submitEvent(QString("reply.%1xx") .arg(code / 100), parameters); });
我们将状态机中的命令转换为 FTP 控制消息
ftpClient.connectToEvent("submit.cmd", &controlChannel, [&controlChannel](const QScxmlEvent &event) { controlChannel.command(event.name().mid(11).toUtf8(), event.data().toMap()["params"].toByteArray()); });
我们发送命令以以匿名用户身份登录到 FTP 服务器,宣布数据连接的端口,并检索文件
QList<Command> commands({ {"cmd.USER", "anonymous"},// login {"cmd.PORT", ""}, // announce port for data connection, // args added below. {"cmd.RETR", file} // retrieve a file });
我们指定 FTP 客户端应在进入 B 状态时发送下一个命令
ftpClient.connectToState("B", QScxmlStateMachine::onEntry([&]() { if (commands.isEmpty()) { app.quit(); return; } Command command = commands.takeFirst(); qDebug() << "Posting command" << command.cmd << command.args; ftpClient.submitEvent(command.cmd, command.args); }));
我们指定,如果服务器请求密码,FTP 客户端应该发送一个空字符串。
ftpClient.connectToState("P", QScxmlStateMachine::onEntry([&ftpClient]() { qDebug() << "Sending password"; ftpClient.submitEvent("cmd.PASS", QString()); }));
最后,我们将连接到由方法第一个参数指定的 FTP 服务器,并检索由第二个参数指定的文件。
controlChannel.connectToServer(server); QObject::connect(&controlChannel, &FtpControlChannel::opened, &dataChannel, [&](const QHostAddress &address, int) { dataChannel.listen(address); commands[1].args = dataChannel.portspec(); ftpClient.start(); });
例如,以下调用会从指定的服务器打印出指定的文件:ftpclient <服务器> <文件>
。
© 2024 The Qt Company Ltd. 本文档中包含的文档贡献的版权属于各自的拥有者。本文档根据由自由软件基金会发布的GNU 自由文档许可证第 1.3 版许可使用。Qt 及其相关标志是 The Qt Company Ltd. 在芬兰和其他国家的商标。所有其他商标均为其各自所有者的财产。