Chapter26
1. 00_SQLite_Example(MySQL로 변환)
<widget.h>
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QSqlDatabase>
#include <QSqlTableModel>
#define HOSTNAME "localhost"
#define USERNAME "root"
#define PASSWORD "Marin0806!"
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
QSqlDatabase m_db;
QSqlTableModel *m_model;
bool initializeDataBase();
void creationTable();
void insertDataToTable();
void initializeModel();
private slots:
void slot_pbtUpdate();
void slot_pbtDelete();
};
#endif // WIDGET_H
>
멤버변수(private)
ui
: Qt Designer로 만든 UI(사용자 인터페이스) 요소에 접근하기 위한 포인터.
m_db
: QSqlDatabase 클래스의 객체로, 데이터베이스 연결을 담당.
m_model
: QSqlTableModel 객체로, 데이터베이스의 테이블을 모델로 사용하여 QTableView와 같은 UI 위젯에 데이터를 표시하고 조작하는 역할.
>
매서드(private)
initializeDataBase()
: MySQL 데이터베이스에 연결하는 함수입니다. 연결 성공 여부를 bool로 반환.
creationTable()
: 데이터베이스에 필요한 테이블을 생성하는 함수.
insertDataToTable()
: 생성된 테이블에 초기 데이터를 삽입하는 함수.
initializeModel()
: QSqlTableModel을 초기화하고, 테이블의 헤더 등을 설정하는 함수.
>
슬롯(private slots)
slot_pbtUpdate()
: "Update" 버튼을 눌렀을 때 호출되어 데이터베이스의 내용을 수정하는 기능을 수행.
slot_pbtDelete()
: "Delete" 버튼을 눌렀을 때 호출되어 데이터베이스의 내용을 삭제하는 기능을 수행.
<widget.cpp>
#include "widget.h"
#include "./ui_widget.h"
#include <QSqlQuery>
#include <QSqlError>
#include <QFile>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
if( initializeDataBase() )
{
creationTable();
insertDataToTable();
initializeModel();
ui->tableView->setModel(m_model);
connect(ui->pbtUpdate, SIGNAL(pressed()),
this, SLOT(slot_pbtUpdate()));
connect(ui->pbtDelete, SIGNAL(pressed()),
this, SLOT(slot_pbtDelete()));
}
}
Widget::~Widget()
{
delete ui;
}
bool Widget::initializeDataBase()
{
// MySQL 드라이버를 사용하도록 설정합니다.
m_db = QSqlDatabase::addDatabase("QMYSQL");
// MySQL 서버 접속 정보를 설정합니다.
m_db.setHostName(HOSTNAME);
m_db.setUserName(USERNAME);
m_db.setPassword(PASSWORD);
// MySQL에 연결을 시도합니다.
if (!m_db.open()) {
qDebug() << Q_FUNC_INFO << m_db.lastError().text();
return false;
}
// 데이터베이스가 존재하지 않는 경우를 대비해 생성 쿼리를 실행합니다.
QSqlQuery qry;
if (!qry.exec("CREATE DATABASE IF NOT EXISTS test_DB")) {
qDebug() << qry.lastError().text();
return false;
}
// 이제 특정 데이터베이스에 연결하도록 설정합니다.
m_db.setDatabaseName("test_DB");
// 데이터베이스 이름을 설정한 후, 다시 연결을 시도.
m_db.close();
if (!m_db.open()) {
qDebug() << Q_FUNC_INFO << m_db.lastError().text();
return false;
}
return true;
}
void Widget::creationTable()
{
QSqlQuery qry;
qry.prepare( "CREATE TABLE IF NOT EXISTS names "
"("
" id INTEGER UNIQUE PRIMARY KEY, "
" firstname VARCHAR(30), "
" lastname VARCHAR(30)"
")" );
if( !qry.exec() ) {
qDebug() << qry.lastError().text();
}
}
void Widget::insertDataToTable()
{
QSqlQuery qry;
qry.prepare( "INSERT INTO names "
"(id, firstname, lastname) "
"VALUES "
"(1, 'John', 'Doe')" );
if( !qry.exec() )
qDebug() << qry.lastError();
qry.prepare( "INSERT INTO names "
"(id, firstname, lastname) "
"VALUES"
"(2, 'Jane', 'Doe')" );
if( !qry.exec() )
qDebug() << qry.lastError();
qry.prepare( "INSERT INTO names "
"(id, firstname, lastname) "
"VALUES "
"(3, 'James', 'Doe')" );
if( !qry.exec() )
qDebug() << qry.lastError();
}
void Widget::initializeModel()
{
m_model = new QSqlTableModel(this, m_db);
m_model->setTable("names");
m_model->setEditStrategy(QSqlTableModel::OnManualSubmit);
m_model->select();
m_model->setHeaderData(0, Qt::Horizontal, tr("ID"));
m_model->setHeaderData(1, Qt::Horizontal, tr("First Name"));
m_model->setHeaderData(2, Qt::Horizontal, tr("Last Name"));
}
void Widget::slot_pbtUpdate()
{
QSqlQuery qry;
qry.prepare( "UPDATE names "
"SET firstname = 'MyeongJin', "
"lastname = 'Lee' WHERE id = 1" );
if( !qry.exec() )
qDebug() << qry.lastError();
m_model->setTable("names");
m_model->select();
ui->tableView->setModel(m_model);
}
void Widget::slot_pbtDelete()
{
QSqlQuery qry;
qry.prepare( "DELETE FROM names WHERE id = 1" );
if( !qry.exec() )
qDebug() << qry.lastError();
m_model->setTable("names");
m_model->select();
ui->tableView->setModel(m_model);
}
>
Widget 생성자 (Widget::Widget(...))
ui->setupUi(this)
: Qt Designer로 만든 UI 레이아웃을 로드.
if (initializeDataBase())
: 데이터베이스 연결을 시도합니다. 연결에 성공해야만 아래의 모든 로직이 실행.
creationTable();, insertDataToTable();, initializeModel();
: 데이터베이스 연결 후 테이블을 생성,
초기 데이터를 넣고,
데이터베이스 모델을 준비.
ui->tableView->setModel(m_model);
: 준비된 데이터 모델을 QTableView 위젯에 연결하여 테이블 데이터를 화면에 보여줌.
connect(...)
: UI의 pbtUpdate와 pbtDelete 버튼이 눌렸을 때 (pressed() 시그널)
각각 slot_pbtUpdate()와 slot_pbtDelete() 슬롯 함수를 호출하도록 연결.
>
initializeDataBase()
m_db = QSqlDatabase::addDatabase("QMYSQL");
: 데이터베이스 드라이버로 MySQL을 선택.
setHostName(), setUserName(), setPassword()
: HOSTNAME, USERNAME, PASSWORD 상수에 정의된 정보로 서버에 접속 설정.
m_db.open()
: MySQL 서버에 접속을 시도.
CREATE DATABASE IF NOT EXISTS test_DB
: QSqlQuery를 사용하여 test_DB라는 이름의 데이터베이스가 없으면 새로 생성.
m_db.setDatabaseName("test_DB"); / 재연결
: 데이터베이스가 준비되면, 이제 test_DB에 연결하도록 설정.
기존 연결을 해제하고 다시 연결.
이 과정은 QSqlQuery가 특정 데이터베이스 컨텍스트에서 실행되도록 하기 위해 필요.
>
creationTable() 및 insertDataToTable()
creationTable()
: names라는 테이블을 만듦.
CREATE TABLE IF NOT EXISTS를 사용해 테이블이 이미 존재하면 오류 없이 넘어감.
id는 UNIQUE PRIMARY KEY로 설정되어 각 행을 고유하게 식별.
insertDataToTable()
: QSqlQuery를 이용해 "John Doe", "Jane Doe", "James Doe" 세 명의 초기 데이터를 names 테이블에 삽입.
>
initializeModel()
m_model = new QSqlTableModel(this, m_db);
: QSqlTableModel 객체를 생성. 이 모델은 데이터베이스 테이블의 데이터를 가져오고, UI(QTableView)와 동기화하는 역할을 함.
m_model->setTable("names");
: 모델이 names 테이블의 데이터를 다루도록 지정.
m_model->setEditStrategy(QSqlTableModel::OnManualSubmit);
: 사용자가 UI에서 데이터를 변경하면, 수동으로 submitAll()을 호출해야만 데이터베이스에 변경 내용이 반영되도록 설정.
m_model->select();
: 모델에 데이터를 불러오도록 명령.
m_model->setHeaderData(...)
: QTableView에 표시될 열의 헤더 이름을 설정.
>
slot_pbtUpdate() 및 slot_pbtDelete()
slot_pbtUpdate()
: UPDATE 쿼리를 실행하여 id가 1인 행의 firstname과 lastname을 변경.
slot_pbtDelete()
: DELETE 쿼리를 실행하여 id가 1인 행을 삭제.
m_model->select();
: 쿼리 실행 후, 모델을 다시 불러와(select()) QTableView가 변경된 데이터베이스 내용을 즉시 반영.
<main.cpp>
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
2. 01_Search_Example(MySQL로 변환)
<widget.ui>

