28. Essential Network Programming
Qt에 제공하는 네트워크 모듈을 이용해 응용 어플리케이션을 개발하는 방법은 C++에서 제공하는 네트워크 라이브러리를 사용하는 것보다 쉽고 빠르게 구현이 가능하다. 가장 큰 이유는 Qt에서는 네트워크 프로그래밍이 쉬운 이유는 Signal/Slot 기반으로 네트워크 기반 응용 어플리케이션을 구현할 수 있기 때문이다. 예를 들어 기존의 C++ 기반 채팅 서버를 구현한다고 가정해 보도록 하자. 구현해야 하는 기능 중 클라이언트의 메시지를 처리하는 부분을 한번 생각해 보자. 여러 명의 클라이언트들이 서버에 접속되어 있는 상황에서 특정 클라이언트가 메시지를 보내면, 그 메시지를 서버에 접속한 클라이언트들에게 모두 전송해야한다. 이 기능을 C++에서 제공하는 표준 네트워크 라이브러리를 사용해 구현해보자. 먼저 클라이언트별로 Thread가 동작되고 있어야 한다. 10명의 클라이언트가 접속되어 있으면 10개의 Thread가 생성되고 동작되어야 한다. 하지만 Qt를 이용한다면 이 기능을 쉽게 구현할 수 있다. 예를 들어 특정 클라이언트 가 메시지를 보냈다면 해당 클라이언트가 보내는 Signal을 특정 Slot 함수와 연결해주 면 된다. 즉 Thread 대신, Signal과 Slot 만 연결해주면 된다.
아래 예제 소스코드는 Qt 에서 제공하는 Network 모듈을 이용해 Signal 과 Slot 으로 구현한 예제이다.
// QHttpSocket 생성자 정의
// QObject 클래스의 부모 생성자를 호출하고 QTcpSocket을 사용하여 소켓을 초기화
QHttpSocket::QHttpSocket(QObject *parent) : QObject(parent)
{
// QTcpSocket 객체를 생성하고 부모로 현재 객체(this)를 설정하여 메모리 관리를 자동으로 처리
QTcpSocket *socket = new QTcpSocket(this);
// 소켓이 서버에 성공적으로 연결되었을 때 'connected()' 시그널을
// 'slotConnected()' 슬롯과 연결하여 연결 완료 시 해당 슬롯을 호출
connect(socket, SIGNAL(connected()), this, SLOT(slotConnected()));
// ... 나머지 코드가 이 위치에 추가될 수 있음
}
// slotConnected 함수는 서버에 성공적으로 연결되었을 때 호출되는 슬롯
void QHttpSocket::slotConnected()
{
// 디버그 메시지 출력: Q_FUNC_INFO는 현재 함수 이름을 포함한 정보를 출력
qDebug("[%s] CONNECTED", Q_FUNC_INFO);
// 서버에 HTTP HEAD 요청을 전송 (HTTP/1.0 프로토콜을 사용)
// 소켓을 통해 서버에 HEAD 요청을 보내서 응답의 헤더 정보만 가져오는 HTTP 요청
socket->write("HEAD / HTTP/1.0\r\n\r\n\r\n\r\n");
}
// 추가 코드가 더 있을 수 있음
각 부분 설명:
QHttpSocket 생성자:
QHttpSocket::QHttpSocket(QObject *parent)는 부모 객체를 전달받아 초기화하는 생성자이다. 이 생성자는 QObject의 부모 생성자를 호출하고, 자식 객체로서 메모리를 관리할 수 있는 QTcpSocket을 생성한다.
QTcpSocket *socket = new QTcpSocket(this);는 TCP 소켓을 생성하며, 이 소켓을 통해 서버와의 통신을 관리한다.
connect():
connect(socket, SIGNAL(connected()), this, SLOT(slotConnected()));는 Qt의 시그널-슬롯 메커니즘을 활용하여, 소켓이 서버에 성공적으로 연결되면 slotConnected() 함수가 자동으로 호출되도록 한다.
connected() 시그널은 서버에 연결되었을 때 발생하며, 이 시그널이 발생하면 슬롯 slotConnected()가 호출된다.
slotConnected():
이 함수는 서버와의 연결이 완료된 후 호출된다. 여기서 qDebug("[%s] CONNECTED", Q_FUNC_INFO);를 사용해 현재 함수 이름을 포함한 디버깅 메시지를 출력한다.
socket->write("HEAD / HTTP/1.0\r\n\r\n\r\n\r\n");는 서버에 HTTP HEAD 요청을 보내는 부분으로, HTTP 요청 메시지를 소켓을 통해 서버로 전송한다.
이 요청은 HTTP/1.0 프로토콜을 사용하며, HEAD 메서드는 HTTP 응답의 헤더만 가져오는 요청이다.
위의 예제 소스코드는 QTcpSocket 클래스의 오브젝트를 선언한다. connect( ) 함수를 이용해 새로운 클라이언트가 서버에 접속하면 connected( ) Signal을 발생한다. 이 Signal 은 connect( ) 함수를 이용해 slotConnected( ) Slot 함수가 호출된다.
즉 Qt에서는 새로운 접속자를 처리하기 위한 Thread 구현 없이 Slot 함수만 구현하면 되기 때문에 쉽게 구현할 수 있다.
Qt 네트워크 모듈은 TCP/UDP 프로토콜 기반 서버 또는 클라이언트 구현을 위한 다양한 API를 제공한다.
TCP/UDP 프로토콜 기반 주요 클래스로 QTcpSocket, QTcpServer 그리고 QUdpSocket 클래스를 제공한다.
- LOW 레벨 네트워크 클래스들
QTcpSocket – TCP 프로토콜 기반의 네트워크 클래스
QTcpServer – TCP 프로토콜 기반의 서버 구현에 적합한 클래스
QUdpSocket – UDP 프로토콜 기반의 네트워크 클래스
LOW 레벨 클래스들은 개발자가 세부적인 네트워크 기능을 구현에 적합하다.

QTcpSocket 클래스와 QUdpSocket 클래스는 QAbstracktSocket 클래스로부터 상속받아 구현되었다.
TCP/UDP 프로토콜의 기반이 아닌 새로운 네트워크 프로토콜을 구현하기 위해서
QAbstracktSocket 클래스를 상속 받아 구현한다면 많은 시간을 절약할 수 있다.
QTcpServer는 QTcpSocket 과 동일한 TCP 프로토콜 기반의 클래스이다.
차이점은 QTcpServer 클래스는 서버 기반의 응용 어플리케이션 구현에 필요한 기능을 제공한다.
Qt에서 제공하는 네트워크 모듈을 사용하기 위해서는 프로젝트파일 상에 다음과 같이 네트워크 모듈을 사용하겠다고 명시해야 한다.
CMake를 사용하는 경우 다음과 같은 항목을 추가해야 한다
find_package(Qt6 REQUIRED COMPONENTS Network)
target_link_libraries(mytarget PRIVATE Qt6::Network)
qmake 를 사용한다면 다음과 같은 항목을 추가해야 한다
QT += network
지금까지 Qt에서 제공하는 Network 에 대한 전반적이 내용을 알아보았다.
다음 소단원에서부터는 구현에 필요한 내용을 자세히 알아보도록 하자.
28.1. TCP 프로토콜 기반 서버/클라이언트 구현
이번 장에서 QTcpServer 클래스와 QTcpSocket 클래스를 이용해 서버/클라이언트 예제를 구현해 보도록 하자
- QTcpServer 클래스를 이용한 TCP서버 예제 이번 예제는 QTcpServer 클래스를 이용해 서버와 클라이언트 어플리케이션을 구현해 보도록 하자.

