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

QTcpSocket 네트워크 프로그래밍에 대한 두 가지 일반적인 접근 방식을 지원합니다:
- 비동기(비블로킹) 방식. 작업은 스케줄링되어 제어권이 Qt의 이벤트 루프로 돌아왔을 때 수행됩니다. 작업이 완료되면 ` QTcpSocket `가 신호를 발생시킵니다. 예를 들어, ` QTcpSocket::connectToHost()`는 즉시 반환되며, 연결이 설정되면 ` QTcpSocket `가 ` connected()` 신호를 발생시킵니다.
- 동기식(블로킹) 접근 방식. 비 GUI 및 멀티스레드 애플리케이션에서는 신호에 연결하는 대신
waitFor...()함수(예: QTcpSocket::waitForConnected())를 호출하여 작업이 완료될 때까지 호출 스레드를 일시 중지할 수 있습니다.
구현 방식은 Fortune Client 예제와 매우 유사하지만, 메인 클래스의 멤버로 QTcpSocket 를 정의하여 메인 스레드에서 비동기 네트워킹을 수행하는 대신, 별도의 스레드에서 모든 네트워크 작업을 수행하고 QTcpSocket 의 차단형 API를 사용할 것입니다.
이 예제의 목적은 사용자 인터페이스의 반응성을 저하시키지 않으면서 네트워킹 코드를 단순화하는 데 사용할 수 있는 패턴을 보여주는 것입니다. Qt Network의 차단형 API를 사용하면 코드가 더 단순해지는 경우가 많지만, 차단 동작 특성상 사용자 인터페이스가 멈추는 것을 방지하기 위해 GUI 스레드가 아닌 곳에서만 사용해야 합니다. 하지만 많은 사람들이 생각하는 것과는 달리, QThread 와 함께 스레드를 사용한다고 해서 애플리케이션에 관리하기 어려운 복잡성이 반드시 추가되는 것은 아닙니다.
먼저 네트워크 코드를 처리하는 FortuneThread 클래스부터 살펴보겠습니다.
class FortuneThread : public QThread
{
Q_OBJECT
public:
FortuneThread(QObject *parent = nullptr);
~FortuneThread();
void requestNewFortune(const QString &hostName, quint16 port);
void run() override;
signals:
void newFortune(const QString &fortune);
void error(int socketError, const QString &message);
private:
QString hostName;
quint16 port;
QMutex mutex;
QWaitCondition cond;
bool quit;
};FortuneThread는 QThread 의 서브클래스로, 운세 요청을 스케줄링하기 위한 API를 제공하며, 운세를 전달하고 오류를 보고하는 신호를 가지고 있습니다. requestNewFortune()을 호출하여 새로운 운세를 요청할 수 있으며, 그 결과는 newFortune() 신호를 통해 전달됩니다. 오류가 발생하면 error() 신호가 발송됩니다.
requestNewFortune()은 메인 GUI 스레드에서 호출되지만, 이 함수가 저장하는 호스트 이름과 포트 값은 FortuneThread의 스레드에서 접근된다는 점을 유의해야 합니다. 서로 다른 스레드에서 FortuneThread의 데이터 멤버를 동시에 읽고 쓰게 되므로, QMutex 를 사용하여 액세스를 동기화합니다.
void FortuneThread::requestNewFortune(const QString &hostName, quint16 port)
{
QMutexLocker locker(&mutex);
this->hostName = hostName;
this->port = port;
if (!isRunning())
start();
else
cond.wakeOne();
}requestNewFortune() 함수는 포춘 서버의 호스트 이름과 포트를 멤버 데이터로 저장하며, 이 데이터를 보호하기 위해 ` QMutexLocker `를 사용하여 뮤텍스를 잠급니다. 그런 다음 스레드가 이미 실행 중이 아닌 경우 스레드를 시작합니다. ` QWaitCondition::wakeOne()` 호출에 대해서는 나중에 다시 다루겠습니다.
void FortuneThread::run()
{
mutex.lock();
QString serverName = hostName;
quint16 serverPort = port;
mutex.unlock();run() 함수에서는 먼저 뮤텍스 잠금을 획득하고, 멤버 데이터에서 호스트 이름과 포트를 가져온 다음, 잠금을 다시 해제합니다. 우리가 방지하고자 하는 상황은, 이 데이터를 가져오는 동안 requestNewFortune() 이 동시에 호출될 수 있다는 점입니다. QString 는 재진입 가능하지만 스레드 안전하지는 않으므로, 한 요청의 호스트 이름을 읽고 다른 요청의 포트를 읽는다는 희박한 위험도 피해야 합니다. 그리고 짐작하셨겠지만, FortuneThread는 한 번에 하나의 요청만 처리할 수 있습니다.
이제 run() 함수는 루프에 진입합니다:
while (!quit) {
const int Timeout = 5 * 1000;
QTcpSocket socket;
socket.connectToHost(serverName, serverPort);quit가 false인 한, 이 루프는 계속해서 운세 메시지를 요청합니다. 스택에 QTcpSocket 를 생성하여 첫 번째 요청을 시작한 다음, connectToHost()를 호출합니다. 이로 인해 비동기 작업이 시작되며, 제어권이 Qt의 이벤트 루프로 돌아온 후 QTcpSocket 가 connected() 또는 error()를 발생시킵니다.
if (!socket.waitForConnected(Timeout)) {
emit error(socket.error(), socket.errorString());
return;
}하지만 비 GUI 스레드에서 실행 중이므로 사용자 인터페이스가 차단되는 것에 대해 걱정할 필요가 없습니다. 따라서 이벤트 루프에 진입하는 대신, 단순히 ` QTcpSocket::waitForConnected()`를 호출합니다. 이 함수는 ` QTcpSocket `가 `connected()`를 방출하거나 오류가 발생할 때까지 호출 스레드를 차단하며 대기합니다. connected()가 발생하면 함수는 true를 반환하고, 연결에 실패하거나 타임아웃(이 예제에서는 5초 후 발생)되면 false를 반환합니다. QTcpSocket::waitForConnected()는 다른 waitFor...() 함수들과 마찬가지로 QTcpSocket 의 차단형 API의 일부입니다.
이 문장이 실행된 후에는 작업할 수 있는 연결된 소켓이 생깁니다.
QDataStream in(&socket);
in.setVersion(QDataStream::Qt_6_5);
QString fortune;이제 소켓을 QDataStream 의 생성자에 전달하여 QDataStream 객체를 생성할 수 있으며, 다른 클라이언트 예제와 마찬가지로 스트림 프로토콜 버전을 QDataStream::Qt_6_5 로 설정합니다.
do {
if (!socket.waitForReadyRead(Timeout)) {
emit error(socket.error(), socket.errorString());
return;
}
in.startTransaction();
in >> fortune;
} while (!in.commitTransaction());이어서 QTcpSocket::waitForReadyRead()를 호출하여 운세 문자열 데이터를 기다리는 루프를 시작합니다. false가 반환되면 작업을 중단합니다. 이 문장 이후에는 스트림 읽기 트랜잭션을 시작합니다. QDataStream::commitTransaction()가 true를 반환하면 루프를 종료하며, 이는 운세 문자열 로딩이 성공했음을 의미합니다. 결과 운세 문자열은 newFortune()을 호출하여 출력됩니다:
mutex.lock();
emit newFortune(fortune);
cond.wait(&mutex);
serverName = hostName;
serverPort = port;
mutex.unlock();
}루프의 마지막 단계에서는 멤버 데이터에서 안전하게 읽을 수 있도록 뮤텍스를 획득합니다. 그런 다음 QWaitCondition::wait()를 호출하여 스레드를 잠들게 합니다. 이 시점에서 requestNewFortune()으로 돌아가 wakeOne() 호출을 자세히 살펴보겠습니다:
void FortuneThread::requestNewFortune(const QString &hostName, quint16 port)
{
...
if (!isRunning())
start();
else
cond.wakeOne();
}여기서 발생한 상황은 스레드가 새로운 요청을 기다리다가 잠들게 되므로, 새로운 요청이 도착하면 스레드를 다시 깨워야 한다는 것입니다. QWaitCondition 는 스레드에서 이와 같은 깨우기 신호를 보내는 데 자주 사용됩니다.
FortuneThread::~FortuneThread()
{
mutex.lock();
quit = true;
cond.wakeOne();
mutex.unlock();
wait();
}FortuneThread 예제를 마무리하며, 다음은 quit를 true로 설정하고, 스레드를 깨운 다음 스레드가 종료될 때까지 기다린 후 반환하는 소멸자입니다. 이를 통해 run() 내의 while 루프가 현재 반복을 완료할 수 있습니다. run()이 반환되면 스레드는 종료되고 소멸됩니다.
이제 BlockingClient 클래스를 살펴보겠습니다.
class BlockingClient : public QWidget
{
Q_OBJECT
public:
BlockingClient(QWidget *parent = nullptr);
private slots:
void requestNewFortune();
void showFortune(const QString &fortune);
void displayError(int socketError, const QString &message);
void enableGetFortuneButton();
private:
QLabel *hostLabel;
QLabel *portLabel;
QLineEdit *hostLineEdit;
QLineEdit *portLineEdit;
QLabel *statusLabel;
QPushButton *getFortuneButton;
QPushButton *quitButton;
QDialogButtonBox *buttonBox;
FortuneThread thread;
QString currentFortune;
};BlockingClient는 Fortune Client 예제의 Client 클래스와 매우 유사하지만, 이 클래스에서는 QTcpSocket 에 대한 포인터 대신 FortuneThread 멤버를 저장합니다. 사용자가 "Get Fortune" 버튼을 클릭하면 동일한 슬롯이 호출되지만, 그 구현은 약간 다릅니다:
connect(&thread, &FortuneThread::newFortune,
this, &BlockingClient::showFortune);
connect(&thread, &FortuneThread::error,
this, &BlockingClient::displayError);FortuneThread의 두 시그널인 newFortune()과 error()를 (이전 예제의 QTcpSocket::readyRead() 및 QTcpSocket::error()와 다소 유사함) requestNewFortune() 및 displayError()에 연결합니다.
void BlockingClient::requestNewFortune()
{
getFortuneButton->setEnabled(false);
thread.requestNewFortune(hostLineEdit->text(),
portLineEdit->text().toInt());
}requestNewFortune() 슬롯은 FortuneThread::requestNewFortune()을 호출하여 요청을 스케줄링합니다. 스레드가 새로운 운세를 수신하고 newFortune()을 방출하면, 우리의 showFortune() 슬롯이 호출됩니다:
void BlockingClient::showFortune(const QString &nextFortune)
{
if (nextFortune == currentFortune) {
requestNewFortune();
return;
}
currentFortune = nextFortune;
statusLabel->setText(currentFortune);
getFortuneButton->setEnabled(true);
}여기서는 인자로 전달받은 운세를 단순히 표시합니다.
Fortune Client 및 Fortune Server도 참조하십시오 .
© 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.