<databasehandler.h>
#ifndef DATABASEHANDLER_H
#define DATABASEHANDLER_H
#include <QObject>
#include <QSql>
#include <QSqlQuery>
#include <QSqlError>
#include <QSqlDatabase>
#include <QDate>
#include <QDebug>
#define DATABASE_HOSTNAME "localhost" // HOST
#define DATABASE_NAME "qtdev" // DB
#define DATABASE_USERNAME "root" // USER
#define DATABASE_PASSWORD "Marin0806!" // 비밀번호
class DatabaseHandler : public QObject
{
Q_OBJECT
public:
explicit DatabaseHandler(QObject *parent = nullptr);
~DatabaseHandler();
void connectToDataBase();
bool insertIntoTable(const QVariantList &data);
private:
QSqlDatabase db;
private:
bool openDataBase();
bool restoreDataBase();
void closeDataBase();
bool createTable();
};
#endif // DATABASEHANDLER_H
>
DatabaseHandler 클래스
connectToDataBase()
: 데이터베이스에 연결하고 테이블을 준비하는 외부 공개 함수.
insertIntoTable()
: QVariantList 형태의 데이터를 테이블에 삽입하는 함수.
>
private 멤버 변수 및 함수
QSqlDatabase db
: 데이터베이스 연결을 관리하는 핵심 객체.
이 객체를 통해 서버에 접속하고 쿼리를 실행.
openDataBase()
: 실제 데이터베이스 연결을 여는 함수.
restoreDataBase()
: openDataBase()와 createTable()을 순차적으로 호출하여 데이터베이스를 준비하는 함수.
closeDataBase()
: 열려 있는 데이터베이스 연결을 닫는 함수.
createTable()
: 데이터베이스 내에 테이블을 생성하는 함수.
<databasehandler.cpp>
#include "databasehandler.h"
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>
DatabaseHandler::DatabaseHandler(QObject *parent)
: QObject(parent)
{
}
void DatabaseHandler::connectToDataBase()
{
// 데이터베이스 복원
this->restoreDataBase();
}
bool DatabaseHandler::restoreDataBase()
{
// 데이터베이스 연결 시도
if (!this->openDataBase()) {
qDebug() << "데이터베이스 연결 실패. restoreDataBase() 함수 종료.";
return false;
}
// 연결에 성공하면 테이블을 생성.
if (!this->createTable()) {
qDebug() << "테이블 생성 실패. restoreDataBase() 함수 종료.";
return false;
}
return true;
}
bool DatabaseHandler::openDataBase()
{
// QMYSQL 드라이버 사용.
db = QSqlDatabase::addDatabase("QMYSQL");
// MySQL 서버 접속 정보 설정.
db.setHostName(DATABASE_HOSTNAME);
db.setUserName(DATABASE_USERNAME);
db.setPassword(DATABASE_PASSWORD);
// MySQL 서버 우선 연결.
if (!db.open()) {
qDebug() << "MySQL 서버 연결 실패: " << db.lastError().text();
return false;
}
// 서버 연결에 성공하면 데이터베이스가 존재하는지 확인하고, 없으면 생성.
QSqlQuery query;
QString createDbQuery = QString("CREATE DATABASE IF NOT EXISTS %1").arg(DATABASE_NAME);
if (!query.exec(createDbQuery)) {
qDebug() << "데이터베이스 생성 실패: " << query.lastError().text();
db.close(); // 실패 시 연결 종료
return false;
}
// 생성된 데이터베이스를 지정하고 다시 연결.
db.setDatabaseName(DATABASE_NAME);
// 기존 연결을 닫고 새로운 데이터베이스로 다시 연결.
db.close();
if (db.open()) {
qDebug() << "MySQL 데이터베이스 연결 성공.";
return true;
} else {
qDebug() << "MySQL 데이터베이스 연결 실패 (재연결): " << db.lastError().text();
return false;
}
}
void DatabaseHandler::closeDataBase()
{
// 데이터베이스 연결 종료.
db.close();
qDebug() << "데이터베이스 연결 종료.";
}
bool DatabaseHandler::createTable()
{
QSqlQuery query;
if (!query.exec( "CREATE TABLE IF NOT EXISTS RECEIVE_MAIL ("
"id INTEGER PRIMARY KEY AUTO_INCREMENT, "
"TIME TIME NOT NULL,"
"MESSAGE INTEGER NOT NULL,"
"RANDOM VARCHAR(255) NOT NULL"
" )"
)){
qDebug() << "테이블 생성 에러 : " << query.lastError().text();
return false;
} else {
qDebug() << "테이블 생성 성공 또는 이미 존재함.";
return true;
}
}
bool DatabaseHandler::insertIntoTable(const QVariantList &data)
{
QSqlQuery query;
query.prepare("INSERT INTO "
"RECEIVE_MAIL (TIME, MESSAGE, RANDOM) "
"VALUES (:TIME, :MESSAGE, :RANDOM )");
query.bindValue(":TIME", data[0].toTime());
query.bindValue(":MESSAGE", data[1].toInt());
query.bindValue(":RANDOM", data[2].toString());
if (!query.exec()){
qDebug() << "데이터 삽입 에러 - " << query.lastError().text();
return false;
} else {
qDebug() << "데이터 삽입 성공.";
return true;
}
}
DatabaseHandler::~DatabaseHandler()
{
closeDataBase();
}
>
connectToDataBase() 및 restoreDataBase()
connectToDataBase()
: 이 함수는 외부에서 데이터베이스 연결을 시작할 때 사용되는 진입점.
restoreDataBase()를 호출하여 실제 연결 및 테이블 생성 과정을 시작.
restoreDataBase()
: 데이터베이스 연결과 테이블 생성을 순차적으로 처리.
openDataBase()를 호출해 연결에 성공하면 다음 단계로 넘어r감.
createTable()을 호출해 테이블을 생성.
이 두 단계 중 하나라도 실패하면 false를 반환하고, 모두 성공하면 true를 반환. 이는 연결 및 초기화 상태를 확인하는 데 유용함.
>
openDataBase()
QMYSQL 드라이버 선택
: db = QSqlDatabase::addDatabase("QMYSQL");를 통해 MySQL용 드라이버를 지정.
서버 연결
: setHostName, setUserName, setPassword를 이용해 서버 자체에 먼저 연결을 시도.
이때 setDatabaseName은 아직 호출하지 않음.
데이터베이스 생성
: 서버 연결에 성공하면 QSqlQuery를 사용해 CREATE DATABASE IF NOT EXISTS %1 쿼리를 실행.
DATABASE_NAME에 지정된 데이터베이스가 없으면 자동으로 생성됨.
재연결
: 데이터베이스가 성공적으로 생성되었거나 이미 존재하는 경우, db.setDatabaseName(DATABASE_NAME);을 호출하여 특정 데이터베이스로 연결 대상을 변경.
기존 연결을 닫았다가 다시 열어 새롭게 지정된 데이터베이스에 접속.
>
createTable()
QSqlQuery query;
: 쿼리를 실행할 객체를 생성.
CREATE TABLE IF NOT EXISTS...
: RECEIVE_MAIL 테이블을 만듦. IF NOT EXISTS를 사용해 테이블이 이미 존재하면 오류 없이 넘어감.
>
insertIntoTable()
QSqlQuery query; query.prepare(...)
: 데이터를 삽입할 쿼리를 준비. prepare 함수는 쿼리의 구조를 정의하고, :TIME과 같은 플레이스홀더를 사용해 SQL 인젝션 공격을 방지하고 성능을 최적화.
query.bindValue(...)
: QVariantList로 전달된 데이터를 placeholder에 바인딩.(ex. :TIME 위치에 data[0].toTime() 값을 저장)
데이터는 toTime(), toInt(), toString() 등을 통해 적절한 타입으로 변환됨.
query.exec()
: 준비된 쿼리를 실행. 실패하면 오류 메시지를 출력하고 false를 반환.
<widget.h>
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QSqlTableModel>
#include "databasehandler.h"
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
DatabaseHandler *m_dbHandler;
QSqlTableModel *m_model;
void setupModel(const QString &tableName, const QStringList &headers);
void createUserInterface();
private slots:
void onPushButton();
};
#endif // WIDGET_H
>
멤버 변수
Ui::Widget *ui
: Qt Designer를 통해 생성된 사용자 인터페이스 요소들에 접근하기 위한 포인터.
DatabaseHandler *m_dbHandler
: 데이터베이스 관련 모든 작업을 처리하는 DatabaseHandler 클래스의 객체 포인터.
QSqlTableModel *m_model
: 데이터베이스의 특정 테이블 데이터를 가져와서 UI 위젯(예: QTableView)에 표시하고 동기화하는 역할을 하는 모델.
>
멤버 함수
setupModel(const QString &tableName, const QStringList &headers)
: 데이터 모델을 설정하는 함수. 어떤 테이블을 사용할지, 헤더는 어떻게 표시할지 등을 정의.
createUserInterface()
: 사용자 인터페이스를 구성하는 함수.
onPushButton()
: 버튼 클릭과 같은 특정 이벤트에 반응하는 슬롯(slot) 함수.
<widget.cpp>
#include "widget.h"
#include "./ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
m_dbHandler = new DatabaseHandler();
m_dbHandler->connectToDataBase();
for(int i = 0; i < 10; i++)
{
QVariantList data;
QTime currTime = QTime::currentTime();
currTime = currTime.addSecs(i*1000);
int random = rand();
data.append(currTime);
data.append(random);
data.append("Message : " + QString::number(random));
m_dbHandler->insertIntoTable(data);
}
setupModel("RECEIVE_MAIL",
QStringList() << "ID"
<< "Time"
<< "Number"
<< "Message"
);
createUserInterface();
}
Widget::~Widget()
{
delete ui;
}
void Widget::setupModel(const QString &tableName,
const QStringList &headers)
{
m_model = new QSqlTableModel(this);
m_model->setTable(tableName);
for(int i = 0, j = 0; i < m_model->columnCount(); i++, j++) {
m_model->setHeaderData(i, Qt::Horizontal, headers[j]);
}
m_model->setSort(0,Qt::AscendingOrder);
}
void Widget::createUserInterface()
{
ui->tableView->setModel(m_model);
ui->tableView->setColumnHidden(0, true);
ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
ui->tableView->setSelectionMode(QAbstractItemView::SingleSelection);
ui->tableView->resizeColumnsToContents();
ui->tableView->horizontalHeader()->setStretchLastSection(true);
for(int i = 0 ; i < 4 ; i++)
ui->tableView->setColumnWidth(i, 100);
m_model->select(); // 테이블로부터 데이터를 Fetche
ui->timeFROM->setTime(QTime::currentTime());
ui->timeTO->setTime(QTime::currentTime());
connect(ui->pbtSearch, SIGNAL(pressed()),
this, SLOT(onPushButton()));
}
void Widget::onPushButton()
{
QString str = QString("TIME between '%3' and '%4'")
.arg(ui->timeFROM->time().toString("hh:mm:ss"));
m_model->setFilter(QString( "TIME between '%3' and '%4'")
.arg(ui->timeFROM->time().toString("hh:mm:ss"),
ui->timeTO->time().toString("hh:mm:ss")));
m_model->select(); // 테이블로부터 데이터를 Fetche
}
>
Widget 생성자 (Widget::Widget(...))
ui->setupUi(this)
: Qt Designer로 만든 UI 파일을 로드.
m_dbHandler = new DatabaseHandler();
: 데이터베이스 연결을 전담하는 DatabaseHandler 객체를 생성.
m_dbHandler->connectToDataBase();
: 데이터베이스에 연결하고 테이블(RECEIVE_MAIL)이 없으면 생성하는 작업을 시작.
데이터 삽입 루프
: for 루프를 통해 현재 시간, 무작위 숫자, 메시지 문자열을 포함하는 10개의 가상 데이터를 생성하여 m_dbHandler->insertIntoTable(data); 함수로 데이터베이스에 삽입.
setupModel(...)
: 데이터베이스 모델을 설정하고, createUserInterface()를 호출하여 UI를 구성.
>
setupModel()
: QSqlTableModel 객체를 설정하고 초기화하는 함수.
m_model = new QSqlTableModel(this);
: QSqlTableModel 객체를 생성.
m_model->setTable(tableName);
: 모델이 데이터베이스의 RECEIVE_MAIL 테이블과 연결되도록 설정.
for(...)
: headers 리스트를 사용하여 ID, Time, Number, Message와 같은 컬럼 헤더 이름을 설정.
i : 인덱스
Qt::Horizontal : 헤더의 방향(가로 헤더)
headers[j] : 헤더에 표시할 실제 텍스트
m_model->setSort(0, Qt::AscendingOrder);
: 첫 번째 컬럼(ID)을 기준으로 오름차순 정렬되도록 설정.
>
createUserInterface()
: UI 위젯의 속성을 설정하고 시그널-슬롯 연결을 담당.
ui->tableView->setModel(m_model);
: 데이터 모델을 tableView에 연결하여 데이터가 화면에 표시.
ui->tableView->setColumnHidden(0, true);
: 첫 번째 컬럼(id)을 숨겨 사용자에게 보이지 않게 함.
setSelectionBehavior(...), setSelectionMode(...)
: 테이블 뷰의 선택 방식을 설정. 여기서는 행 단위로 하나씩만 선택되도록 설정되어 있음.
m_model->select();
: 데이터베이스에서 테이블의 데이터를 가져와 모델에 로드.
ui->timeFROM->setTime(...), ui->timeTO->setTime(...)
: QTimeEdit 위젯에 현재 시간을 초기값으로 설정.
connect(...)
: pbtSearch 버튼이 눌리면 onPushButton() 슬롯 함수가 호출되도록 연결.
>
onPushButton()
: pbtSearch 버튼을 눌렀을 때 실행되는 함수로, 특정 시간 범위로 데이터를 필터링하는 기능을 수행.
m_model->setFilter(...)
: SQL WHERE 절과 동일한 문법으로 필터링 조건을 설정.
이 코드에서는 ui->timeFROM과 ui->timeTO 위젯의 시간 값을 이용해 TIME 컬럼이 특정 시간 범위 안에 있는 데이터만 선택하도록 필터를 적용.
오류 수정
: QString::arg를 사용할 때 %3과 %4를 지정했음에도 불구하고, 앞쪽 str 변수에서는 %3만 사용하고 있어 오류가 발생할 수 있음. m_model->setFilter(...) 부분은 %3과 %4를 모두 사용하여 올바르게 동작하도록 작성되어 있음.
m_model->select();
: 설정된 필터 조건에 맞게 데이터베이스에서 데이터를 다시 가져와 화면을 업데이트.
<main.cpp>
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
Chapter28
0. QT의 Network programming
>
Signal/Slot 기반의 이벤트 처리
일반적인 네트워크 프로그래밍에서는 수신 대기나 데이터 처리를 위해 별도의 thread를 두는 경우가 많은 반면
Qt에서는 Signal/Slot 메커니즘을 활용하여 네트워크 이벤트(연결, 데이터 수신, 연결 종료 등)를 비동기적으로 처리가 가능
이 방식은 스레드 관리의 복잡성을 줄이고, Qt의 이벤트 루프와 자연스럽게 연계됨.
>
저수준(LOW-Level) 네트워크 클래스 제공
TCP/UDP 통신을 직접 제어할 수 있는 클래스들을 제공하여, 세밀한 네트워크 제어 가능.
QTcpSocket : TCP 클라이언트 소켓
QTcpServer : TCP 서버 소켓
QUdpSocket : UDP 통신 소켓
이 클래스들은 운영체제의 소켓 API를 캡슐화하여, 플랫폼에 구애받지 않는 코드 작성 가능.
1. TCP 프로토콜 기반 서버/클라이언트
1.1 00_TcpServer
<widget.ui>

