阻塞接收器
展示了如何在非 GUI 线程中使用 QSerialPort 的同步 API。
阻塞接收器 展示了如何使用在非 GUI 线程中 QSerialPort 的同步 API 创建用于串行接口的应用程序。
QSerialPort 支持两种通用的编程方法
- 异步(非阻塞)方法。 操作会在将控制权返回 Qt 的事件循环时被安排和执行。在操作完成后,QSerialPort 会发出一个信号。例如,QSerialPort::write() 将立即返回。当数据发送到串行端口时,QSerialPort 会发出 bytesWritten()。
- 同步(阻塞)方法。 在非 GUI 和多线程应用程序中,可以调用(即 QSerialPort::waitForReadyRead())来挂起调用线程,直到操作完成。
在这个示例中,演示了同步方法。终端 示例说明了异步方法。
这个示例的目的在于展示一种你可以用来简化你的串行编程代码,同时又不牺牲用户界面响应性的模式。使用 Qt 的阻塞串行编程 API 常常会导致代码更加简洁,但由于它的阻塞行为,它应该只在非 GUI 线程中使用,以避免用户界面冻结。但与许多人认为的相反,在应用程序中使用线程(例如 QThread)并不一定会给你的应用程序添加难以管理的复杂性。
这个应用程序是一个接收器,演示了与发送应用程序 阻塞发送器示例 配合工作的功能。
接收器应用程序从发送应用程序通过串行端口接收请求,并向其发送响应。
我们将从处理串行编程代码的 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 的子类,它提供了从发送器接收请求的 API,并具有发送响应和报告错误的信号。
你应该调用 startReceiver() 来启动接收器应用程序。此方法将所需的参数传递给 ReceiverThread 以配置和启动串行接口。当 ReceiverThread 接收到来自发送器的任何请求时,将发出 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() 方法,因为它处理所有的 I/O 例程,而不是 Qt 事件循环。
如果在读取数据时发生错误,将发出 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() 方法,因为它处理所有的 I/O 例程,而不是 Qt 事件循环。
如果在写入数据时发生错误,将发出 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(); }
运行示例
要从 Qt Creator 运行示例,请打开 欢迎 模式并从 示例 中选择示例。更多信息,请访问 构建和运行示例。
© 2024 Qt 公司有限版权公司。此处包含的文档贡献归各自所有者所有。本提供的文档是根据自由软件基金会发布的 GNU 自由文档许可版本 1.3 的条款进行许可的。Qt 及相关标识是芬兰和/或全球其他国家的 Qt 公司的商标。所有其他商标均为其各自所有者的财产。