Qt 프레임워크 핵심 정리

Qt 개요

Qt는 C++ 기반의 크로스 플랫폼 애플리케이션 프레임워크로, GUI 개발을 중심으로 네트워크, 데이터베이스, 멀티미디어 등 다양한 기능을 제공한다. Windows, Linux, macOS뿐 아니라 Android, iOS, 임베디드 환경까지 동일한 코드베이스로 대응 가능하다.

QML과 위젯 방식

QML(Qt Meta-object Language)은 선언형 문법을 활용한 현대적인 UI 구현 방식으로, 터치 인터페이스와 부드러운 애니메이션에 특화되어 있다. 반면 전통적인 위젯 기반 방식은 QWidget, QMainWindow, QDialog 등의 클래스를 활용하며, 임베디드 환경에서는 불필요한 타이틀바나 상태바가 없는 QWidget을 선호한다.

프로젝트 구조와 빌드 설정

Qt Creator에서 새 프로젝트를 생성하면 .pro 파일, 헤더, 소스, UI 파일이 자동으로 구성된다. qmake 빌드 시스템은 Makefile을 생성하여 컴파일을 수행한다.

# 프로젝트 설정 예시
QT       += core gui widgets network

CONFIG   += c++17

SOURCES  += \
    main.cpp \
    mainwindow.cpp

HEADERS  += \
    mainwindow.h

FORMS    += \
    mainwindow.ui

핵심 파일 역할

  • .pro: 모듈 의존성, 빌드 션, 타겟 이름 정의
  • .ui: XML 기반의 인터페이스 설계 (Designer로 편집)
  • ui_*.h: uic 도구에 의해 자동 생성되는 UI 클래스

메인 윈도우 구현

아래는 기본적인 메인 윈도우 클래스의 구조다. Ui::MainWindow 포인터를 통해 Designer에서 설계한 UI 요소에 접근한다.

// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
};

#endif
// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}
// main.cpp
#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MainWindow w;
    w.show();
    return app.exec();
}

신호와 슬롯 메커니즘

Qt의 핵심 통신 방식인 신호-슬롯은 객체 간 느슨한 결합을 가능하게 한다. QObject::connect()를 통해 발신자의 신호와 수신자의 슬롯을 연결하며, 런타임에 동적으로 해제도 가능하다.

연결 패턴

// 기본 연결
QObject::connect(btn, &QPushButton::clicked, this, &MainWindow::handleClick);

// 람다 표현식 활용
connect(btn, &QPushButton::clicked, [=]() {
    qDebug() << "버튼 클릭됨";
});

// 신호 대 신호 연결
connect(objA, &ClassA::eventOccurred, objB, &ClassB::propagateEvent);

연결 해제

// 특정 연결만 해제
disconnect(sender, &Sender::signalName, receiver, &Receiver::slotName);

// 객체의 모든 연결 제거
obj->disconnect();

사용자 정의 신호와 슬롯

class ControlPanel : public QWidget
{
    Q_OBJECT

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

signals:
    void valueAdjusted(int newVal);
    void stateToggled(bool active);

public slots:
    void onConfirmPressed();
    void onResetPressed();

private:
    QSlider *slider;
    QPushButton *confirmBtn;
};

멀티스레딩

Qt에서 화면 갱신은 메인 스레드에서만 수행해야 하며, 백그라운드 작업은 별도의 작업자 스레드로 분리한다. 스레드 간 데이터 교환은 반드시 신호-슬롯을 통해 이루어진다.

방식 1: QThread 상속

run() 메서드를 재정의하여 작업 내용을 정의한다. 간단하지만 유연성이 떨어진다.

class DataProcessor : public QThread
{
    Q_OBJECT

protected:
    void run() override
    {
        while (!isInterruptionRequested()) {
            // 데이터 처리 로직
            QThread::msleep(100);
        }
    }
};

// 사용
DataProcessor *proc = new DataProcessor;
proc->start();

방식 2: QObject + moveToThread (권장)

작업 객체를 별도의 QThread로 이동시켜 실행한다. 여러 작업 함수를 병렬로 운영할 수 있어 유연하다.

class TaskExecutor : public QObject
{
    Q_OBJECT

public slots:
    void performHeavyTask(const QString &target);
    void performAnotherTask(int param);

signals:
    void progressUpdated(int percent);
    void taskCompleted(QVariant result);
};

// 스레드 설정
QThread *workerThread = new QThread;
TaskExecutor *executor = new TaskExecutor;

executor->moveToThread(workerThread);
connect(workerThread, &QThread::started, executor, &TaskExecutor::performHeavyTask);

workerThread->start();

자원 정리

workerThread->requestInterruption();
workerThread->quit();
if (workerThread->wait(3000)) {
    delete workerThread;
}

네트워크 프로그래밍

네트워크 모듈 사용 시 QT += network를 프로젝트 파일에 추가해야 한다.

호스트 정보 조회

#include <QHostInfo>
#include <QNetworkInterface>