<widget.h>
#ifndef WIDGET_H
#define WIDGET_H
#include <QObject>
#include <QWidget>
#include <QtNetwork/QTcpServer>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
QTcpServer *tcpServer;
void initialize();
private slots:
void newConnection();
};
#endif // WIDGET_H
헤더 파일
>
#include <QObject>
: Qt의 객체 모델을 사용하기 위한 기본 클래스.
QObject를 상속받아야 시그널과 슬롯 기능을 사용 가능.
>
#include <QWidget>
: 사용자 인터페이스(UI) 위젯의 기본 클래스.
Widget 클래스가 UI를 가지므로 QWidget를 상속받음.
>
#include <QtNetwork/QTcpServer>
: TCP 서버 기능을 제공하는 클래스.
클라이언트 연결을 수신하고 관리하는 역할.
클래스 정의(Class)
>
class Widget : public QWidget
: QWidget를 상속받아 정의.
Q_OBJECT 매크로를 사용하여 시그널-슬롯 메커니즘을 활성화합니다.
멤버 변수(Filed)
>
Ui::Widget *ui
: Qt 디자이너로 생성된 UI 파일(widget.ui)의 요소들을 관리하는 포인터.
UI 객체에 접근하고 조작하는 데 사용.
>
QTcpServer *tcpServer
: TCP 서버 객체의 포인터.
이 객체를 통해 클라이언트 연결을 기다리고 처리.
멤버 함수(Method)
>
Widget(QWidget *parent = nullptr)
: 위젯의 생성자.
위젯을 초기화하고 부모 위젯을 지정.
>
~Widget()
: 위젯의 소멸자.
객체가 파괴될 때 메모리 해제 등의 정리 작업을 수행.
>
void initialize()
: 위젯의 초기화 로직을 담을 함수.
주로 UI 설정이나 tcpServer 객체 생성 및 설정을 담당.
슬롯 함수(Slot)
>
void newConnection():
QTcpServer가 새로운 클라이언트의 연결을 감지하면, 이 슬롯 함수가 자동으로 호출됨.
이 함수 안에서 새로운 연결을 처리하는 로직을 작성.
<widget.cpp>
#include "widget.h"
#include "./ui_widget.h"
#include <QtNetwork/QNetworkInterface>
#include <QtNetwork/QTcpSocket>
#include <QMessageBox>
#include <QTime>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
initialize();
}
void Widget::initialize()
{
QHostAddress hostAddr;
QList<QHostAddress> ipList = QNetworkInterface::allAddresses();
// Use something other than localhost (127.0.0.1)
for (int i = 0; i < ipList.size(); ++i) {
if (ipList.at(i) != QHostAddress::LocalHost &&
ipList.at(i).toIPv4Address()) {
hostAddr = ipList.at(i);
break;
}
}
if (hostAddr.toString().isEmpty())
hostAddr = QHostAddress(QHostAddress::LocalHost);
tcpServer = new QTcpServer(this);
if (!tcpServer->listen(hostAddr, 25000)) {
QMessageBox::critical(this, tr("TCP Server"),
tr("Cannot start the server, Error: %1.")
.arg(tcpServer->errorString()));
close();
return;
}
ui->labelStatus->setText(tr("Server running \n\n"
"IP : %1\n"
"PORT : %2\n")
.arg(hostAddr.toString())
.arg(tcpServer->serverPort()));
connect(tcpServer, SIGNAL(newConnection()), this, SLOT(newConnection()));
ui->connMsgEdit->clear();
}
void Widget::newConnection()
{
QTcpSocket *clientConnection = tcpServer->nextPendingConnection();
connect(clientConnection, SIGNAL(disconnected()),
clientConnection, SLOT(deleteLater()));
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;
}
Widget 생성자
>
QWidget(parent)
: Widget 클래스는 QWidget를 상속받았으므로, 먼저 부모 클래스인 QWidget의 생성자를 호출하여 초기화.
parent 인자는 Widget 객체가 다른 위젯의 자식 위젯으로 포함될 때, 그 부모 위젯을 지정하는 데 사용됨.
nullptr이 전달되면 부모 위젯 없이 독립적인 최상위 윈도우로 생성됨.
이 과정을 통해 Widget 객체가 올바른 위젯 계층 구조에 속하게 되며, QWidget가 제공하는 기본 기능(이벤트 처리, 렌더링 등)을 사용할 준비가 됨.
>
ui(new Ui::Widget)
: Widget 클래스의 멤버 변수인 ui를 초기화.
new Ui::Widget은 Ui::Widget 타입의 객체를 힙(heap) 메모리에 동적으로 할당하고, 그 주소를 ui 포인터에 할당.
Ui::Widget 클래스는 Qt Designer로 .ui 파일을 만들 때 자동으로 생성되는 클래스로, UI 요소들(버튼, 라벨 등)을 관리하는 역할.
이 초기화를 통해 생성자 본문이 실행되기 전에 ui 포인터가 유효한 Ui::Widget 객체를 가리키게 되어, setupUi() 같은 함수를 호출할 수 있게 됨.
>
ui->setupUi(this)
: widget.ui 파일에 정의된 UI를 화면에 로드.
>
initialize()
: 서버를 초기화하는 함수를 호출.
initialize() 함수
>
IP 주소 찾기
QNetworkInterface::allAddresses()
: 현재 컴퓨터에 연결된 모든 네트워크 인터페이스의 IP 주소 목록을 가져옴.
LocalHost 제외
: for 루프를 통해 127.0.0.1(localhost)이 아닌 첫 번째 IPv4 주소를 찾아 hostAddr에 저장.
이는 외부 네트워크에서 서버에 접속할 수 있도록 하기 위함이고, 만약 IPv4 주소를 찾지 못하면 localhost를 사용함.
>
서버 시작
tcpServer = new QTcpServer(this)
: QTcpServer 객체를 생성.
tcpServer->listen(hostAddr, 25000)
: 지정된 IP 주소(hostAddr)와 **포트 번호 25000**에서 클라이언트의 연결을 기다리기 시작.
만약 서버를 시작하지 못하면, 에러 메시지를 표시하고 프로그램을 종료.
>
UI 업데이트
ui->labelStatus->setText(...)
: 서버의 IP와 포트 정보를 화면에 있는 labelStatus 위젯에 표시.
>
시그널-슬롯 연결
connect(tcpServer, SIGNAL(newConnection()), this, SLOT(newConnection()))
: tcpServer에 새로운 클라이언트가 연결되면, newConnection() 슬롯 함수를 자동으로 호출하도록 연결함.
newConnection() 슬롯 함수
>
클라이언트 연결 처리
tcpServer->nextPendingConnection()
: 연결을 요청한 클라이언트와의 통신을 위한 QTcpSocket 객체를 가져옴.
connect(clientConnection, ...)
: 클라이언트가 연결을 끊었을 때(disconnected() 시그널), clientConnection 객체를 자동으로 메모리에서 해제(deleteLater() 슬롯)하도록 연결함.
>
메시지 전송
ui->connMsgEdit->append(text)
: 클라이언트 연결 시간을 UI의 connMsgEdit에 추가함.
clientConnection->write(message)
: 클라이언트에게 "Hello Client ~ "라는 메시지를 보냄.
clientConnection->disconnectFromHost()
: 메시지를 보낸 후 즉시 클라이언트와의 연결을 종료.
Widget 소멸자
delete ui
: 프로그램이 종료될 때 UI 객체가 차지했던 메모리를 해제.
<main.cpp>
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
1.2 00_TcpClient
<widget.ui>

