이 페이지에서

블로킹 리시버

QSerialPort 의 동기식 API를 GUI가 아닌 스레드에서 사용하는 방법을 보여줍니다.

'블로킹 리시버'는 비GUI 스레드에서 QSerialPort 의 동기식 API를 사용하여 직렬 인터페이스용 애플리케이션을 만드는 방법을 보여줍니다.

Qt Serial Port 차단 수신기

QSerialPort 다음과 같은 두 가지 일반적인 프로그래밍 방식을 지원합니다:

  • 비동기(비블로킹) 방식. 작업은 제어권이 Qt의 이벤트 루프로 돌아왔을 때 스케줄링되어 수행됩니다. 작업이 완료되면 ` QSerialPort `가 신호를 발생시킵니다. 예를 들어, ` QSerialPort::write()`는 즉시 반환됩니다. 데이터가 시리얼 포트로 전송되면 ` QSerialPort `가 ` bytesWritten()` 신호를 발생시킵니다.
  • 동기식(블로킹) 방식. 비 GUI 및 멀티스레드 애플리케이션에서는 waitFor...() 함수(예: QSerialPort::waitForReadyRead())를 호출하여 작업이 완료될 때까지 호출 스레드를 일시 중지할 수 있습니다.

이 예제에서는 동기식 접근 방식을 보여줍니다. '터미널' 예제는 비동기식 접근 방식을 설명합니다.

이 예제의 목적은 사용자 인터페이스의 반응성을 저하시키지 않으면서 직렬 프로그래밍 코드를 단순화하는 데 사용할 수 있는 패턴을 보여주는 것입니다. Qt의 차단형 직렬 프로그래밍 API를 사용하면 코드가 단순해지는 경우가 많지만, 차단 동작 특성상 사용자 인터페이스가 멈추는 것을 방지하기 위해 비 GUI 스레드에서만 사용해야 합니다. 하지만 많은 사람들이 생각하는 것과는 달리, QThread 와 함께 스레드를 사용한다고 해서 반드시 애플리케이션에 관리하기 어려운 복잡성이 추가되는 것은 아닙니다.

이 애플리케이션은 ‘Blocking Sender’ 예제와 함께 작동하는 수신기(Receiver) 애플리케이션입니다.

Receiver 애플리케이션은 시리얼 포트를 통해 Sender 애플리케이션으로부터 요청을 수신하고, 이에 대한 응답을 보냅니다.

먼저 시리얼 프로그래밍 코드를 처리하는 ReceiverThread 클래스부터 살펴보겠습니다.

class ReceiverThread : public QThread
{
    Q_OBJECT

public:
    explicit ReceiverThread(QObject *parent = nullptr);
    ~ReceiverThread();

    void startReceiver(const QString &portName, int waitTimeout, const QString &response);

signals:
    void request(const QString &s);
    void error(const QString &s);
    void timeout(const QString &s);

private:
    void run() override;

    QString m_portName;
    QString m_response;
    int m_waitTimeout = 0;
    QMutex m_mutex;
    bool m_quit = false;
};

ReceiverThread는 QThread 의 하위 클래스로, Sender로부터 요청을 수신하기 위한 API를 제공하며, 응답을 전달하고 오류를 보고하기 위한 신호를 가지고 있습니다.

수신기 애플리케이션을 시작하려면 startReceiver()를 호출해야 합니다. 이 메서드는 시리얼 인터페이스를 구성하고 시작하기 위해 필요한 매개변수를 ReceiverThread로 전달합니다. ReceiverThread가 Sender로부터 요청을 수신하면 request() 신호를 발생시킵니다. 오류가 발생하면 error() 또는 timeout() 신호가 발생합니다.

startReceiver()는 메인(GUI) 스레드에서 호출되지만, 응답 데이터 및 기타 매개변수는 ReceiverThread의 스레드에서 액세스된다는 점에 유의해야 합니다. ReceiverThread의 데이터 멤버는 서로 다른 스레드에서 동시에 읽히거나 쓰이기 때문에, 액세스를 동기화하기 위해 QMutex 를 사용하는 것이 좋습니다.

void ReceiverThread::startReceiver(const QString &portName, int waitTimeout, const QString &response)
{
    const QMutexLocker locker(&m_mutex);
    m_portName = portName;
    m_waitTimeout = waitTimeout;
    m_response = response;
    if (!isRunning())
        start();
}

startReceiver() 함수는 시리얼 포트 이름, 타임아웃 및 응답 데이터를 저장하며, ` QMutexLocker `는 이러한 데이터를 보호하기 위해 뮤텍스를 잠급니다. 그런 다음 스레드가 이미 실행 중이 아닌 경우 스레드를 시작합니다. ` QWaitCondition::wakeOne()`에 대해서는 나중에 설명하겠습니다.