QString fetchNetworkInfo()
{
    QString info = "호스트명: " + QHostInfo::localHostName() + "\n";

    for (const QNetworkInterface &iface : QNetworkInterface::allInterfaces()) {
        info += "장치: " + iface.name() + "\n";
        info += "MAC: " + iface.hardwareAddress() + "\n";

        for (const QNetworkAddressEntry &entry : iface.addressEntries()) {
            if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) {
                info += "IPv4: " + entry.ip().toString() + "\n";
                info += "넷마스크: " + entry.netmask().toString() + "\n";
            }
        }
    }
    return info;
}

TCP 통신

서버 구현

class TcpServer : public QObject
{
    Q_OBJECT

public:
    explicit TcpServer(QObject *parent = nullptr) : QObject(parent)
    {
        server = new QTcpServer(this);
        connect(server, &QTcpServer::newConnection, this, &TcpServer::acceptClient);
        server->listen(QHostAddress::Any, 8080);
    }

private slots:
    void acceptClient()
    {
        QTcpSocket *client = server->nextPendingConnection();
        connect(client, &QTcpSocket::readyRead, [this, client]() {
            QByteArray payload = client->readAll();
            // 데이터 처리
            client->write("ACK");
        });
    }

private:
    QTcpServer *server;
};

클라이언트 구현

QTcpSocket *socket = new QTcpSocket(this);
socket->connectToHost(QHostAddress("192.168.1.50"), 8080);

connect(socket, &QTcpSocket::connected, []() {
    qDebug() << "서버 연결 성공";
});

connect(socket, &QTcpSocket::readyRead, [socket]() {
    QByteArray response = socket->readAll();
    qDebug() << "수신:" << response;
});

UDP 통신

QUdpSocket *udpSocket = new QUdpSocket(this);
udpSocket->bind(QHostAddress::AnyIPv4, 9090);

// 데이터 수신
connect(udpSocket, &QUdpSocket::readyRead, [=]() {
    QByteArray datagram;
    datagram.resize(udpSocket->pendingDatagramSize());

    QHostAddress senderAddr;
    quint16 senderPort;
    udpSocket->readDatagram(datagram.data(), datagram.size(), &senderAddr, &senderPort);

    qDebug() << "발신자:" << senderAddr << "내용:" << datagram;
});

// 데이터 송신
udpSocket->writeDatagram("Hello", QHostAddress("192.168.1.100"), 9090);

멀티캐스트 참여

udpSocket->bind(QHostAddress::AnyIPv4, 5000, QUdpSocket::ShareAddress);
udpSocket->joinMulticastGroup(QHostAddress("239.255.0.1"));

실전 활용: 스마트 홈 시스템

아래는 Qt 기반 스마트 홈 프로젝트에서 적용한 아키텍처다. 여러 스레드와 통신 프로토콜을 조합하여 구현했다.

MQTT 메시지 브로커 연동

별도의 QThread 상속 클래스로 MQTT 통신을 관리하며, 토픽별로 장치 상태를 제어한다.

class MqttHandler : public QThread
{
    Q_OBJECT

public:
    void run() override
    {
        // mqtt_connect(), mqtt_subscribe() 등 호출
        // LED, 경보, 비디오 스트리밍 제어 토픽 구독
    }

signals:
    void ledStateChanged(bool on);
    void alarmTriggered();
    void streamRequested(bool start);
};

V4L2 영상 캡처

카메라 프레임을 획득하는 작업자 스레드에서 QImage를 생성하여 메인 UI로 전달한다.

class CameraCapture : public QThread
{
    Q_OBJECT

public:
    void run() override
    {
        // v4l2_init(), mmap 설정
        while (running) {
            // 프레임 획득
            QImage frame = convertToImage(buffer);
            emit frameReady(frame);

            if (udpBroadcastEnabled) {
                // UDP로 실시간 전송
            }
        }
    }

signals:
    void frameReady(const QImage &img);
};

파일 시스템 감시 연동

QFileSystemWatcher로 장치 상태 파일을 모니터링하고, 변경 시 MQTT 메시지를 발행한다.

class DeviceMonitor : public QObject
{
    Q_OBJECT

public:
    explicit DeviceMonitor(QObject *parent = nullptr) : QObject(parent)
    {
        watcher = new QFileSystemWatcher(this);
        watcher->addPath("/sys/class/leds/status");
        watcher->addPath("/sys/class/beep/status");

        connect(watcher, &QFileSystemWatcher::fileChanged,
                this, &DeviceMonitor::notifyStateChange);
    }

private slots:
    void notifyStateChange(const QString &path)
    {
        // MQTT 발행, UI 갱신
    }

private:
    QFileSystemWatcher *watcher;
};

I2C 센서 기반 자동 조명

거리 센서(AP3216C) 값을 읽어 임계값 이하일 때 LED를 자동으로 켜는 기능을 QObject 기반으로 구현했다.

class ProximityController : public QObject
{
    Q_OBJECT

public:
    ProximityController();
    ~ProximityController();

public slots:
    void monitorLoop();

private:
    int sensorFd;
    unsigned short threshold;
    bool ledActive;
};

// 메인에서 스레드 연결
QThread *sensorThread = new QThread;
ProximityController *controller = new ProximityController;
controller->moveToThread(sensorThread);

connect(sensorThread, &QThread::started, controller, &ProximityController::monitorLoop);
sensorThread->start();

태그: Qt C++ QThread QTcpSocket QUdpSocket

6월 5일 17:48에 게시됨