<widget.h>
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QtNetwork/QTcpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
QTcpSocket *tcpSocket;
void initialize();
private slots:
void connectButton();
void readMessage(); // 서버로부터 메세지를 받을 때 호출 됨
void disconnected();
};
#endif // WIDGET_H
헤더 파일
>
#include <QWidget>
>
#include <QtNetwork/QTcpSocket>
클래스 정의(Class)
>
class Widget : public QWidget
멤버 변수(Filed)
>
Ui::Widget *ui
>
QTcpSocket *tcpSocket
멤버 함수(Method)
>
Widget(QWidget *parent = nullptr)
>
~Widget()
>
void initialize()
슬롯 함수(Slot)
void connectButton()
: "연결" 버튼 등 특정 UI 요소와 연결될 슬롯 함수.
이 함수에서 서버에 연결하는 로직을 구현.
void readMessage()
: 소켓으로 데이터가 도착했을 때 자동으로 호출되는 슬롯 함수.
서버가 보낸 메시지를 읽는 역할.
void disconnected()
: 서버와의 연결이 끊어졌을 때 자동으로 호출되는 슬롯 함수.
연결이 끊겼을 때 필요한 처리(예: UI 업데이트)를 수행.
<widget.cpp>
#include "widget.h"
#include "./ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->connectButton, SIGNAL(clicked()),
this, SLOT(connectButton()));
initialize();
}
void Widget::initialize()
{
tcpSocket = new QTcpSocket(this);
connect(tcpSocket, SIGNAL(readyRead()),
this, SLOT(readMessage()));
connect(tcpSocket, SIGNAL(disconnected()),
this, SLOT(disconnected()));
}
void Widget::connectButton()
{
QString serverip = ui->serverIP->text().trimmed();
QHostAddress serverAddress(serverip);
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 << "Server Connection Close.";
}
Widget::~Widget()
{
delete ui;
}
Widget 생성자
>
ui->setupUi(this)
>
connect(ui->connectButton, SIGNAL(clicked()), this, SLOT(connectButton()))
: UI에 있는 connectButton이 클릭되면 connectButton() 슬롯 함수가 호출되도록 연결.
이 함수를 통해 서버에 연결을 시도.
>
initialize()
initialize() 함수
>
tcpSocket = new QTcpSocket(this)
: QTcpSocket 객체를 동적으로 생성. 이 소켓이 서버와의 통신을 담당.
>
connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(readMessage()))
: tcpSocket에 서버로부터 읽을 수 있는 데이터가 도착하면(readyRead() 시그널), readMessage() 슬롯 함수를 자동으로 호출하도록 연결.
이 함수에서 데이터를 읽어옴.
>
connect(tcpSocket, SIGNAL(disconnected()), this, SLOT(disconnected()))
: 서버와의 연결이 끊어지면(disconnected() 시그널), disconnected() 슬롯 함수를 호출하도록 연결.
connectButton() 함수
QString serverip = ui->serverIP->text().trimmed()
: UI의 serverIP라는 텍스트 입력란에 사용자가 입력한 IP 주소를 가져와 공백을 제거.
QHostAddress serverAddress(serverip)
: 가져온 문자열을 QHostAddress 객체로 변환.
tcpSocket->connectToHost(serverAddress, 25000)
: 변환된 IP 주소와 25000번 포트로 서버에 연결을 시도.
readMessage() 함수
if(tcpSocket->bytesAvailable() >= 0)
: 소켓에 읽을 수 있는 데이터가 있는지 확인.
QByteArray readData = tcpSocket->readAll()
: 소켓의 모든 데이터를 읽어 QByteArray에 저장.
ui->textEdit->append(readData)
: 읽어온 데이터를 UI의 textEdit 위젯에 추가하여 사용자에게 보여줌.
disconnected() 함수
qDebug() << Q_FUNC_INFO << "Server Connection Close."
: 서버와의 연결이 끊어졌을 때 디버그 콘솔에 메시지를 출력.
Widget 소멸자
<main.cpp>
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}