위의 그림에서 보는 것과 같이 GUI상에 위젯을 배치해 보도록 하자.
CMake 기반 프로젝트 생성 후, CMakeLists.txt 파일에 아래와 같이 항목을 수정 및 추 가한다.
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network)
...
target_link_libraries(00_TcpServer PRIVATE Qt${QT_VERSION_MAJOR}::Network)

가장 상단에 QLabel 위젯을 배치하고 하단의 그룹 박스에는 클라이언트가 접속한 시간 을 출력하는 텍스트로 출력할 수 있는 QTextEdit를 배치한다.
다음은 Widget 클래스의 헤더 파일에 다음과 같이 소스코드를 작성한다.
#ifndef WIDGET_H
#define WIDGET_H
#include <QObject> // Qt의 신호와 슬롯 기능을 사용하는 기본 클래스 포함
#include <QWidget> // QWidget 클래스 포함, GUI 애플리케이션의 기본 클래스
#include <QtNetwork/QTcpServer> // QTcpServer 포함, TCP 서버를 구현하기 위한 클래스
// Qt에서 생성된 UI 파일을 네임스페이스로 구분하여 사용
QT_BEGIN_NAMESPACE
namespace Ui
{
class Widget; // Widget 클래스의 UI 선언
}
QT_END_NAMESPACE
// Widget 클래스 정의, QWidget을 상속받아 GUI 기능을 추가
class Widget : public QWidget
{
Q_OBJECT // Qt의 신호와 슬롯 메커니즘을 사용하기 위해 필요한 매크로
public:
// 생성자: 부모 위젯을 받아 초기화하며 nullptr일 경우 기본적으로 부모가 없는 상태로 생성
Widget(QWidget *parent = nullptr);
// 소멸자: 객체가 소멸될 때 호출되며, 메모리 정리 역할
~Widget();
private:
Ui::Widget *ui; // UI를 관리하는 포인터 (Qt Designer에서 생성된 UI 클래스)
QTcpServer *tcpServer; // QTcpServer 객체, TCP 서버 역할을 수행하는 클래스
void initialize(); // TCP 서버를 초기화하는 함수 선언
private slots:
// 새로운 연결이 들어왔을 때 처리하는 슬롯 함수
void newConnection();
};
#endif // WIDGET_H
각 부분 설명:
#ifndef WIDGET_H, #define WIDGET_H, #endif:
이 코드는 헤더 가드로, 파일이 여러 번 포함되는 것을 방지한다. #ifndef WIDGET_H는 WIDGET_H가 정의되지 않았을 때만 이 파일의 내용을 포함시키도록 합니다. 이미 정의되었으면 포함을 방지하여 중복 포함 오류를 피할 수 있다.
#include 문들:
#include <QObject>: QObject은 Qt의 신호와 슬롯 메커니즘을 지원하는 기본 클래스이다.
#include <QWidget>: QWidget은 기본 GUI 위젯 클래스로, GUI 애플리케이션에서 사용된다.
#include <QtNetwork/QTcpServer>: QTcpServer는 TCP 서버를 구현하는 클래스이다. TCP 연결 요청을 수신하고 처리할 수 있습니다.
QT_BEGIN_NAMESPACE, QT_END_NAMESPACE:
이 매크로들은 Qt의 네임스페이스와 관련된 코드를 관리하기 위해 사용된다. Ui::Widget은 UI 파일에서 생성된 클래스로, Qt Designer에서 생성된 위젯을 관리하는 역할을 한다.
Widget 클래스:
Widget 클래스는 QWidget을 상속받아 GUI 애플리케이션의 윈도우나 위젯을 나니다.
Q_OBJECT: 이 매크로는 Qt의 신호와 슬롯 메커니즘을 사용하기 위해 필요한 선언입니다. Qt의 메타 객체 시스템에서 사용됩니다.
생성자 (Widget(QWidget *parent = nullptr)):
생성자는 부모 위젯을 설정하며, 부모가 nullptr인 경우 독립적인 윈도우로 생성됩니다. parent 인자를 받아 부모 위젯을 지정할 수 있습니다.
소멸자 (~Widget()):
소멸자는 위젯이 파괴될 때 호출되며, 메모리 해제 등 정리 작업을 수행합니다.
멤버 변수들:
Ui::Widget *ui: UI를 관리하는 포인터로, Qt Designer에서 생성된 UI를 관리합니다.
QTcpServer *tcpServer: QTcpServer 객체로, TCP 서버 역할을 합니다. 새로운 연결을 수신하고 관리합니다.
initialize() 함수:
이 함수는 TCP 서버를 설정하고 초기화하는 함수입니다. 서버 시작, 포트 설정 등의 초기 작업을 수행할 수 있습니다.
슬롯 함수 (newConnection()):
이 함수는 새로운 클라이언트 연결이 수신되었을 때 호출됩니다. TCP 서버는 클라이언트가 연결을 요청할 때마다 이 슬롯을 호출하여 연결을 처리할 수 있습니다.
위의 헤더 소스코드에서 initialize( ) 함수는 QTcpServer 클래스의 오브젝트를 초기화를 위한 함수이다. newConnection( ) Slot 함수는 클라이언트가 접속하면 Signal 이 발생하 면 호출되는 함수 이다.
다음은 widget.cpp 소스코드이다.
#include "widget.h" // 위젯 헤더 파일 포함
#include "./ui_widget.h" // UI 위젯 헤더 파일 포함
#include <QtNetwork/QNetworkInterface> // 네트워크 인터페이스 관련 클래스 포함
#include <QtNetwork/QTcpSocket> // TCP 소켓 관련 클래스 포함
#include <QMessageBox> // 메시지 박스 관련 클래스 포함
#include <QTime> // 시간 관련 클래스 포함
// 위젯 생성자 정의
Widget::Widget(QWidget *parent)
: QWidget(parent), ui(new Ui::Widget) // 부모 위젯을 인자로 받아 UI 초기화
{
ui->setupUi(this); // UI 설정
initialize(); // 서버 초기화 함수 호출
}
// 서버를 초기화하는 함수
void Widget::initialize()
{
QHostAddress hostAddr; // 서버의 IP 주소를 저장할 변수
// 시스템의 모든 네트워크 인터페이스에서 IP 주소 목록을 가져옴
QList<QHostAddress> ipList = QNetworkInterface::allAddresses();
// 로컬호스트(127.0.0.1)가 아닌 IP 주소를 찾음
for (int i = 0; i < ipList.size(); ++i)
{
// IP 주소가 로컬호스트가 아니고 IPv4 주소일 경우 해당 주소를 사용
if (ipList.at(i) != QHostAddress::LocalHost &&
ipList.at(i).toIPv4Address())
{
hostAddr = ipList.at(i); // IP 주소 설정
break;
}
}
// 만약 유효한 IP 주소를 찾지 못했다면 로컬호스트를 기본으로 사용
if (hostAddr.toString().isEmpty())
hostAddr = QHostAddress(QHostAddress::LocalHost);
// QTcpServer 객체를 생성하여 서버 설정
tcpServer = new QTcpServer(this);
// 서버 시작, IP 주소와 포트 번호 25000 사용
if (!tcpServer->listen(hostAddr, 25000))
{
// 서버 시작 실패 시 에러 메시지를 출력하고 프로그램 종료
QMessageBox::critical(this, tr("TCP Server"),
tr("Cannot start the server, Error: %1.")
.arg(tcpServer->errorString()));
close();
return;
}
// 서버가 정상적으로 시작되면 상태 라벨에 IP 주소와 포트 번호 표시
ui->labelStatus->setText(tr("Server running \n\n"
"IP : %1\n"
"PORT : %2\n")
.arg(hostAddr.toString())
.arg(tcpServer->serverPort()));
// 새로운 연결이 발생했을 때 newConnection 슬롯 함수와 연결
connect(tcpServer, SIGNAL(newConnection()), this, SLOT(newConnection()));
// 연결 메시지 편집창을 초기화
ui->connMsgEdit->clear();
}
// 클라이언트의 새로운 연결이 발생했을 때 호출되는 함수
void Widget::newConnection()
{
// 대기 중인 연결을 처리하는 소켓 객체 생성
QTcpSocket *clientConnection = tcpServer->nextPendingConnection();
// 클라이언트가 연결을 끊으면 소켓 객체를 삭제하도록 설정
connect(clientConnection, SIGNAL(disconnected()),
clientConnection, SLOT(deleteLater()));
// 현재 시간을 hh:mm:ss 형식으로 가져옴
QString currTime = QTime::currentTime().toString("hh:mm:ss");
// 클라이언트 연결 메시지를 구성하여 화면에 출력
QString text = QString("Client Connection (%1)").arg(currTime);
ui->connMsgEdit->append(text);
// 클라이언트에게 보낼 메시지를 설정
QByteArray message = QByteArray("Hello Client ~ ");
// 클라이언트에게 메시지를 전송
clientConnection->write(message);
// 메시지 전송 후 클라이언트와의 연결을 끊음
clientConnection->disconnectFromHost();
}
// 위젯 소멸자 정의
Widget::~Widget()
{
delete ui; // UI 객체 삭제
}
생성자 함수에서 호출되는 initialize( ) 함수는 클라이언트가 접속할 서버의 IP를 시스템으로부터 얻어온다.
그리고 QTcpServer 클래스의 tcpServer 오브젝트를 초기화 하고 listen( ) 멤버 함수를 이용해 클라이언트 접속 요청 대기 상태가 될 수 있도록 한다.
listen( ) 함수의 첫 번째 인자는 IP주소이며 두 번째 인자는 서버 Port 번호 이다.
그리고 initialize( ) 함수 하단의 connect( ) 함수는 클라이언트가 접속하게 되면 호출되는 Signal 과 Slot 함수를 연결하였다.
따라서 새로운 클라이언트가 접속하게 되면 newConnection( ) 함수가 호출된다.
newConnection( ) 함수는 GUI 상에 QTextEdit 위젯에 클라이언트가 접속한 시간을 출력하고 접속한 클라이언트에게 “Hello Client ~ “ 메시지를 전송한다.
- QTcpSocket 클래스를 이용한 TCP 클라이언트 예제
CMake 기반 프로젝트 생성 후, CMakeLists.txt 파일에 아래와 같이 항목을 수정 및 추가한다.
// Qt 패키지를 찾는 명령어
// Qt6 또는 Qt5 중에서 사용 가능한 버전을 탐색하고
// Widgets와 Network 모듈을 필수적으로 포함시킴
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network)
// 선택된 Qt의 메이저 버전(예: Qt5 또는 Qt6)에 맞춰서
// Widgets와 Network 컴포넌트를 필수로 포함시킴
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network)
// 00_TcpServer 타겟에 선택된 Qt 버전의 Network 모듈을 링크시킴
// PRIVATE는 해당 라이브러리가 이 타겟 내에서만 사용됨을 의미
target_link_libraries(00_TcpServer PRIVATE Qt${QT_VERSION_MAJOR}::Network)
다음은 클라이언트 예제이다.
QWidget 을 상속받는 프로젝트를 생성하고 다음 그림에 서 보는 것과 같이 GUI상에 위젯을 배치한다.

