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
Welcome 모드를 열고 Examples 에서 예제를 선택하십시오. 자세한 내용은 Qt Creator: 튜토리얼: 빌드 및 실행을 참조하십시오.
- Qt Extension for Visual Studio Code
Command Palette 에서 Qt: Open Qt examples 명령을 실행하고, 목록에서 예제를 선택하십시오. 자세한 내용은 Qt Extension for Visual Studio Code: 튜토리얼: 빌드 및 실행을 참조하십시오.
상태 머신 컴파일하기
프로젝트 빌드 파일에 다음 줄을 추가하여 Qt SCXML 모듈을 링크합니다.
qmake를 사용하여 ftpclient.pro 파일에 다음 내용을 추가합니다.
QT = core scxml network그런 다음 컴파일할 상태 머신을 지정합니다:
STATECHARTS += simpleftp.scxmlCMake를 사용하여 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 <server> <file>.
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.