2. 데이터 송/수신
2.1 00_Sync(동기 방식)
<qhttpsocket.h>
#ifndef QHTTPSOCKET_H
#define QHTTPSOCKET_H
#include <QObject>
#include <QTcpSocket>
class QHttpSocket : public QObject
{
Q_OBJECT
public:
explicit QHttpSocket(QObject *parent = nullptr);
~QHttpSocket();
void httpConnect();
signals:
public slots:
void httpDisconnected();
private:
QTcpSocket *socket;
};
#endif // QHTTPSOCKET_H
헤더 파일
>
#include <QObject>
>
#include <QTcpSocket>
클래스 정의(Class)
>
class QHttpSocket : public QObject
멤버 변수(Filed)
>
QTcpSocket *socket
멤버 함수(Method)
>
explicit QHttpSocket(QObject *parent = nullptr)
>
~QHttpSocket()
>
void httpConnect()
: HTTP 연결을 시작하는 로직을 담을 함수.
이 함수 내부에서 QTcpSocket을 사용해 서버에 연결을 시도.
슬롯 함수(Slot)
void httpDisconnected()
: TCP 소켓이 서버와의 연결을 끊었을 때 호출될 슬롯 함수.
연결이 끊어졌을 때 필요한 후속 처리(예: 상태 변경)를 담당.
<qhttpsocket.cpp>
#include "qhttpsocket.h"
QHttpSocket::QHttpSocket(QObject *parent)
: QObject{parent}
{
socket = new QTcpSocket(this);
connect(socket, SIGNAL(disconnected()),
this, SLOT(httpDisconnected()));
}
QHttpSocket::~QHttpSocket()
{
}
void QHttpSocket::httpConnect()
{
socket->connectToHost("qt-dev.com", 80);
if(socket->waitForConnected(5000))
{
qDebug() << "TCP Connected.";
// send
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;
}
생성자 (QHttpSocket::QHttpSocket(...))
>
socket = new QTcpSocket(this)
: QHttpSocket 객체가 생성될 때, 내부적으로 사용할 QTcpSocket 객체를 동적으로 생성.
this를 부모 객체로 지정하여, QHttpSocket이 소멸될 때 QTcpSocket도 자동으로 함께 소멸되도록 관리.
>
connect(socket, SIGNAL(disconnected()), this, SLOT(httpDisconnected()))
: 내부 소켓인 socket의 disconnected() 시그널을 QHttpSocket의 httpDisconnected() 슬롯과 연결.
이를 통해 서버와의 연결이 끊어지면 httpDisconnected() 함수가 자동으로 호출됨.
httpConnect() 함수
>
socket->connectToHost("qt-dev.com", 80)
: QTcpSocket의 connectToHost() 함수를 호출하여 "qt-dev.com" 서버의 80번 포트로 연결을 시도.
80번 포트는 일반적인 HTTP 통신에 사용되는 기본 포트임.
>
socket->waitForConnected(5000)
: 연결이 완료될 때까지 최대 5초(5000ms) 동안 대기.
동기(blocking) 방식으로 코드를 진행시킴.
>
socket->write("Hello server\r\n\r\n")
: 연결 성공 후, 서버에 "Hello server\r\n\r\n"라는 문자열을 보냄.
\r\n\r\n은 HTTP 프로토콜에서 헤더의 끝을 나타내는 표준 구분자임.
>
socket->waitForBytesWritten(1000)
: 데이터가 전송될 때까지 최대 1초 동안 대기.
>
socket->waitForReadyRead(3000)
: 서버로부터의 응답이 도착할 때까지 최대 3초 동안 대기.
>
socket->readAll()
: 서버로부터 받은 모든 데이터를 읽어와 디버그 콘솔에 출력.
httpDisconnected() 함수
이 슬롯 함수는 연결이 끊어질 때 자동으로 호출됨.
디버그 콘솔에 Q_FUNC_INFO 매크로를 이용해 함수 이름(QHttpSocket::httpDisconnected)을 출력.
<main.cpp>
#include <QCoreApplication>
#include "qhttpsocket.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QHttpSocket *httpSocket = new QHttpSocket();
httpSocket->httpConnect();
return a.exec();
}
2.2 01_Async(비동기 방식)
<qhttpsocket.h>
#ifndef QHTTPSOCKET_H
#define QHTTPSOCKET_H
#include <QObject>
#include <QTcpSocket>
class QHttpSocket : public QObject
{
Q_OBJECT
public:
explicit QHttpSocket(QObject *parent = nullptr);
~QHttpSocket();
void httpConnect();
signals:
public slots:
void slotConnected();
void slotDisconnected();
void slotBytesWritten(qint64 bytes);
void slotReadPandingDatagram();
private:
QTcpSocket *socket;
};
#endif // QHTTPSOCKET_H
멤버 변수(Filed)
>
QTcpSocket *socket
멤버 함수(Method)
>
explicit QHttpSocket(QObject *parent = nullptr)
>
~QHttpSocket()
>
void httpConnect()
: HTTP 서버에 연결하는 로직을 담을 함수.
이 함수 내부에서 QTcpSocket의 connectToHost() 함수를 호출.
슬롯 함수(slot)
>
void slotConnected()
: QTcpSocket이 서버에 성공적으로 연결되면 호출될 슬롯.
>
void slotDisconnected()
: QTcpSocket이 서버와의 연결이 끊어졌을 때 호출될 슬롯.
>
void slotBytesWritten(qint64 bytes)
: QTcpSocket이 데이터를 서버에 성공적으로 썼을 때 호출될 슬롯.
bytes 인자는 전송된 바이트 수를 나타냄.
>
void slotReadPandingDatagram()
: QTcpSocket에 서버로부터 읽을 수 있는 데이터가 도착했을 때 호출될 슬롯.
<qhttpsocket.cpp>
#include "qhttpsocket.h"
QHttpSocket::QHttpSocket(QObject *parent)
: QObject{parent}
{
socket = new QTcpSocket(this);
connect(socket, SIGNAL(connected()),
this, SLOT(slotConnected()));
connect(socket, SIGNAL(disconnected()),
this, SLOT(slotDisconnected()));
connect(socket, SIGNAL(bytesWritten(qint64)),
this, SLOT(slotBytesWritten(qint64)));
connect(socket, SIGNAL(readyRead()),
this, SLOT(slotReadPandingDatagram()));
}
QHttpSocket::~QHttpSocket()
{
}
void QHttpSocket::httpConnect()
{
socket->connectToHost("qt-dev.com", 80);
if(!socket->waitForConnected(3000))
{
qDebug() << "Socket Error : "
<< socket->errorString();
}
}
void QHttpSocket::slotConnected()
{
qDebug("\n [%s] CONNECTED", Q_FUNC_INFO);
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();
}
생성자 (QHttpSocket::QHttpSocket(...))
>
socket = new QTcpSocket(this)
: QTcpSocket 객체를 생성하여 TCP 통신을 준비.
connect(...)
connected()
: 서버에 성공적으로 연결되면 slotConnected() 함수가 호출.
disconnected()
: 연결이 끊어지면 slotDisconnected() 함수가 호출.
bytesWritten(qint64)
: 데이터가 서버로 전송되면 slotBytesWritten() 함수가 호출.
qint64 인자로 전송된 바이트 크기를 받음.
readyRead()
: 서버로부터 읽을 데이터가 도착하면 slotReadPandingDatagram() 함수가 호출.
httpConnect() 함수
>
socket->connectToHost("qt-dev.com", 80)
: "qt-dev.com" 서버의 80번 포트로 연결을 시도.
이 함수는 즉시 반환되며, 연결 성공 여부는 connected() 시그널을 통해 나중에 통보됨.
>
if(!socket->waitForConnected(3000))
: 연결이 완료될 때까지 최대 3초 동안 대기. 연결에 실패하면 오류 메시지를 출력.
이 부분은 비동기적으로 연결을 시도하는 와중에 잠시 동기적으로 대기하는 혼합된 방식임.
slotConnected() 함수
>
socket->write("HEAD / HTTP/1.0\r\n\r\n\r\n\r\n")
: 서버에 HTTP HEAD 요청을 보냄.
HEAD는 웹 서버에게 문서의 헤더 정보만 요청하는 메서드로, 본문 없이 상태 정보만 빠르게 받아올 때 사용됨.
slotDisconnected() 함수
서버와의 연결이 끊어졌을 때 호출됨.
디버그 콘솔에 메시지를 출력.
slotBytesWritten() 함수
데이터가 성공적으로 서버에 전송되었을 때 호출됨.
전송된 데이터의 크기를 디버그 콘솔에 출력.
slotReadPandingDatagram() 함수
socket->readAll()
: 소켓에 도착한 모든 데이터를 읽어와 디버그 콘솔에 출력.
<main.cpp>
#include <QCoreApplication>
#include "qhttpsocket.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QHttpSocket *httpSocket = new QHttpSocket();
httpSocket->httpConnect();
return a.exec();
}
3. 채팅 서버/클라이언트 구현
3.1 00_ChatServer
<widget.ui>

