포춘 클라이언트

네트워크 서비스용 클라이언트를 만드는 방법을 보여줍니다.

이 예에서는 QTcpSocket 을 사용하며, Fortune 서버 예제 또는 스레드형 Fortune 서버 예제와 함께 실행하도록 되어 있습니다.

Screenshot of the Fortune Client example

이 예에서는 간단한 QDataStream 기반 데이터 전송 프로토콜을 사용하여 운세 서버(운세 서버 예제에서)에 텍스트 한 줄을 요청합니다. 클라이언트는 단순히 서버에 연결하여 운세를 요청합니다. 그러면 서버는 운세 텍스트가 포함된 QString 로 응답합니다.

QTcpSocket 는 네트워크 프로그래밍에 대한 두 가지 일반적인 접근 방식을 지원합니다:

  • 비동기(비차단) 접근 방식. 제어가 Qt의 이벤트 루프로 돌아올 때 작업이 예약되고 수행됩니다. 연산이 완료되면 QTcpSocket 은 신호를 방출합니다. 예를 들어 QTcpSocket::connectToHost()는 즉시 반환되고, 연결이 설정되면 QTcpSocketconnected()를 반환합니다.
  • 동기식(블로킹) 접근 방식. 비GUI 및 멀티스레드 애플리케이션에서는 waitFor...() 함수(예: QTcpSocket::waitForConnected())를 호출하여 신호에 연결하는 대신 작업이 완료될 때까지 호출 스레드를 일시 중단할 수 있습니다.

이 예제에서는 비동기 접근 방식을 보여드리겠습니다. 블로킹 포춘 클라이언트 예제는 동기식 접근 방식을 보여줍니다.

이 클래스에는 일부 데이터와 몇 개의 비공개 슬롯이 포함되어 있습니다:

class Client : public QDialog
{
    Q_OBJECT

public:
    explicit Client(QWidget *parent = nullptr);

private slots:
    void requestNewFortune();
    void readFortune();
    void displayError(QAbstractSocket::SocketError socketError);
    void enableGetFortuneButton();

private:
    QComboBox *hostCombo = nullptr;
    QLineEdit *portLineEdit = nullptr;
    QLabel *statusLabel = nullptr;
    QPushButton *getFortuneButton = nullptr;

    QTcpSocket *tcpSocket = nullptr;
    QDataStream in;
    QString currentFortune;
};

GUI를 구성하는 위젯 외에 데이터 멤버에는 QTcpSocket 포인터, 소켓에서 작동하는 QDataStream 객체, 현재 표시되는 운세 텍스트의 사본이 포함됩니다.

소켓은 클라이언트 생성자에서 초기화됩니다. 메인 위젯을 부모로 전달하므로 소켓을 삭제하는 것에 대해 걱정할 필요가 없습니다:

Client::Client(QWidget *parent)
    : QDialog(parent)
    , hostCombo(new QComboBox)
    , portLineEdit(new QLineEdit)
    , getFortuneButton(new QPushButton(tr("Get Fortune")))
    , tcpSocket(new QTcpSocket(this))
{
    ...
    in.setDevice(tcpSocket);
    in.setVersion(QDataStream::Qt_6_5);

프로토콜은 QDataStream 을 기반으로 하므로 스트림 디바이스를 새로 생성된 소켓으로 설정합니다. 그런 다음 스트림의 프로토콜 버전을 QDataStream::Qt_6_5 로 명시적으로 설정하여 클라이언트와 서버가 어떤 버전의 Qt를 사용하든 포춘 서버와 동일한 버전을 사용하도록 합니다.

이 예제에서 필요한 QTcpSocket 신호는 데이터가 수신되었음을 나타내는 QTcpSocket::readyRead()와 연결 오류를 포착하는 데 사용할 QTcpSocket::errorOccurred()뿐입니다:

    ...
    connect(tcpSocket, &QIODevice::readyRead, this, &Client::readFortune);
    connect(tcpSocket, &QAbstractSocket::errorOccurred,
    ...
}

Get Fortune 버튼을 클릭하면 requestNewFortune() 슬롯이 호출됩니다:

void Client::requestNewFortune()
{
    getFortuneButton->setEnabled(false);
    tcpSocket->abort();
    tcpSocket->connectToHost(hostCombo->currentText(),
                             portLineEdit->text().toInt());
}

이전 연결이 종료되기 전에 사용자가 Get Fortune 을 클릭할 수 있으므로 QTcpSocket::abort()을 호출하여 이전 연결을 중단하는 것으로 시작합니다. (연결되지 않은 소켓에서는 이 함수가 아무 일도 하지 않습니다.) 그런 다음 사용자 인터페이스에서 호스트 이름과 포트를 인수로 전달하여 QTcpSocket::connectToHost()를 호출하여 포춘 서버에 연결합니다.

connectToHost()를 호출하면 다음 두 가지 중 하나가 발생할 수 있습니다:

  • 연결이 설정됩니다. 이 경우 서버가 포춘을 전송합니다. QTcpSocket 은 데이터 블록을 수신할 때마다 readyRead()을 전송합니다.
  • 오류가 발생합니다. 연결이 실패했거나 끊어진 경우 사용자에게 알려야 합니다. 이 경우 QTcpSocketerrorOccurred()을 전송하고 Client::displayError() 을 호출합니다.

errorOccurred()의 경우를 먼저 살펴봅시다:

void Client::displayError(QAbstractSocket::SocketError socketError)
{
    switch (socketError) {
    case QAbstractSocket::RemoteHostClosedError:
        break;
    case QAbstractSocket::HostNotFoundError:
        QMessageBox::information(this, tr("Fortune Client"),
                                 tr("The host was not found. Please check the "
                                    "host name and port settings."));
        break;
    case QAbstractSocket::ConnectionRefusedError:
        QMessageBox::information(this, tr("Fortune Client"),
                                 tr("The connection was refused by the peer. "
                                    "Make sure the fortune server is running, "
                                    "and check that the host name and port "
                                    "settings are correct."));
        break;
    default:
        QMessageBox::information(this, tr("Fortune Client"),
                                 tr("The following error occurred: %1.")
                                 .arg(tcpSocket->errorString()));
    }

    getFortuneButton->setEnabled(true);
}

QMessageBox::information()를 사용하여 모든 오류를 대화 상자에 표시합니다. 포춘 서버 프로토콜은 서버가 연결을 닫는 것으로 끝나기 때문에 QTcpSocket::RemoteHostClosedError는 조용히 무시됩니다.

이제 readyRead()를 대체할 수 있습니다. 이 신호는 Client::readFortune() 에 연결됩니다:

void Client::readFortune()
{
    in.startTransaction();

    QString nextFortune;
    in >> nextFortune;

    if (!in.commitTransaction())
        return;

    if (nextFortune == currentFortune) {
        QTimer::singleShot(0, this, &Client::requestNewFortune);
        return;
    }

    currentFortune = nextFortune;
    statusLabel->setText(currentFortune);
    getFortuneButton->setEnabled(true);
}

이제 TCP는 데이터 스트림 전송을 기반으로 하므로 전체 포춘을 한 번에 가져올 수 없습니다. 특히 느린 네트워크에서는 데이터가 여러 개의 작은 조각으로 수신될 수 있습니다. QTcpSocket 은 들어오는 모든 데이터를 버퍼링하고 새로운 블록이 도착할 때마다 readyRead()을 전송하며, 구문 분석을 시작하기 전에 필요한 모든 데이터를 수신했는지 확인하는 것이 우리의 임무입니다.

이를 위해 QDataStream 읽기 트랜잭션을 사용합니다. 이는 스트림 데이터를 내부 버퍼에 계속 읽고 불완전한 읽기가 발생할 경우 롤백합니다. 소켓에서 새 데이터가 수신되었음을 나타내기 위해 스트림 상태를 재설정하는 startTransaction()을 호출하는 것으로 시작합니다. QDataStream 의 스트리밍 연산자를 사용하여 소켓에서 QString 로 포춘을 읽습니다. 읽기가 완료되면 QDataStream::commitTransaction()를 호출하여 트랜잭션을 완료합니다. 전체 패킷을 수신하지 못한 경우, 이 함수는 스트림 데이터를 초기 위치로 복원하고 그 후 새로운 readyRead() 신호를 기다릴 수 있습니다.

읽기 트랜잭션이 성공하면 QLabel::setText()를 호출하여 운세를 표시합니다.

예제 프로젝트 @ code.qt.io

포춘 서버포춘 클라이언트 차단도참조하세요 .

© 2025 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.