void ReceiverThread::run()
{
    bool currentPortNameChanged = false;

    m_mutex.lock();
    QString currentPortName;
    if (currentPortName != m_portName) {
        currentPortName = m_portName;
        currentPortNameChanged = true;
    }

    int currentWaitTimeout = m_waitTimeout;
    QString currentRespone = m_response;
    m_mutex.unlock();

run() 함수에서는 먼저 뮤텍스 잠금을 획득한 후, 멤버 데이터에서 시리얼 포트 이름, 타임아웃 및 응답 데이터를 가져온 다음, 잠금을 다시 해제합니다. 어떤 상황에서도 startReceiver() 메서드는 이러한 데이터를 가져오는 프로세스와 동시에 호출되어서는 안 됩니다. QString 는 재진입 가능하지만 스레드 안전하지는 않으며, 한 프로세스의 시작 정보에서 시리얼 포트 이름을 읽고 다른 프로세스의 호출, 타임아웃 또는 응답 데이터를 읽는 것은 권장되지 않습니다. ReceiverThread는 한 번에 하나의 시작 정보만 처리할 수 있습니다.

루프에 진입하기 전 run() 함수 내에서 스택에 생성하는 QSerialPort 객체는 다음과 같습니다:

    QSerialPort serial;

    while (!m_quit) {

이를 통해 루프 실행 중에 객체를 한 번만 생성할 수 있으며, 동시에 객체의 모든 메서드가 run() 스레드의 컨텍스트에서 실행된다는 것을 의미합니다.

루프 내에서 현재 시작 요청에 대한 시리얼 포트 이름이 변경되었는지 확인합니다. 변경된 경우, 시리얼 포트를 다시 열고 재구성합니다.

        if (currentPortName.isEmpty()) {
            emit error(tr("Port not set"));
            return;
        } else if (currentPortNameChanged) {
            serial.close();
            serial.setPortName(currentPortName);

            if (!serial.open(QIODevice::ReadWrite)) {
                emit error(tr("Can't open %1, error code %2")
                           .arg(m_portName).arg(serial.error()));
                return;
            }
        }

        if (serial.waitForReadyRead(currentWaitTimeout)) {

루프는 요청 데이터를 계속 기다립니다:

            // read request
            QByteArray requestData = serial.readAll();
            while (serial.waitForReadyRead(10))
                requestData += serial.readAll();

경고: 차단 방식의 경우, 각 read() 호출 전에 waitForReadyRead()메서드를 사용해야 합니다. 이 메서드는 Qt 이벤트 루프 대신 모든 I/O 루틴을 처리하기 때문입니다.

데이터 읽기 중에 오류가 발생하면 timeout() 신호가 발송됩니다.

        } else {
            emit timeout(tr("Wait read request timeout %1")
                         .arg(QTime::currentTime().toString()));
        }

읽기가 성공적으로 완료된 후에는 응답을 전송하고 전송이 완료될 때까지 대기합니다:

            // write response
            const QByteArray responseData = currentRespone.toUtf8();
            serial.write(responseData);
            if (serial.waitForBytesWritten(m_waitTimeout)) {
                const QString request = QString::fromUtf8(requestData);
                emit this->request(request);

경고: 차단 방식의 경우, 각 write() 호출 후에 waitForBytesWritten()메서드를 사용해야 합니다. 이 메서드는 Qt 이벤트 루프 대신 모든 I/O 루틴을 처리하기 때문입니다.

데이터를 쓰다가 오류가 발생하면 timeout() 신호가 발송됩니다.

            } else {
                emit timeout(tr("Wait write response timeout %1")
                             .arg(QTime::currentTime().toString()));
            }

쓰기가 성공적으로 완료된 후, Sender 애플리케이션에서 수신한 데이터를 포함하는 request() 신호를 발생시킵니다:

                emit this->request(request);

다음으로, 스레드는 시리얼 인터페이스의 현재 매개변수가 이미 업데이트되었을 수 있으므로 해당 매개변수를 읽는 작업으로 전환하고, 루프를 처음부터 다시 실행합니다.

        m_mutex.lock();
        if (currentPortName != m_portName) {
            currentPortName = m_portName;
            currentPortNameChanged = true;
        } else {
            currentPortNameChanged = false;
        }
        currentWaitTimeout = m_waitTimeout;
        currentRespone = m_response;
        m_mutex.unlock();
    }

예제 실행

다음 경로에서 예제를 실행할 수 있습니다:

code.qt.io의 예제 프로젝트

‘직렬 터미널 ’ 및 ‘블로킹 송신기’항목도 참조하십시오 .

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.