<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
상속 및 멤버 변수
>
class ChatServer : public QTcpServer
: ChatServer 클래스가 Qt의 TCP 서버 기능인 QTcpServer를 상속받음.
이를 통해 클라이언트 연결 요청을 기다리고 수락하는 기능을 갖게 됨.
>
QSet<QTcpSocket*> clients
: 현재 서버에 연결된 모든 클라이언트 소켓의 포인터를 저장하는 컨테이너.
QSet은 중복을 허용하지 않음.
>
QMap<QTcpSocket*,QString> users
: 각 QTcpSocket 객체에 대응하는 사용자 이름을 저장하는 컨테이너.
이를 통해 어떤 소켓이 어떤 사용자인지 식별.
슬롯 함수 (slots)
>
void readyRead()
: 클라이언트 소켓에서 읽을 수 있는 데이터가 도착했을 때 호출.
메시지를 읽고 처리하는 역할.
>
void disconnected()
: 클라이언트가 연결을 끊었을 때 호출.
해당 클라이언트를 사용자 목록에서 제거하는 등의 후속 처리를 담당.
>
void sendUserList()
: 서버에 접속한 사용자 목록을 클라이언트들에게 전송하는 함수.
시그널 (signals)
>
void clients_signal(int users)
: 현재 연결된 클라이언트의 수를 알릴 때 사용되는 시그널.
>
void message_signal(QString msg)
: 서버에 도착한 메시지를 외부에 알릴 때 사용되는 시그널.
멤버 함수 (Method)
>
void incomingConnection(qintptr socketfd)
: QTcpServer의 가상 함수를 재정의한 것.
새로운 클라이언트가 서버에 연결을 요청하면 자동으로 호출됨.
socketfd는 새 연결의 소켓 디스크립터(파일 핸들)를 나타냄.
이 함수 내에서 QTcpSocket 객체를 생성하고, 슬롯을 연결하는 로직을 구현.
<chatserver.cpp>
#include "chatserver.h"
#include <QRegularExpression>
#include <QRegularExpressionMatch>
ChatServer::ChatServer(QObject *parent)
: QTcpServer(parent)
{
}
void ChatServer::incomingConnection(qintptr socketfd)
{
QTcpSocket *client = new QTcpSocket(this);
client->setSocketDescriptor(socketfd);
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())
{
QString line = QString::fromUtf8(client->readLine()).trimmed();
QString str;
str = QString("Read line: %1").arg(line);
emit message_signal(str);
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()
{
deleteLater();
}
생성자 (ChatServer::ChatServer(QObject *parent))
>
QTcpServer(parent)
: ChatServer 객체를 생성하면서 부모 클래스인 QTcpServer의 생성자를 호출해 초기화.
이 과정에서 ChatServer가 QTcpServer의 기능을 온전히 상속받게 됨.
새로운 연결 처리 (void ChatServer::incomingConnection(qintptr socketfd))
>
QTcpSocket *client = new QTcpSocket(this)
: 새로운 클라이언트와의 통신을 위해 QTcpSocket 객체를 동적으로 생성.
이 객체가 각 클라이언트와의 개별적인 연결을 관리.
>
client->setSocketDescriptor(socketfd)
: QTcpServer로부터 받은 소켓 파일 디스크립터(socketfd)를 새로 만든 QTcpSocket 객체에 할당.
이로써 소켓 객체가 실제 네트워크 연결을 제어 가능.
>
clients.insert(client)
: 서버에 연결된 모든 클라이언트 소켓의 포인터를 저장하는 **clients QSet**에 새 클라이언트 소켓을 추가.
>
connect(...)
readyRead()
: 클라이언트가 데이터를 보내면 readyRead() 슬롯 함수가 호출.
disconnected()
: 클라이언트의 연결이 끊어지면 disconnected() 슬롯 함수가 호출.
데이터 수신 및 메시지 처리 (void ChatServer::readyRead())
>
QTcpSocket *client = (QTcpSocket*)sender()
: 어떤 클라이언트 소켓이 시그널을 보냈는지 sender() 함수를 사용해 알아냄.
>
QString line = ... client->readLine()...
: 클라이언트가 보낸 메시지를 한 줄씩 읽어와 QString으로 변환.
사용자 이름 설정: line이 "/me:사용자이름" 형식인지 정규 표현식으로 검사.
>
match.hasMatch()
: 만약 형식이 일치하면, 정규 표현식으로 추출한 사용자 이름을 users **QMap**에 client 소켓과 함께 저장.
>
foreach(QTcpSocket *client, clients) ...
: 모든 클라이언트에게 새로운 사용자가 접속했음을 알리는 메시지를 보냄.
>
users.contains(client)
: 이미 사용자 이름이 설정된 클라이언트가 메시지를 보냈다면, 해당 메시지를 모든 클라이언트에게 브로드캐스트함.
연결 끊김 처리 (void ChatServer::disconnected())
>
QTcpSocket *client = (QTcpSocket*)sender()
: 연결이 끊어진 클라이언트 소켓을 식별.
>
clients.remove(client)
: **clients QSet**에서 해당 클라이언트 소켓을 제거.
>
users.remove(client)
: **users QMap**에서도 해당 클라이언트의 사용자 정보를 제거.
>
sendUserList()
: 사용자 목록이 변경되었으므로 모든 클라이언트에게 업데이트된 사용자 목록을 다시 보냄.
>
foreach(...) ... client->write(...)
: 모든 클라이언트에게 특정 사용자가 연결을 끊었다는 메시지를 전송.
<widget.h>
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "chatserver.h"
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
ChatServer *server;
private slots:
void slot_clients(int users);
void slot_message(QString msg);
};
#endif // WIDGET_H
상속 및 멤버 변수
>
class Widget : public QWidget
>
Ui::Widget *ui
>
ChatServer *server
: 위젯에 채팅 서버 객체를 담는 역할.
ChatServer 클래스는 별도의 스레드나 프로세스로 실행되지 않고, Widget과 동일한 스레드에서 서버 기능을 수행.
슬롯 함수
void slot_clients(int users)
: ChatServer의 clients_signal(int users) 시그널에 연결될 슬롯.
연결된 클라이언트 수가 변경될 때마다 호출되며, 이 함수 안에서 UI의 접속자 수 표시 부분을 업데이트하는 로직을 작성.
void slot_message(QString msg)
: ChatServer의 message_signal(QString msg) 시그널에 연결될 슬롯.
서버에 새로운 메시지가 도착하거나 이벤트가 발생할 때 호출되며, 이 함수 안에서 메시지를 UI의 텍스트 상자에 표시하는 로직을 구현.
<widget.cpp>
#include "widget.h"
#include "./ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
server = new ChatServer();
connect(server, SIGNAL(clients_signal(int)), this,
SLOT(slot_clients(int)));
connect(server, SIGNAL(message_signal(QString)), this,
SLOT(slot_message(QString)));
server->listen(QHostAddress::Any, 35000);
}
void Widget::slot_clients(int users)
{
QString str = QString("Connected Members : %1").arg(users);
ui->label->setText(str);
}
void Widget::slot_message(QString msg)
{
ui->textEdit->append(msg);
}
Widget::~Widget()
{
delete ui;
}
생성자 (Widget::Widget(...))
>
ui->setupUi(this)
>
server = new ChatServer()
: ChatServer 객체를 동적으로 생성.
실제 TCP 서버 역할을 담당.
>
connect(...)
: ChatServer의 시그널들을 Widget의 슬롯 함수에 연결.
**server**에서 clients_signal(int) 시그널이 발생하면, **Widget**의 slot_clients(int) 슬롯 함수가 호출.
이는 접속자 수 변경을 UI에 반영.
**server**에서 message_signal(QString) 시그널이 발생하면, **Widget**의 slot_message(QString) 슬롯 함수가 호출.
이는 서버에 도착한 메시지를 UI에 표시.
>
server->listen(QHostAddress::Any, 35000)
: ChatServer 객체에게 모든 네트워크 인터페이스의 35000번 포트에서 클라이언트의 연결을 기다리도록 명령.
슬롯 함수
>
slot_clients(int users)
: ChatServer로부터 받은 **현재 접속자 수(users)**를 UI의 label 위젯에 "Connected Members : n" 형식으로 표시.
>
slot_message(QString msg)
: ChatServer로부터 전달받은 **메시지(msg)**를 UI의 textEdit 위젯에 추가.
<main.cpp>
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
3.2 00_ChatClient
<loginwidget.ui>

