발신자 차단하기
작업자 스레드에서 QSerialPort 의 동기식 API를 사용하는 방법을 보여줍니다.
발신자 차단 하기는 작업자 스레드에서 QSerialPort 의 동기식 API를 사용하여 직렬 인터페이스용 애플리케이션을 만드는 방법을 보여줍니다.
QSerialPort 두 가지 프로그래밍 대안을 지원합니다:
- 비동기(비차단) 대안. 제어가 Qt 이벤트 루프로 돌아갈 때 작업이 예약되고 수행됩니다. QSerialPort 클래스는 작업이 완료되면 신호를 방출합니다. 예를 들어 write() 메서드는 즉시 반환됩니다. 데이터가 직렬 포트로 전송되면 QSerialPort 클래스는 bytesWritten() 신호를 방출합니다.
- 동기식(블로킹) 대안. 헤드리스 및 멀티스레드 애플리케이션에서는 대기 메서드(이 경우 waitForReadyRead())를 호출하여 작업이 완료될 때까지 호출 스레드를 일시 중단할 수 있습니다.
이 예에서는 동기식 대안을 보여줍니다. 터미널 예제에서는 비동기 대안을 설명합니다.
이 예의 목적은 사용자 인터페이스의 응답성을 잃지 않고 직렬 프로그래밍 코드를 간소화하는 방법을 보여주기 위한 것입니다. 블로킹 직렬 프로그래밍 API는 종종 더 간단한 코드로 이어지지만, 사용자 인터페이스의 응답성을 유지하려면 비-GUI 스레드에서만 사용해야 합니다.
이 애플리케이션은 수신자 애플리케이션인 블로킹 수신자 예제와 짝을 이루는 작업을 보여주는 발신자입니다.
발신자 애플리케이션은 직렬 포트를 통해 수신자 애플리케이션에 전송 요청을 시작하고 응답을 기다립니다.
class SenderThread : public QThread { Q_OBJECT public: explicit SenderThread(QObject *parent = nullptr); ~SenderThread(); void transaction(const QString &portName, int waitTimeout, const QString &request); signals: void response(const QString &s); void error(const QString &s); void timeout(const QString &s); private: void run() override; QString m_portName; QString m_request; int m_waitTimeout = 0; QMutex m_mutex; QWaitCondition m_cond; bool m_quit = false; };
SenderThread는 수신자에게 요청을 예약하기 위한 API를 제공하는 QThread 서브클래스입니다. 이 클래스는 응답 및 오류 보고를 위한 시그널을 제공합니다. 트랜잭션() 메서드를 호출하여 원하는 요청으로 새 발신자 트랜잭션을 시작할 수 있습니다. 결과는 response() 시그널로 제공됩니다. 문제가 발생하면 error() 또는 timeout() 신호가 전송됩니다.
트랜잭션() 메서드는 메인 스레드에서 호출되지만 요청은 SenderThread 스레드에서 제공된다는 점에 유의하세요. SenderThread 데이터 멤버는 서로 다른 스레드에서 동시에 읽기와 쓰기가 이루어지므로 QMutex 클래스를 사용하여 액세스를 동기화합니다.
void SenderThread::transaction(const QString &portName, int waitTimeout, const QString &request) { const QMutexLocker locker(&m_mutex); m_portName = portName; m_waitTimeout = waitTimeout; m_request = request; if (!isRunning()) start(); else m_cond.wakeOne(); }
트랜잭션() 메서드는 직렬 포트 이름, 시간 초과 및 요청 데이터를 저장합니다. 이 데이터를 보호하기 위해 뮤텍스는 QMutexLocker 로 잠글 수 있습니다. 그러면 스레드가 이미 실행 중이 아니라면 시작할 수 있습니다. wakeOne () 메서드는 나중에 설명합니다.
void SenderThread::run() { bool currentPortNameChanged = false; m_mutex.lock(); QString currentPortName; if (currentPortName != m_portName) { currentPortName = m_portName; currentPortNameChanged = true; } int currentWaitTimeout = m_waitTimeout; QString currentRequest = m_request; m_mutex.unlock();
run() 함수에서는 먼저 QMutex 객체를 잠근 다음 멤버 데이터를 사용하여 직렬 포트 이름, 시간 초과 및 요청 데이터를 가져옵니다. 이 작업이 완료되면 QMutex 잠금이 해제됩니다.
어떤 경우에도 transaction()
메서드는 데이터를 가져오는 프로세스와 동시에 호출되어서는 안 됩니다. QString 클래스는 재진입이 가능하지만 스레드 안전하지 않다는 점에 유의하세요. 따라서 요청 스레드에서 직렬 포트 이름을 읽고 다른 스레드에서 시간 초과 또는 데이터 요청을 하는 것은 권장하지 않습니다. SenderThread 클래스는 한 번에 하나의 요청만 처리할 수 있습니다.
QSerialPort 객체는 루프에 들어가기 전에 run() 메서드에서 스택에 구성됩니다:
QSerialPort serial; if (currentPortName.isEmpty()) { emit error(tr("No port name specified")); return; } while (!m_quit) {
따라서 루프를 실행하는 동안 객체를 생성할 수 있습니다. 또한 모든 객체 메서드가 run() 메서드의 범위 내에서 실행된다는 의미이기도 합니다.
현재 트랜잭션의 직렬 포트 이름이 변경되었는지 여부는 루프 내부에서 확인됩니다. 변경된 경우 직렬 포트가 다시 열린 다음 재구성됩니다.
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; } }
루프는 계속해서 데이터를 요청하고 직렬 포트에 쓰고 모든 데이터가 전송될 때까지 기다립니다.
// write request const QByteArray requestData = currentRequest.toUtf8(); serial.write(requestData); if (serial.waitForBytesWritten(m_waitTimeout)) {
경고: 차단 전송의 경우 write 메서드를 호출할 때마다 waitForBytesWritten() 메서드를 사용해야 합니다. 이렇게 하면 Qt 이벤트 루프 대신 모든 I/O 루틴이 처리됩니다.
데이터를 전송할 때 시간 초과 오류가 발생하면 timeout() 신호가 발생합니다.
} else { emit timeout(tr("Wait write request timeout %1") .arg(QTime::currentTime().toString())); }
요청이 성공한 후 응답 대기 기간이 있으며, 그 후 다시 읽습니다.
// read response if (serial.waitForReadyRead(currentWaitTimeout)) { QByteArray responseData = serial.readAll(); while (serial.waitForReadyRead(10)) responseData += serial.readAll(); const QString response = QString::fromUtf8(responseData); emit this->response(response);
경고: 차단 대안의 경우, 각 read() 호출 전에 waitForReadyRead() 메서드를 사용해야 합니다. 이렇게 하면 Qt 이벤트 루프 대신 모든 I/O 루틴이 처리됩니다.
데이터를 수신할 때 시간 초과 오류가 발생하면 timeout() 신호가 발생합니다.
} else { emit timeout(tr("Wait read response timeout %1") .arg(QTime::currentTime().toString())); }
트랜잭션이 성공적으로 완료되면 응답() 신호에는 수신자 애플리케이션으로부터 받은 데이터가 포함됩니다:
emit this->response(response);
그 후 스레드는 다음 트랜잭션이 나타날 때까지 절전 모드로 전환됩니다. 스레드는 깨어난 후 멤버를 사용하여 새 데이터를 읽고 처음부터 루프를 실행합니다.
m_mutex.lock(); m_cond.wait(&m_mutex); if (currentPortName != m_portName) { currentPortName = m_portName; currentPortNameChanged = true; } else { currentPortNameChanged = false; } currentWaitTimeout = m_waitTimeout; currentRequest = m_request; m_mutex.unlock(); }
예제 실행하기
에서 예제를 실행하려면 Qt Creator에서 Welcome 모드를 열고 Examples 에서 예제를 선택합니다. 자세한 내용은 예제 빌드 및 실행하기를 참조하세요.
© 2025 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.