위의 그림에서 보는 것과 같이 접속할 서버의 IP와 PORT를 입력할 수 있도록 QLineEdit 위젯을 배치한다.
그리고 [접속] 버튼과 하단의 QTextEdit를 배치한다.
[Connecting Server] 버튼을 클릭하면 서버와 연결을 시도한다.
서버와 연결이 완료되면 서버로부터 받은 메시지를 QTextEdit 위젯에 출력한다.
아래 예제 소스코드와 같이 widget.h 헤더 소스코드를 작성한다.
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget> // QWidget 클래스 포함
#include <QtNetwork/QTcpSocket> // TCP 소켓 통신을 위한 클래스 포함
QT_BEGIN_NAMESPACE
namespace Ui
{
class Widget; // Ui::Widget 클래스 선언 (UI 요소를 관리)
}
QT_END_NAMESPACE
// Widget 클래스 정의, QWidget을 상속받음
class Widget : public QWidget
{
Q_OBJECT // Qt의 시그널 및 슬롯 메커니즘을 사용하기 위한 매크로
public:
// 생성자, 부모 위젯을 인자로 받음 (기본값은 nullptr)
Widget(QWidget *parent = nullptr);
// 소멸자, 객체가 소멸될 때 호출됨
~Widget();
private:
Ui::Widget *ui; // UI 요소를 관리하는 포인터
QTcpSocket *tcpSocket; // TCP 소켓을 위한 포인터
// 초기화 함수, 필요한 설정을 수행
void initialize();
private slots:
// 버튼 클릭 시 서버에 연결을 시도하는 슬롯 함수
void connectButton();
// 서버로부터 메시지를 수신할 때 호출되는 슬롯 함수
void readMessage();
// 서버와의 연결이 끊어졌을 때 호출되는 슬롯 함수
void disconnected();
};
#endif // WIDGET_H
connectButton( ) Slot 함수는 [접속] 버튼 클릭 시 호출된다.
readMessage( ) Slot 함수는 서버로부터 메시지를 받는 Signal 이 발생하면 호출되는 함수이다.
그리고 disconnected( ) Slot 함수는 서버로부터 접속이 종료된 Signal 이 발생하면 호출 되는 함수이다.
다음은 widget.cpp 예제소스코드이다.
#include "widget.h" // 위젯 헤더 파일 포함
#include "./ui_widget.h" // UI 위젯 헤더 파일 포함
// 위젯 생성자 정의
Widget::Widget(QWidget *parent)
: QWidget(parent), ui(new Ui::Widget) // 부모 위젯을 받아 UI 초기화
{
ui->setupUi(this); // UI 설정
// connectButton 버튼이 클릭되었을 때 connectButton 슬롯 함수가 호출되도록 연결
connect(ui->connectButton, SIGNAL(clicked()),
this, SLOT(connectButton()));
// 초기화 함수 호출
initialize();
}
// TCP 소켓과 관련된 초기화 작업을 수행하는 함수
void Widget::initialize()
{
// QTcpSocket 객체 생성, this를 부모로 설정
tcpSocket = new QTcpSocket(this);
// 서버로부터 데이터를 받을 준비가 되었을 때 readMessage 슬롯 함수 호출
connect(tcpSocket, SIGNAL(readyRead()),
this, SLOT(readMessage()));
// 서버와의 연결이 끊어졌을 때 disconnected 슬롯 함수 호출
connect(tcpSocket, SIGNAL(disconnected()),
this, SLOT(disconnected()));
}
// 연결 버튼이 클릭되었을 때 서버에 연결하는 함수
void Widget::connectButton()
{
// 서버 IP 주소를 입력 필드에서 가져오고 공백 제거
QString serverip = ui->serverIP->text().trimmed();
// 입력된 IP 주소로 QHostAddress 객체 생성
QHostAddress serverAddress(serverip);
// 지정된 IP 주소와 포트 번호(25000)를 사용하여 서버에 연결
tcpSocket->connectToHost(serverAddress, 25000);
}
// 서버로부터 메시지를 수신할 때 호출되는 함수
void Widget::readMessage()
{
// 소켓에 읽을 수 있는 데이터가 있는지 확인
if (tcpSocket->bytesAvailable() >= 0)
{
// 모든 수신된 데이터를 읽어옴
QByteArray readData = tcpSocket->readAll();
// 읽어온 데이터를 텍스트 편집기에 출력
ui->textEdit->append(readData);
}
}
// 서버와의 연결이 끊어졌을 때 호출되는 함수
void Widget::disconnected()
{
// 서버 접속이 종료되었음을 디버그 콘솔에 출력
qDebug() << Q_FUNC_INFO << "서버 접속 종료.";
}
// 위젯 소멸자 정의
Widget::~Widget()
{
delete ui; // UI 객체 삭제
}
initialize( ) 함수는 QTcpSocket 클래스의 tcpSocket 오브젝트를 선언하고
서버로부터 메시지를 받을 발생한 Signal 을 readMessage( ) Slot 함수와 연결한다.
그리고 서버와 연 결 종료 Signal을 받으면 disconnected( ) Slot 함수와 연결한다.
따라서 서버로부터 메시지를 받으면 readMessage( ) 함수를 호출하고 서버와 연결이 종료되면 disconnect( ) Slot 함수가 호출된다. readMessage( ) Slot 함수에서 tcpSocket->bytesAvailable() 함수는 서버가 보내온 메시지의 Bytes 수를 구할 수 있다.
그리고 readAll( ) 멤버 함수는 서버가 보내온 메시지를 읽어올 수 있는 기능을 제공한다.
28.2. 동기 방식 비 동기 방식 구현
네트워크에서 데이터 송/수신 처리 방식을 크게 두가지로 나누어 볼 수 있다.
첫 번째 는 동기 방식이고 두 번째 방식은 비 동기 방식이다.
동기 방식은 서버가 클라이언트에게 Request 했을 때, 다른 처리는 하지 않고 응답을 기다리는 경우이다.
이러한 경우를 동기 방식이라 한다
...
int writeBytes = socket->write("Hello server\r\n\r\n");
socket->waitForReadyRead(3000);
...
위의 소스코드에서 보는 것과 같이 write( ) 함수를 이용해 메시지를 전송하고 상대방으로부터 메시지를 받을 때까지 기다리기 위해서 waitForReadyRead( ) 함수를 사용할 수 있다.
비 동기 방식이란 서버가 클라이언트에게 Request을 하였지만 언제 응답을 받을지 모르는 상황을 비 동기 방식으로 처리할 수 있다.
// QHttpSocket 클래스 생성자 정의
// QObject을 부모로 받아 초기화
QHttpSocket::QHttpSocket(QObject *parent) : QObject(parent)
{
// QTcpSocket 객체 생성, this를 부모로 설정
socket = new QTcpSocket(this);
// 소켓에서 데이터를 읽을 준비가 되었을 때 slotRead 슬롯 함수를 호출하도록 연결
connect(socket, SIGNAL(readyRead()), this, SLOT(slotRead()));
}
// 슬롯 함수 정의, 소켓에서 데이터를 읽을 때 호출됨
void QHttpSocket::slotRead()
{
// 소켓으로부터 모든 데이터를 읽어와서 디버그 콘솔에 출력
qDebug() << socket->readAll();
}
위의 예에서 보는 것과 같이 상대방으로부터 메시지를 수신 받을 때 slotRead( ) Slot 함 수가 호출 된다.
이런 방식을 비 동기 방식이라고 한다.
우리는 이번 장에서 우리는 qt-dev.com 웹 서버를 이용해 동기 방식과 비 동기 방식을 이용해 데이터를 송/수신 하는 방법에 대해서 알아보도록 하자.
- 동기 방식 예제
이번 예제는 GUI를 사용하지 않고 콘솔 기반의 프로젝트를 생성한다.