<widget.ui>

<loginwidget.h>
#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
상속 및 멤버 변수
>
class LoginWidget : public QWidget
>
Ui::LoginWidget *ui
슬롯 함수 (slots)
>
void loginBtnClicked()
: 로그인 버튼이 클릭될 때 호출되는 슬롯 함수.
사용자가 입력한 IP 주소와 이름을 가져와 처리하는 로직을 담고 있음.
시그널 (signals)
>
void sig_loginInfo(QString addr, QString name)
: LoginWidget이 로그인 정보를 다른 클래스에 전달할 때 사용하는 시그널.
loginBtnClicked() 슬롯에서 발생(emit)하며, 연결된 다른 객체의 슬롯 함수에 사용자가 입력한 **IP 주소(addr)**와 **이름(name)**을 전달함.
<loginwidget.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;
}
생성자 (LoginWidget::LoginWidget(...))
>
ui->setupUi(this)
>
connect(ui->loginButton, &QPushButton::pressed, this, &LoginWidget::loginBtnClicked)
: UI에 있는 **loginButton**이 눌렸을 때(pressed 시그널), LoginWidget 클래스의 loginBtnClicked 슬롯 함수가 호출되도록 연결.
슬롯 함수 (void LoginWidget::loginBtnClicked())
>
QString serverIp = ui->ipLineEdit->text().trimmed();
: 사용자가 ipLineEdit 위젯에 입력한 텍스트를 가져와서 양쪽 끝의 공백을 제거하고 serverIp 변수에 저장.
>
QString name = ui->nameLineEdit->text().trimmed();
: nameLineEdit 위젯에 입력한 텍스트를 같은 방식으로 가져와 name 변수에 저장.
>
emit sig_loginInfo(serverIp, name)
: LoginWidget의 sig_loginInfo 시그널을 발생(emit)시킴.
이 시그널에 serverIp와 name 변수의 값을 매개변수로 담아 보냄.
이 시그널에 연결된 다른 객체의 슬롯 함수가 이 두 값을 전달받게 됨.
소멸자 (LoginWidget::~LoginWidget())
>
delete ui
<widget.h>
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTcpSocket>
#include "loginwidget.h"
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
LoginWidget *loginWidget;
QTcpSocket *socket;
QString ipAddr;
QString userName;
private slots:
void loginInfo(QString addr, QString name);
void sayButton_clicked();
void connected();
void readyRead();
};
#endif // WIDGET_H
상속 및 멤버 변수
>
class Widget : public QWidget
>
LoginWidget *loginWidget
: 사용자에게 로그인 정보를 입력받는 로그인 창 객체의 포인터.
>
QTcpSocket *socket
: 서버와 통신하는 데 사용되는 TCP 소켓의 포인터.
>
QString ipAddr, QString userName
: 로그인 창에서 입력받은 서버의 IP 주소와 사용자 이름을 저장하는 멤버 변수.
슬롯 함수(slot)
이 슬롯 함수들은 UI 이벤트나 소켓의 시그널에 연결되어 특정 동작을 수행.
>
void loginInfo(QString addr, QString name)
: LoginWidget의 sig_loginInfo 시그널에 연결될 슬롯.
로그인 창에서 입력받은 **IP 주소(addr)**와 **사용자 이름(name)**을 받아서 저장하고, 서버에 연결을 시도하는 로직.
>
void sayButton_clicked()
: "말하기" 버튼이 클릭될 때 호출될 슬롯.
사용자가 입력한 메시지를 서버로 전송하는 역할.
>
void connected()
: QTcpSocket이 서버에 성공적으로 연결되면 호출될 슬롯.
연결이 성공했음을 UI에 표시하거나, 사용자 이름을 서버에 보내는 등의 초기 작업을 수행.
>
void readyRead()
: QTcpSocket에 서버로부터 읽을 수 있는 데이터가 도착하면 호출될 슬롯.
서버가 보낸 메시지를 읽어 UI에 표시하는 역할.
<widget.cpp>
#include "widget.h"
#include "./ui_widget.h"
#include <QRegularExpression>
#include <QRegularExpressionMatch>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
loginWidget = new LoginWidget();
connect(loginWidget, &LoginWidget::sig_loginInfo,
this, &Widget::loginInfo);
connect(ui->sayButton, &QPushButton::pressed,
this, &Widget::sayButton_clicked);
loginWidget->show();
socket = new QTcpSocket(this);
connect(socket, SIGNAL(readyRead()),
this, SLOT(readyRead()));
connect(socket, SIGNAL(connected()),
this, SLOT(connected()));
}
void Widget::loginInfo(QString addr, QString name)
{
ipAddr = addr;
userName = name;
socket->connectToHost(ipAddr, 35000);
}
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);
if(match.hasMatch())
{
QString user = match.captured(1);
QString message = match.captured(2);
ui->roomTextEdit->append("<b>"+user+"</b>:"+message);
}
}
}
Widget::~Widget()
{
delete ui;
}
생성자 (Widget::Widget(...))
>
ui->setupUi(this)
>
loginWidget = new LoginWidget()
: 로그인 정보를 입력받을 LoginWidget 객체를 동적으로 생성.
>
connect(...)
: loginWidget의 sig_loginInfo 시그널이 발생하면, Widget의 loginInfo 슬롯 함수가 호출.
UI에 있는 sayButton이 눌리면, Widget의 sayButton_clicked 슬롯 함수가 호출.
>
loginWidget->show()
: 애플리케이션 시작 시 LoginWidget을 먼저 표시.
>
socket = new QTcpSocket(this)
: 서버와 통신할 QTcpSocket 객체를 생성.
>
connect(socket, ...)
: QTcpSocket의 readyRead() 시그널과 connected() 시그널을 Widget의 슬롯 함수에 연결.
슬롯 함수
>
loginInfo(QString addr, QString name)
: LoginWidget으로부터 받은 서버 IP 주소와 사용자 이름을 멤버 변수에 저장.
이후 **socket->connectToHost(ipAddr, 35000)**를 호출하여 서버에 연결을 시도.
>
sayButton_clicked()
: sayLineEdit 위젯에서 사용자가 입력한 메시지를 가져와 **socket->write(...)**를 통해 서버로 전송.
메시지를 보낸 후에는 sayLineEdit를 비우고 포커스를 유지.
>
connected()
loginWidget->hide()
: 로그인 창을 숨김.
this->window()->show()
: 메인 채팅 위젯을 화면에 표시.
socket->write(QString("/me:" + userName + "\n").toUtf8())
: 서버에 "/me:사용자이름" 형식의 초기 메시지를 보내 자신의 사용자 이름을 등록.
>
readyRead()
: 서버로부터 데이터가 도착하면 호출.
socket->readLine()을 통해 서버가 보낸 메시지를 한 줄씩 읽음.
정규 표현식 ^([^:]+):(.*)$을 사용하여 메시지에서 사용자 이름과 메시지 내용을 분리.
분리된 내용을 ui->roomTextEdit 위젯에 HTML 형식(<b>...</b>)으로 추가하여 표시.
<main.cpp>
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.setWindowTitle("Chat Client");
w.hide();
return a.exec();
}
'LMS 7 > 개발일지' 카테고리의 다른 글
| 25.09.05 / 제60회 전국기능경기대회 전시 작품 제작 프로젝트 2팀(안전/보안) / 3일차 (1) | 2025.11.04 |
|---|---|
| 25.08.13 / QT6 개인 프로젝트 1 (4) | 2025.08.13 |
| 25.08.10 학습개발일지 / QT6 Chapter 10 (0) | 2025.08.11 |
| 25.08.08 학습개발일지 / QT6 Chapter06, 09 (1) | 2025.08.11 |
| 25.08.07 학습개발일지 / QT6 Chapter05(2) (2) | 2025.08.11 |