25.08.11 학습개발일지 / QT6 Chapter26, 28

2025. 8. 13. 20:48·LMS 7/개발일지

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
'LMS 7/개발일지' 카테고리의 다른 글
  • 25.09.05 / 제60회 전국기능경기대회 전시 작품 제작 프로젝트 2팀(안전/보안) / 3일차
  • 25.08.13 / QT6 개인 프로젝트 1
  • 25.08.10 학습개발일지 / QT6 Chapter 10
  • 25.08.08 학습개발일지 / QT6 Chapter06, 09
m_Dev
m_Dev
  • m_Dev
    m_Dev
    m_Dev
  • 전체
    오늘
    어제
    • 분류 전체보기
      • MAIN STUDY
        • 정보보안
        • 빅데이터
        • 정보처리
        • 컴퓨터 구조
        • 기타
      • JOB
        • Study
        • Project
      • LMS 7
        • 개발일지
      • FRAMEWORK
        • Qt
        • MFC
        • Winform
        • WPF
        • MAUI
      • NETWORK
        • Study
        • Assignment
      • PYTHON
        • Set
        • Study
        • Assignment
        • Project
      • C
        • Set
        • Study
        • Assignment
        • Project
      • C++
        • Set
        • Study
        • Assignment
        • Project
      • C#
        • Set
        • Study
        • Assignment
        • Project
      • DATABASE
        • MySQL
        • SQLite
      • IDE
        • VisualStudioCode
        • VisualStudio
        • Pycharm
        • Colab
      • 기타
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
m_Dev
25.08.11 학습개발일지 / QT6 Chapter26, 28
상단으로

티스토리툴바