SCXML FTP クライアント
ステートマシンを使用したシンプルな FTP クライアントを実装します。
FTP Client はQt SCXML を使用して FTP クライアントを実装します。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 からサンプルを実行するには、Welcome モードを開き、Examples からサンプルを選択します。詳細については、Building and Running an Example を参照してください。
ステートマシンのコンパイル
プロジェクトのビルドファイルに以下の行を追加して、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 が生成され、ヘッダとソースとしてプロジェクトに追加されます。
ステートマシンのインスタンス化
生成されたFtpClient
クラスと、main.cppファイル内の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()); }));
最後に、メソッドの第1引数として指定されたFTPサーバーに接続し、第2引数として指定されたファイルを取得する:
controlChannel.connectToServer(server); QObject::connect(&controlChannel, &FtpControlChannel::opened, &dataChannel, [&](const QHostAddress &address, int) { dataChannel.listen(address); commands[1].args = dataChannel.portspec(); ftpClient.start(); });
例えば、次の呼び出しは、指定されたサーバーから指定されたファイルを表示します:ftpclient <server> <file>
.
©2024 The Qt Company Ltd. 本書に含まれる文書の著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。