생성이 완료되면 CMakeList.txt 파일에 아래와 같이 Network 모듈을 추가한다.
cmake_minimum_required(VERSION 3.14)
project(00_Sync LANGUAGES CXX)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Network)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Network)
add_executable(00_Sync
main.cpp
qhttpsocket.h qhttpsocket.cpp
)
target_link_libraries(00_Sync Qt${QT_VERSION_MAJOR}::Core)
target_link_libraries(00_Sync Qt${QT_VERSION_MAJOR}::Network)
include(GNUInstallDirs)
install(TARGETS 00_Sync
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
다음으로 QObject 클래스를 상속받는 QHttpSocket 라는 이름의 클래스를 생성하자.


클래스의 헤더에는 아래와 같이 작성한다
#ifndef QHTTPSOCKET_H
#define QHTTPSOCKET_H
#include <QObject> // QObject 클래스 포함
#include <QTcpSocket> // QTcpSocket 클래스 포함
// QHttpSocket 클래스 정의, QObject를 상속받음
class QHttpSocket : public QObject
{
Q_OBJECT // Qt의 시그널 및 슬롯 메커니즘을 사용하기 위한 매크로
public:
// 생성자, 부모 객체를 인자로 받음 (기본값은 nullptr)
explicit QHttpSocket(QObject *parent = nullptr);
// 소멸자, 객체가 소멸될 때 호출됨
~QHttpSocket();
// HTTP 서버에 연결을 시도하는 함수
void httpConnect();
signals:
// (비어 있음, 시그널을 추가할 때 사용)
public slots:
// 서버와의 연결이 끊어졌을 때 호출되는 슬롯 함수
void httpDisconnected();
private:
// TCP 소켓 객체, 서버와의 통신을 처리
QTcpSocket *socket;
};
#endif // QHTTPSOCKET_H
다음은 qhttpsocket.cpp 소스코드를 아래와 같이 작성한다.
#include "qhttpsocket.h"
// QHttpSocket 클래스의 생성자 정의
QHttpSocket::QHttpSocket(QObject *parent)
: QObject{parent} // 부모 객체를 초기화
{
// QTcpSocket 객체를 생성하고, this를 부모로 설정
socket = new QTcpSocket(this);
// 서버와의 연결이 끊어졌을 때 httpDisconnected 슬롯 함수가 호출되도록 연결
connect(socket, SIGNAL(disconnected()),
this, SLOT(httpDisconnected()));
}
// QHttpSocket 클래스의 소멸자 정의 (소멸자에서는 별다른 작업 없음)
QHttpSocket::~QHttpSocket()
{
}
// 서버에 연결을 시도하고 데이터를 전송하는 함수
void QHttpSocket::httpConnect()
{
// 서버 qt-dev.com의 80번 포트에 연결 시도
socket->connectToHost("qt-dev.com", 80);
// 5초 동안 연결 대기
if (socket->waitForConnected(5000))
{
// 연결 성공 시 디버그 메시지 출력
qDebug() << "TCP Connected.";
// 서버에 데이터를 전송 (HTTP 프로토콜을 이용해 "Hello server"를 전송)
int writeBytes = socket->write("Hello server\r\n\r\n");
// 데이터 전송을 완료할 때까지 대기
socket->waitForBytesWritten(1000);
// 전송된 바이트 수를 출력
qDebug() << "write bytes : " << writeBytes;
// 서버로부터 데이터를 받을 때까지 대기
socket->waitForReadyRead(3000);
// 수신된 데이터의 크기를 출력
qDebug() << "Reading: " << socket->bytesAvailable();
// 수신된 데이터를 모두 읽어와 출력
qDebug() << socket->readAll();
}
else
{
// 연결 실패 시 디버그 메시지 출력
qDebug() << "Not connected!";
}
}
// 서버와의 연결이 끊어졌을 때 호출되는 슬롯 함수
void QHttpSocket::httpDisconnected()
{
// 연결이 끊어졌음을 디버그 메시지로 출력
qDebug() << Q_FUNC_INFO;
}
httpConnect( ) 함수에서 QTcpSocket 클래스에서 제공하는 connectToHost( ) 함수는 첫 번째 인자에 명시한 URL에 접속한다.
그리고 두 번째 인자는 접속할 Port 번호를 명시 한다. connectToHost( ) 함수 다음 라인에서 사용한 waitForConnected( ) 함수는 웹 서버와 연 결 될 때 까지 기다린다.
첫 번째 인자는 서버가 응답할 때까지 기다리는 시간이다.
다음으로 상대방으로부터 데이터 수신할 때 까지 기다리기 위해서 waitForReadyRead( ) 함수를 사용할 수 있다.
이러한 방식으로 동기 방식으로 소스코드를 구현할 수 있다.
다음은 main.cpp 에서 QHttpSocket 클래스의 오브젝트를 생성하고 httpConnect( ) 멤 버 함수를 하도록 아래와 같이 작성해 보도록 하자.
#include <QCoreApplication> // Qt의 콘솔 애플리케이션을 위한 헤더 파일 포함
#include "qhttpsocket.h" // QHttpSocket 클래스의 헤더 파일 포함
int main(int argc, char *argv[])
{
// 콘솔 애플리케이션 객체 생성 (Qt 이벤트 루프 초기화)
QCoreApplication a(argc, argv);
// QHttpSocket 객체 동적 할당
QHttpSocket *httpSocket = new QHttpSocket();
// 서버에 연결하고 데이터를 전송하는 함수 호출
httpSocket->httpConnect();
// Qt 이벤트 루프 실행 (프로그램이 종료될 때까지 이벤트 대기)
return a.exec();
}
- 비 동기 방식 예제
이번 예제는 Signal/Slot을 이용한 비 동기 방식 예제 소스코드 이다. 이전 예제와 같이 Console 기반의 프로젝트를 생성하고 QHttpSocket 클래스를 추가한다.
그런 다음에 qhttpsocket.h 헤더 파일을 아래와 같이 작성한다.
#ifndef QHTTPSOCKET_H
#define QHTTPSOCKET_H
#include <QObject> // QObject 클래스 포함 (Qt의 기본 객체 클래스)
#include <QTcpSocket> // TCP 소켓을 사용하기 위한 헤더 포함
// QHttpSocket 클래스 정의, QObject를 상속받음
class QHttpSocket : public QObject
{
Q_OBJECT // Qt의 시그널 및 슬롯 메커니즘을 사용하기 위한 매크로
public:
// 생성자, 부모 객체를 인자로 받음 (기본값은 nullptr)
explicit QHttpSocket(QObject *parent = nullptr);
// 소멸자, 객체가 소멸될 때 호출됨
~QHttpSocket();
// HTTP 서버에 연결을 시도하는 함수
void httpConnect();
signals:
// 시그널: 필요시 여기서 시그널 정의 가능 (현재는 빈 상태)
public slots:
// 서버에 연결되었을 때 호출되는 슬롯 함수
void slotConnected();
// 서버와의 연결이 끊어졌을 때 호출되는 슬롯 함수
void slotDisconnected();
// 데이터를 서버로 전송한 후 호출되는 슬롯 함수 (전송된 바이트 수 인자로 받음)
void slotBytesWritten(qint64 bytes);
// 서버로부터 데이터를 읽을 때 호출되는 슬롯 함수
void slotReadPandingDatagram();
private:
// TCP 소켓 객체, 서버와의 통신을 처리
QTcpSocket *socket;
};
#endif // QHTTPSOCKET_H
다음 예제 소스코드는 QHttpSocket 클래스의 구현 소스코드이다.
#include "qhttpsocket.h"
// QHttpSocket 클래스 생성자 정의
QHttpSocket::QHttpSocket(QObject *parent)
: QObject{parent} // 부모 객체 초기화
{
// QTcpSocket 객체를 생성하고 this를 부모로 설정
socket = new QTcpSocket(this);
// 소켓이 서버와 연결되었을 때 slotConnected 슬롯 함수를 호출
connect(socket, SIGNAL(connected()),
this, SLOT(slotConnected()));
// 소켓이 서버와의 연결이 끊어졌을 때 slotDisconnected 슬롯 함수를 호출
connect(socket, SIGNAL(disconnected()),
this, SLOT(slotDisconnected()));
// 서버에 데이터를 보낸 후 slotBytesWritten 슬롯 함수를 호출
connect(socket, SIGNAL(bytesWritten(qint64)),
this, SLOT(slotBytesWritten(qint64)));
// 서버로부터 데이터를 받을 준비가 되었을 때 slotReadPandingDatagram 슬롯 함수를 호출
connect(socket, SIGNAL(readyRead()),
this, SLOT(slotReadPandingDatagram()));
}
// QHttpSocket 클래스 소멸자 정의 (소멸 시 특별한 작업은 없음)
QHttpSocket::~QHttpSocket()
{
}
// 서버에 연결을 시도하는 함수
void QHttpSocket::httpConnect()
{
// 지정된 서버 주소("qt-dev.com")와 포트(80번)로 연결을 시도
socket->connectToHost("qt-dev.com", 80);
// 3초 동안 연결 대기, 연결되지 않으면 에러 메시지 출력
if (!socket->waitForConnected(3000))
{
// 연결 실패 시 에러 메시지 출력
qDebug() << "Socket Error : "
<< socket->errorString();
}
}
// 서버와의 연결이 성공했을 때 호출되는 슬롯 함수
void QHttpSocket::slotConnected()
{
// 서버 연결 성공 시 디버그 메시지 출력
qDebug("\n [%s] CONNECTED", Q_FUNC_INFO);
// 서버에 HTTP 요청 메시지를 전송 (HEAD 요청)
socket->write("HEAD / HTTP/1.0\r\n\r\n\r\n\r\n");
}
// 서버와의 연결이 끊어졌을 때 호출되는 슬롯 함수
void QHttpSocket::slotDisconnected()
{
// 연결이 끊어졌음을 디버그 메시지로 출력
qDebug("\n [%s] DISCONNECTED", Q_FUNC_INFO);
}
// 데이터를 서버로 전송한 후 호출되는 슬롯 함수
void QHttpSocket::slotBytesWritten(qint64 bytes)
{
// 전송된 바이트 수를 디버그 메시지로 출력
qDebug("\n [%s] Bytes Written [size :%d]",
Q_FUNC_INFO, bytes);
}
// 서버로부터 데이터를 읽을 때 호출되는 슬롯 함수
void QHttpSocket::slotReadPandingDatagram()
{
// 서버에서 수신한 모든 데이터를 읽어와서 디버그 메시지로 출력
qDebug() << socket->readAll();
}
생성자에서는 QTcpSocket 클래스 오브젝트를 선언하고 서버와 연결, 종료, 메시지 전송 완료 시 그리고 웹 서버로부터 메시지 수신 시 Signal 을 Slot 함수와 연결한다. httpConnect( ) 함수를 실행하면 웹 서버와 연결을 시도한다. 이전 동기 방식과 다르게 이번 예제에서는 Signal 과 Slot을 이용해 비 동기 방식을 사용할 수 있다.
28.6. 채팅 서버/클라이언트 구현
이번 장에서는 QTcpServer 와 QTcpSocket 클래스를 이용해 간단한 채팅 서버 예제와 클라이언트 예제를 구현해 보도록 하자.
- 채팅 서버 예제 구현
아래의 그림에서 보는 것과 같이 GUI 상에 위젯을 배치한다. GUI 상에서 QTextEdit 위젯 은 새로운 사용자가 접속, 사용자들이 보내는 메시지 그리고 접속이 끊긴 사용자 정보 를 표시한다.

위와 같이 배치했으면, 다음으로 ChatServer 클래스를 프로젝트에 추가한다.
그리고 chatserver.h 헤더 파일에 아래와 같이 작성한다.
#ifndef CHATSERVER_H
#define CHATSERVER_H
#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
class ChatServer : public QTcpServer
{
Q_OBJECT
public:
ChatServer(QObject *parent = nullptr);
~ChatServer();
private slots:
void readyRead();
void disconnected();
void sendUserList();
signals:
void clients_signal(int users);
void message_signal(QString msg);
protected:
void incomingConnection(qintptr socketfd);
private:
QSet<QTcpSocket *> clients;
QMap<QTcpSocket *, QString> users;
};
#endif // CHATSERVER_H
위의 헤더에서 protected 접근 제한자에서 선언한 incomingConnection( ) 함수는 새로 운 클라이언트가 접속하게 되면 호출되는 함수이다. clients_signal( ) 시그널은 이 함수에서 발생한다.
이 시그널의 인자로 현재 서버에 접속한 사용자 수를 인자로 넘겨준다.
그리고 클라이언트에 대한 메시지를 수신하면 readyRead( ) Slot 함수가 호출될 수 있도록 이 함수를 시그널과 연결한다.
따라서 클라이언트가 메시지를 보내면 readyRead( ) Slot 함수가 호출된다.
그리고 disconnected( ) 시그널은 클라이언트 접속을 종료하면 이 시그널이 발생하고 이 시그널과 연결된 disconnected( ) Slot 함수가 호출된다.
다음 예제 소스코드는 ChatServer.cpp 의 소스코드 이다.
#include "chatserver.h"
#include <QRegularExpression>
#include <QRegularExpressionMatch>
// ChatServer 클래스 생성자 정의
ChatServer::ChatServer(QObject *parent)
: QTcpServer(parent) // QTcpServer를 부모로 초기화
{
}
// 새로운 클라이언트가 연결되었을 때 호출되는 함수
void ChatServer::incomingConnection(qintptr socketfd)
{
// 새로운 클라이언트 소켓 생성 및 소켓 파일 디스크립터 설정
QTcpSocket *client = new QTcpSocket(this);
client->setSocketDescriptor(socketfd);
// 클라이언트 소켓을 관리하는 set에 추가
clients.insert(client);
// 클라이언트 수 변경 시그널 전송
emit clients_signal(clients.count());
// 새로운 클라이언트가 접속했다는 메시지 생성 및 전송
QString str;
str = QString("New Member: %1").arg(client->peerAddress().toString());
emit message_signal(str);
// 클라이언트의 데이터 읽기 및 연결 종료 시그널과 슬롯을 연결
connect(client, SIGNAL(readyRead()), this, SLOT(readyRead()));
connect(client, SIGNAL(disconnected()), this, SLOT(disconnected()));
}
// 클라이언트로부터 데이터를 읽을 때 호출되는 슬롯 함수
void ChatServer::readyRead()
{
// 데이터를 보낸 클라이언트 소켓 가져오기
QTcpSocket *client = (QTcpSocket *)sender();
// 소켓에서 읽을 수 있는 데이터가 있을 때까지 반복
while (client->canReadLine())
{
// 클라이언트로부터 받은 한 줄을 UTF-8 형식으로 읽어옴
QString line = QString::fromUtf8(client->readLine()).trimmed();
// 수신한 메시지를 디버그용으로 시그널 전송
QString str;
str = QString("Read line: %1").arg(line);
emit message_signal(str);
// '/me:'로 시작하는 메시지를 처리 (사용자 이름 설정)
QRegularExpression meRegex("^/me:(.*)$");
QRegularExpressionMatch match = meRegex.match(line);
if (match.hasMatch())
{
// 사용자 이름을 설정하고 모든 클라이언트에 알림
QString user = match.captured(1);
users[client] = user;
foreach (QTcpSocket *client, clients)
{
client->write(QString("Server: %1 connected\n")
.arg(user)
.toUtf8());
}
// 사용자 목록을 전송
// sendUserList();
}
// 메시지를 보낸 사용자가 이미 등록된 경우
else if (users.contains(client))
{
// 메시지를 읽고 모든 클라이언트에 전송
QString message = line;
QString user = users[client];
QString str;
str = QString("User name: %1, Message: %2")
.arg(user, message);
emit message_signal(str);
foreach (QTcpSocket *otherClient, clients)
otherClient->write(QString(user + ":" + message + "\n")
.toUtf8());
}
}
}
// 클라이언트가 연결을 끊었을 때 호출되는 슬롯 함수
void ChatServer::disconnected()
{
// 연결을 끊은 클라이언트 소켓 가져오기
QTcpSocket *client = (QTcpSocket *)sender();
// 클라이언트 연결 종료 메시지 생성 및 전송
QString str;
str = QString("Disconnect: %1")
.arg(client->peerAddress().toString());
emit message_signal(str);
// 클라이언트 목록에서 제거
clients.remove(client);
// 클라이언트 수 변경 시그널 전송
emit clients_signal(clients.count());
// 연결을 끊은 사용자의 이름을 가져와서 사용자 목록에서 제거
QString user = users[client];
users.remove(client);
// 사용자 목록을 다시 전송
sendUserList();
// 다른 모든 클라이언트에게 사용자 연결 종료 메시지 전송
foreach (QTcpSocket *client, clients)
client->write(QString("Server: %1 Disconnect").arg(user).toUtf8());
}
// 사용자 목록을 모든 클라이언트에게 전송하는 함수
void ChatServer::sendUserList()
{
QStringList userList;
// 현재 접속한 모든 사용자 이름을 목록에 추가
foreach (QString user, users.values())
userList << user;
// 각 클라이언트에게 사용자 목록을 전송
foreach (QTcpSocket *client, clients)
client->write(QString("User:" + userList.join(",") + "\n").toUtf8());
}
// ChatServer 소멸자 정의
ChatServer::~ChatServer()
{
deleteLater(); // 서버 소멸 시 메모리 해제
}
incomingConnection( ) 함수에서 QTcpSocket 을 새로 만드는 것은 새로운 클라이언트의 소켓 오브젝트를 생성하기 위함이다.
따라서 이 함수에서 생성된 QTcpSocket 클래 스의 오브젝트는 users 라는 클라이언트 QMap 컨테이너에 저장된다.
readyRead( ) 함수는 서버에 접속한 클라이언트가 메시지를 보내면 호출되는 Slot 함수이다. 이 함수에서는 클라이언트가 보낸 메시지를 서버에 접속한 모든 클라이언트에게 전송한다.
disconnected( ) 함수는 클라이언트가 접속을 종료하면 호출되는 Slot 함수이다.
이 함수에서는 users 라는 QMap 컨테이너에서 접속을 종료한 클라이언트의 QTcpSocket 클래스의 오브젝트를 제거한다.
다음은 widget.h 헤더 파일이다.
아래와 같이 소스코드를 작성한다.
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget> // QWidget 클래스 포함 (Qt 위젯 기반 클래스)
#include "chatserver.h" // ChatServer 클래스 포함
QT_BEGIN_NAMESPACE
namespace Ui
{
// UI 클래스 정의 (UI 파일에서 생성된 클래스)
class Widget;
}
QT_END_NAMESPACE
// Widget 클래스 정의, QWidget을 상속받음
class Widget : public QWidget
{
Q_OBJECT // Qt의 시그널 및 슬롯 메커니즘을 사용하기 위한 매크로
public:
// 생성자, 부모 위젯을 인자로 받음 (기본값은 nullptr)
Widget(QWidget *parent = nullptr);
// 소멸자, 객체가 소멸될 때 호출됨
~Widget();
private:
Ui::Widget *ui; // UI 요소를 관리하는 포인터
ChatServer *server; // ChatServer 객체를 관리하는 포인터
private slots:
// 사용자 수가 변경될 때 호출되는 슬롯 함수
void slot_clients(int users);
// 메시지가 수신될 때 호출되는 슬롯 함수
void slot_message(QString msg);
};
#endif // WIDGET_H
Widget 클래스에서 slot_clients( ) Slot 함수는 새로운 클라이언트 접속하거나 접속을 종 료 했을 때 ChatServer 클래스에서 발생하는 시그널이다. 그리고 slot_message( ) 함수는 ChatServer 클래스에서 클라이언트가 메시지를 보내거 나 접속을 종료하면 호출되는 Slot 함수이다.
다음 예제 소스는 widget.cpp 소스 코드 이다.
#include "widget.h" // 위젯 헤더 파일 포함
#include "./ui_widget.h" // UI 위젯 헤더 파일 포함
// Widget 클래스 생성자 정의
Widget::Widget(QWidget *parent)
: QWidget(parent), ui(new Ui::Widget) // 부모 위젯 초기화 및 UI 설정
{
ui->setupUi(this); // UI 구성 설정
// ChatServer 객체 생성
server = new ChatServer();
// 서버에서 클라이언트 수가 변경되었을 때 slot_clients 슬롯을 호출하도록 연결
connect(server, SIGNAL(clients_signal(int)), this,
SLOT(slot_clients(int)));
// 서버에서 메시지를 받을 때 slot_message 슬롯을 호출하도록 연결
connect(server, SIGNAL(message_signal(QString)), this,
SLOT(slot_message(QString)));
// 서버가 모든 네트워크 인터페이스(QHostAddress::Any)에서 포트 35000으로 리슨 시작
server->listen(QHostAddress::Any, 35000);
}
// 서버에서 클라이언트 수가 변경되었을 때 호출되는 슬롯 함수
void Widget::slot_clients(int users)
{
// 연결된 클라이언트 수를 표시할 문자열 생성
QString str = QString("Connected Members : %1").arg(users);
// label에 클라이언트 수를 표시
ui->label->setText(str);
}
// 서버에서 새로운 메시지를 받을 때 호출되는 슬롯 함수
void Widget::slot_message(QString msg)
{
// 수신된 메시지를 textEdit 위젯에 추가
ui->textEdit->append(msg);
}
// Widget 클래스 소멸자 정의
Widget::~Widget()
{
// UI 메모리 해제
delete ui;
}
slot_clients( ) Slot 함수가 호출되면 GUI 상에서 접속 자의 숫자를 업데이트 한다.
그리고 slot_message( ) Slot 함수는 QTextEdit 창에 메시를 출력한다.
위와 같이 작성이 완 료 되었다면 빌드 후 실행해보자.

위의 그림에서 보는 것과 같이 사용자가 접속한 정보, 보낸 메시지 정보가 GUI상에 표시된다.
- 채팅 클라이언트 예제 구현
이번에는 채팅 서버와 메시지를 송수신을 하는 클라이언트를 구현해 보도록 하자.
채팅 클라이언트는 화면이 2개로 구성되어 있다. 첫 번째는 아래 그림에서 보는 것과 같이 로그인 화면이다.
로그인 위젯에서 보는 것과 같이 [login] 버튼을 클릭하면 서버 IP주 소로 채팅 서버에 접속이 완료 된다.
그리고 로그인 위젯은 Hide 되고 채팅 클라이언 트 위젯이 활성화(Show) 된다.

채팅 클라이언트 위젯에서 중간에 위치한 QTextEdit 위젯은 서버로부터 수신한 메시지 를 출력한다. 하단의 QLineEdit 는 서버로 보낼 메시지를 입력한다. 그리고 [Send] 버튼 을 클릭하면 메시지를 채팅 서버로 전송한다.
Qt Widget 기반 프로젝트를 생성한다.
그리고 아래 그림에서 보는 것과 같이 [Qt Designer Form Class] 를 선택하고 하단의 [Choose] 버튼을 클릭한다

다음 다이얼로그 화면에서는 [Widget] 을 선택하고 [Next] 버튼을 클릭


#ifndef LOGINWIDGET_H
#define LOGINWIDGET_H
#include <QWidget>
namespace Ui
{
class LoginWidget;
}
class LoginWidget : public QWidget
{
Q_OBJECT
public:
explicit LoginWidget(QWidget *parent = nullptr);
~LoginWidget();
private:
Ui::LoginWidget *ui;
private slots:
void loginBtnClicked();
signals:
void sig_loginInfo(QString addr, QString name);
};
#endif // LOGINWIDGET_H
다음은 widget.cpp 소스 파일을 아래와 같이 작성한다.
#include "loginwidget.h"
#include "ui_loginwidget.h"
LoginWidget::LoginWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::LoginWidget)
{
ui->setupUi(this);
connect(ui->loginButton, &QPushButton::pressed,
this, &LoginWidget::loginBtnClicked);
}
void LoginWidget::loginBtnClicked()
{
QString serverIp = ui->ipLineEdit->text().trimmed();
QString name = ui->nameLineEdit->text().trimmed();
emit sig_loginInfo(serverIp, name);
}
LoginWidget::~LoginWidget()
{
delete ui;
}
GUI 상에서 [Login] 버튼을 클릭하면 Server IP 와 User name 을 QString 에 저장한다. 그리고 QString 에 저장한 값을 sig_loginInfo( ) 시그널의 인자로 전달한다. 이 시그널은 Widget 클래스의 Slot 함수와 연결된다.
다음은 widget.h 헤더 파일의 소스코드 이다.
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget> // QWidget 클래스 포함
#include <QTcpSocket> // QTcpSocket 클래스 포함 (TCP 소켓 통신)
#include "loginwidget.h" // LoginWidget 클래스 포함
QT_BEGIN_NAMESPACE
namespace Ui
{
// UI 클래스 정의 (UI 파일에서 생성된 클래스)
class Widget;
}
QT_END_NAMESPACE
// Widget 클래스 정의, QWidget을 상속받음
class Widget : public QWidget
{
Q_OBJECT // Qt의 시그널 및 슬롯 메커니즘을 사용하기 위한 매크로
public:
// 생성자, 부모 위젯을 인자로 받음 (기본값은 nullptr)
Widget(QWidget *parent = nullptr);
// 소멸자, 객체가 소멸될 때 호출됨
~Widget();
private:
Ui::Widget *ui; // UI 요소를 관리하는 포인터
LoginWidget *loginWidget; // 로그인 위젯 포인터
QTcpSocket *socket; // 서버와의 TCP 통신을 위한 소켓
QString ipAddr; // 서버 IP 주소를 저장하는 변수
QString userName; // 사용자 이름을 저장하는 변수
private slots:
// 로그인 정보를 받아오는 슬롯 함수 (IP 주소와 사용자 이름)
void loginInfo(QString addr, QString name);
// 'Say' 버튼 클릭 시 호출되는 슬롯 함수
void sayButton_clicked();
// 서버와 연결되었을 때 호출되는 슬롯 함수
void connected();
// 서버로부터 데이터를 수신할 때 호출되는 슬롯 함수
void readyRead();
};
#endif // WIDGET_H
oginInfo( ) Slot 함수는 LoginWidget 클래스에서 [로그인] 버튼을 클릭하면 로그인창에 서 입력한 서버 IP주소와 사용자명을 전달하기 위한 시그널이 발생하며 이 시그널이 발생하면 호출되는 Slot 함수이다. 첫 번째 인자는 서버 IP주소이며 두 번째 인자는 사 용자 명이다.
다음 예제는 widget.cpp 소스코드이다.
#include "widget.h" // 위젯 헤더 파일 포함
#include "./ui_widget.h" // UI 위젯 헤더 파일 포함
#include <QRegularExpression> // 정규 표현식 포함
#include <QRegularExpressionMatch> // 정규 표현식 매칭 포함
// Widget 클래스 생성자 정의
Widget::Widget(QWidget *parent)
: QWidget(parent), ui(new Ui::Widget) // 부모 위젯 초기화 및 UI 설정
{
ui->setupUi(this); // UI 구성 설정
// 로그인 위젯 생성
loginWidget = new LoginWidget();
// 로그인 위젯에서 로그인 정보가 입력되었을 때 loginInfo 슬롯을 호출하도록 연결
connect(loginWidget, &LoginWidget::sig_loginInfo,
this, &Widget::loginInfo);
// 'Say' 버튼이 클릭되었을 때 sayButton_clicked 슬롯을 호출하도록 연결
connect(ui->sayButton, &QPushButton::pressed,
this, &Widget::sayButton_clicked);
// 로그인 위젯 표시
loginWidget->show();
// TCP 소켓 객체 생성
socket = new QTcpSocket(this);
// 소켓에서 데이터를 읽을 준비가 되었을 때 readyRead 슬롯을 호출하도록 연결
connect(socket, SIGNAL(readyRead()),
this, SLOT(readyRead()));
// 서버와 연결되었을 때 connected 슬롯을 호출하도록 연결
connect(socket, SIGNAL(connected()),
this, SLOT(connected()));
}
// 로그인 위젯에서 IP 주소와 사용자 이름을 받아 서버에 연결하는 함수
void Widget::loginInfo(QString addr, QString name)
{
ipAddr = addr; // 서버 IP 주소 저장
userName = name; // 사용자 이름 저장
// 지정된 IP 주소와 포트(35000)로 서버에 연결
socket->connectToHost(ipAddr, 35000);
}
// 'Say' 버튼이 클릭되었을 때 메시지를 서버로 전송하는 함수
void Widget::sayButton_clicked()
{
// 입력된 메시지를 가져오고 공백을 제거
QString message = ui->sayLineEdit->text().trimmed();
// 메시지가 비어있지 않다면 서버로 전송
if (!message.isEmpty())
{
socket->write(QString(message + "\n").toUtf8());
}
// 입력 필드를 초기화하고 포커스를 설정
ui->sayLineEdit->clear();
ui->sayLineEdit->setFocus();
}
// 서버와의 연결이 성공했을 때 호출되는 함수
void Widget::connected()
{
// 로그인 위젯을 숨기고 현재 위젯을 표시
loginWidget->hide();
this->window()->show();
// 사용자 이름을 서버에 전송
socket->write(QString("/me:" + userName + "\n").toUtf8());
}
// 서버로부터 데이터를 수신할 때 호출되는 함수
void Widget::readyRead()
{
// 소켓에 읽을 수 있는 데이터가 있는 동안 반복
while (socket->canReadLine())
{
// 서버로부터 한 줄의 데이터를 읽어와서 공백을 제거
QString line = QString::fromUtf8(socket->readLine()).trimmed();
// 정규 표현식을 사용하여 "사용자:메시지" 형식으로 데이터를 분리
QRegularExpression re("^([^:]+):(.*)$");
QRegularExpressionMatch match = re.match(line);
// 매칭된 데이터가 있으면 사용자와 메시지를 UI에 표시
if (match.hasMatch())
{
QString user = match.captured(1); // 사용자 이름
QString message = match.captured(2); // 메시지 내용
ui->roomTextEdit->append("<b>" + user + "</b>:" + message);
}
}
}
// Widget 클래스 소멸자 정의
Widget::~Widget()
{
// UI 메모리 해제
delete ui;
}
[Send] 버튼을 클릭하면 sayButton_clicked( ) Slot 함수가 호출된다. connected( ) Slot 함수는 채팅 서버와 연결이 완료되면 호출된다. readyRead( ) 는 채팅 서버가 메시지를 전송하면 호출되는 Slot 함수이다. 이 Slot 함수 에서 메시지를 QTextEdit 위젯에 출력한다. 이 예제를 빌드한 다음 실해 보도록 하자.
이 예제를 실행하기 전 서버 예제가 먼저 실행되어야 한다.

'스터디 > QT 스터디' 카테고리의 다른 글
QT 스터디 챕터 19 ~ 26 (0) | 2024.10.09 |
---|---|
QT 스터디 챕터 9~16 (14제외) (2) | 2024.10.08 |
QT 스터디 챕터 7~8 (0) | 2024.10.08 |
QT 스터디 챕터 1~6 (0) | 2024.10.08 |