qt 串口多线程 的一个例子

QSerialPort只能在实例化的线程调用,如果跨线程调用QSerialPort,则会报错,类似于Socket notifiers cannot be enabled or disabled from another thread。

为什么QSerialPort,QTcpSokcet,QModbusClient等等IO类会出现跨线程报错呢,因为当他们被实例化的时候,它们就处于实例化它们的事件循环里了,如果跨线程,也就是是跨事件循环是不被允许的。

一个QSerialPort,QTcpSokcet,QModbusClient等等IO类被motothread另外一个线程了,相当于处于它处于另外一个事件循环里。

QT有2种方法可以跨线程调用对象,一种是使用信号槽方式,一种使用QMetaObject::invokeMethod方式。

本文的例子使用的是QMetaObject::invokeMethod的方式,当然也可以改成使用信号槽的方式。

qt 串口多线程 的一个例子_第1张图片

 下面是写数据的同步异步调用的代码,onWriteData是调用对象的槽函数,异步ASync使用的是Qt::QueuedConnection方法,同步Sync使用的是 Qt::BlockingQueuedConnection。Qt::BlockingQueuedConnection的作用是阻塞发送方的运行直到接收方执行完毕。

bool	SerialportCtrl::write_data(QString connName, QString strSend, bool isHex, int msTimeOut, bool bAsync)
{

	bool ret = true;
	if (bAsync)
	{
		QMetaObject::invokeMethod(m_mapSerialPort[connName], "onWriteData", Qt::QueuedConnection,
			Q_ARG(QString, connName),
			Q_ARG(QString, strSend),
			Q_ARG(bool, isHex),
			Q_ARG(int, msTimeOut),
			Q_ARG(bool, bAsync));
	
	}else
	{


		QMetaObject::invokeMethod(m_mapSerialPort[connName], "onWriteData", Qt::BlockingQueuedConnection,
			Q_RETURN_ARG(bool, ret),
			Q_ARG(QString, connName),
			Q_ARG(QString, strSend),
			Q_ARG(bool, isHex),
			Q_ARG(int, msTimeOut),
			Q_ARG(bool, bAsync));
		
		
	}
	
	return ret;
}

TSerialPort.h


/*************************************************
*File name :NetWorkCtrl
*Author :luo jinqu
*Version :1.0.1
*Date : 2022.06.07
*Description : 继承QSerialPort实现的串口通讯类TSerialPort

*History :
*1. Date :
*Author :
*Modification :
*************************************************/
#pragma once

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "./include/Common.h"
#include "./System/SystemConfig.h"
using namespace Common;

class TSerialPort : public QSerialPort
{
	Q_OBJECT

public:
	TSerialPort(QObject *parent = Q_NULLPTR);
	~TSerialPort();
signals :
	void			sig_finished	();
	void			sig_recvMsg		(QString name, QString strRecv);
private slots:
	void			onErrorOccurred	(QSerialPort::SerialPortError error);
	void			onReadyRead		();

public :
	bool			isOpened();
	void			setSerialInfo	(TSeriaPortInfo info);
	void			setRecvHexData(bool bHexData = false) { m_bRecvHexData = bHexData; }; 
public slots:
	bool			onOpenPort		(QString connName);   
	void			onClosePort		(QString connName);
	bool			onWriteData		(QString connName,QString strSend,bool isHex=false, int msTimeout = 3000, bool bAsync = true);
	bool			onReadData		(QString connName,QString& strRecv,int msTimeout = 3000);
	bool			onWriteReadData	(QString connName,QString strSend, QString& strRecv, bool isHex = false, int msTimeout = 3000);
private:
	bool			wait_result_sync(int msTimeout = 3000);  //等待同步结果的返回
private:
	TSeriaPortInfo	m_SerialInfo;
	QMutex*			m_pRecursiveMutex	= nullptr;
	QThread*		m_pThread			= nullptr;
private:	
	bool			m_bRecvHexData = false;
	QString			m_temporayRecvData;
	QString			m_strRecv;
	QSharedPointer		m_recevieTimer = NULL;
private slots:
	void			on_receive_allData();
private:	
	//将qstring转16进制
	QByteArray		QStringHexToByteArrayHex(QString src);

};

TSerialPort.cpp

#include "TSerialPort.h"
#include 
#include


TSerialPort::TSerialPort(QObject *parent)
	:QSerialPort(parent)
{
	connect(this, &QSerialPort::readyRead, this, &TSerialPort::onReadyRead);
	connect(this, &QSerialPort::errorOccurred, this, &TSerialPort::onErrorOccurred);

	m_pRecursiveMutex = new QMutex(QMutex::Recursive);

	m_pThread = new QThread;
	this->moveToThread(m_pThread);
	m_pThread->start();
}

TSerialPort::~TSerialPort()
{

}

bool	TSerialPort::isOpened()
{
	return this->isOpen();
}

void	TSerialPort::setSerialInfo(TSeriaPortInfo info)
{
	m_SerialInfo = info;
}

bool	TSerialPort::onOpenPort(QString connName)
{
	QMutexLocker locker(m_pRecursiveMutex);

	if (connName != m_SerialInfo.name) return false;


	QSerialPort::BaudRate	_baudrate;
	QSerialPort::Parity	    _Parity;
	QSerialPort::DataBits	_databits;
	QSerialPort::StopBits	_stopbits;

	_baudrate = (QSerialPort::BaudRate)m_SerialInfo.baudrate;
	_databits = (QSerialPort::DataBits)m_SerialInfo.databits;

	QMetaEnum metaParity = QMetaEnum::fromType();
	_Parity = (QSerialPort::Parity) metaParity.keyToValue(m_SerialInfo.parity.toStdString().c_str());

	if (m_SerialInfo.stopbits == "1")
	{
		_stopbits = QSerialPort::StopBits::OneStop;
	}
	else if (m_SerialInfo.stopbits == "1.5")
	{
		_stopbits = QSerialPort::StopBits::OneAndHalfStop;
	}
	else if (m_SerialInfo.stopbits == "2")
	{
		_stopbits = QSerialPort::StopBits::TwoStop;
	}
	else
	{
		_stopbits = QSerialPort::StopBits::UnknownStopBits;
	}

	this->setPortName(m_SerialInfo.comNO);

	if (this->open(QIODevice::ReadWrite))   
	{
		this->setReadBufferSize(1024);
		if (!this->setBaudRate(_baudrate))		return false;
		if (!this->setDataBits(_databits))		return false;
		if (!this->setParity(_Parity))			return false;
		if (!this->setStopBits(_stopbits))		return false;
		if (!this->setFlowControl(QSerialPort::NoFlowControl)) 	return false;

		this->setFlowControl(QSerialPort::NoFlowControl);     //设置流控制

		qInfo() << QStringLiteral("串口[%1],端口[%2],波特率[%3],数据位[%4],校验位[%5],停止位[%6] 打开成功")
			.arg(m_SerialInfo.name)
			.arg(m_SerialInfo.comNO)
			.arg(m_SerialInfo.baudrate)
			.arg(m_SerialInfo.databits)
			.arg(m_SerialInfo.parity)
			.arg(m_SerialInfo.stopbits);

		return true;

	}

	return false;

						
}
void	TSerialPort::onClosePort(QString connName)
{
	QMutexLocker locker(m_pRecursiveMutex);
	if (connName != m_SerialInfo.name) return ;

	if (isOpened())
	{
		this->clear();
		this->close();
	}
}

bool	TSerialPort::onWriteData(QString connName, QString strSend, bool isHex, int msTimeout, bool bAsync)
{
	QMutexLocker locker(m_pRecursiveMutex);

	if (connName != m_SerialInfo.name) return false;
	this->readAll();	//发送之前,清空接收缓冲

	if (isHex)
	{
		QByteArray hexBuf = QStringHexToByteArrayHex(strSend);
		this->write(hexBuf);
	}
	else
	{	
		
		QByteArray sendByteArray = strSend.toLatin1();		//必须分开写
		char* data = new char[sendByteArray.size() + 1];
		strcpy_s(data, sendByteArray.size() + 1, sendByteArray.data());

		this->write(data);
		
		FREE_ANY(data);
	}
	bool	bWriteSucceed = true;
	if (!bAsync)
	{
		bWriteSucceed = this->waitForBytesWritten(msTimeout);
	}
	return bWriteSucceed;
}

bool	TSerialPort::onReadData(QString connName, QString& strRecv, int msTimeout)
{
	QMutexLocker locker(m_pRecursiveMutex);
	if (connName != m_SerialInfo.name) return false;

	bool rtn = true;
	rtn = wait_result_sync(msTimeout);
	if (rtn)
	{
		strRecv = m_strRecv;
		m_strRecv.clear();
	}

	if (strRecv.isEmpty())
	{
		return false;
	}
	return true;

}
bool	TSerialPort::onWriteReadData(QString connName, QString strSend, QString& strRecv, bool isHex, int msTimeout)
{
	//异步写入,同步接收
	onWriteData(connName, strSend, isHex, msTimeout, true);
	return onReadData(connName, strRecv, msTimeout);
}
void	TSerialPort::onReadyRead()
{
	m_recevieTimer.reset(new QTimer);
	connect(m_recevieTimer.data(), &QTimer::timeout, this, &TSerialPort::on_receive_allData);
	m_recevieTimer.data()->start(500);
	QByteArray  byteArray = this->readAll();

	if (m_bRecvHexData)
	{
		QString hexString = byteArray.toHex();
		m_temporayRecvData.append(hexString);
	}
	else
	{
		m_temporayRecvData.append(QString(byteArray));
	}

}

void	TSerialPort::on_receive_allData()
{
	m_recevieTimer.data()->stop();
	m_strRecv = m_temporayRecvData;
	m_temporayRecvData.clear();
	emit	sig_finished();
	emit	sig_recvMsg(m_SerialInfo.name, m_strRecv);
}

bool	TSerialPort::wait_result_sync(int msTimeout)
{
	bool ret = false;
	QTimer timer;
	QEventLoop eventLoop;
	connect(&timer, &QTimer::timeout, &eventLoop, &QEventLoop::quit, Qt::DirectConnection);
	connect(this, &TSerialPort::sig_finished, &eventLoop, &QEventLoop::quit, Qt::DirectConnection);

	timer.start(msTimeout);
	eventLoop.exec();
	if (!timer.isActive())
	{
		qCritical() << QStringLiteral("串口[%1],端口[%2],波特率[%3],数据位[%4],校验位[%5],停止位[%6] 超时")
			.arg(m_SerialInfo.name)
			.arg(m_SerialInfo.comNO)
			.arg(m_SerialInfo.baudrate)
			.arg(m_SerialInfo.databits)
			.arg(m_SerialInfo.parity)
			.arg(m_SerialInfo.stopbits);
		ret = false;
	}
	else
	{
		timer.stop();
		ret = true;
	}

	return ret;
}

void	TSerialPort::onErrorOccurred(QSerialPort::SerialPortError error)
{

	if (error != QSerialPort::NoError)
	{

		qWarning() << QStringLiteral("串口[%1],端口[%2],波特率[%3],数据位[%4],校验位[%5],停止位[%6] 发生错误:%7")
			.arg(m_SerialInfo.name)
			.arg(m_SerialInfo.comNO)
			.arg(m_SerialInfo.baudrate)
			.arg(m_SerialInfo.databits)
			.arg(m_SerialInfo.parity)
			.arg(m_SerialInfo.stopbits)
			.arg(this->errorString());


	}

}

QByteArray	TSerialPort::QStringHexToByteArrayHex(QString src)
{
	return QByteArray::fromHex(src.toLatin1());
}

下面是SerialportCtrl类,用于管理所有的串口类的。

/*************************************************
*File name :NetWorkCtrl
*Author :luo jinqu
*Version :1.0.1
*Date : 2022.06.17
*Description : 
SerialportCtrl为一个单例类,SerialportCtrl类使用QMap管理多个TSerialPort类,使用一个QString作为TSerialPort的标识,此QString是TSeriaPortInfo的name.
SerialportCtrl提供了串口读写和连接的同步异步函数,函数中使用QMetaObject::invokeMethod 来进行同步异步线程调用
参数Qt::DirectConnection 同一线程ID下同步
参数Qt::QueuedConnection 线程ID下异步
参数Qt::BlockingQueuedConnection 不同线程ID下的同步.信号和槽函数不能处于同一线程,否则死锁。
槽函数会在槽函数所处线程执行,但信号线程阻塞,等待槽函数执行完毕。

注意:QT所有的IO类都不能在不同的线程中调用,否则会报错Socket notifiers cannot be enabled or disabled from another thread。
所以用QMetaObject::invokeMethod进行跨线程调用TSerialPort
*History :
*1. Date :
*Author :
*Modification :
*************************************************/



#pragma once

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "./System/SystemConfig.h"
#include "./include/Common.h"
using namespace Common;

#include"TSerialPort.h"


/*单例模式,返回SerialportCtrl的唯一静态指针*/
#define  SERIALPORTCTRL_INSTANCE SerialportCtrl::instance()
class  SerialportCtrl : public QObject
{
	Q_OBJECT

public:
	static SerialportCtrl* instance();
	~SerialportCtrl();
private:
	static	std::once_flag m_initFlag;
	static	std::unique_ptr m_pInstance;
	SerialportCtrl(QObject *parent = nullptr);

signals:
	void    sig_recvSerialMsg(QString name, QString msg);
public slots:
	void	initWork();
	void    exitWork();
public:

	bool	isOpened		(QString connName);
	bool	open_port		(QString connName, bool bAsync=true);
	void	close_port		(QString connName);
	bool	write_data		(QString connName, QString strSend, bool isHex = false, int msTimeOut = 3000, bool bAsync = true);
	bool	read_data		(QString connName, QString& strRecv, int msTimeOut = 3000);
	bool	write_read_data	(QString connName, QString strSend, QString& strRecv, bool isHex = false, int msTimeOut = 3000);
	void	setRecvHexData	(QString connName, bool bHexData = false);

	
private:
	QVector	m_vecSerialInfo;
	QMap	m_mapSerialPort;
	bool	init_open_serial(TSeriaPortInfo info);

};

#include "SerialportCtrl.h"
#include 

std::once_flag  SerialportCtrl::m_initFlag;
std::unique_ptr SerialportCtrl::m_pInstance = nullptr;

SerialportCtrl::SerialportCtrl(QObject* parent)
	:QObject(parent)
{

}
SerialportCtrl::~SerialportCtrl()
{

}
SerialportCtrl* SerialportCtrl::instance()
{
	std::call_once(m_initFlag, [&]() {m_pInstance = std::unique_ptr(new SerialportCtrl()); });
	return m_pInstance.get();
}
void	SerialportCtrl::initWork()
{
	SYSTEM_CONFIG_INSTANCE->getSerialPortInfo(m_vecSerialInfo);

	for (int i = 0; i < m_vecSerialInfo.size(); i++)
	{
		init_open_serial(m_vecSerialInfo[i]);
	}
}
bool	SerialportCtrl::init_open_serial(TSeriaPortInfo info)
{
	TSerialPort* pSerialPort = new TSerialPort;
	connect(pSerialPort, &TSerialPort::sig_recvMsg, this, &SerialportCtrl::sig_recvSerialMsg);
	pSerialPort->setSerialInfo(info);
	m_mapSerialPort.insert(info.name, pSerialPort);
	return open_port(info.name);
}

bool	SerialportCtrl::isOpened(QString connName)
{
	if (m_mapSerialPort.contains(connName))
	{
		return m_mapSerialPort[connName]->isOpened();
	}
	else
	{
		return false;
	}
}

bool	SerialportCtrl::open_port(QString connName, bool bAsync)
{
	SYSTEM_CONFIG_INSTANCE->getSerialPortInfo(m_vecSerialInfo);

	TSeriaPortInfo serialInfo;
	bool isExist = false;

	for each (TSeriaPortInfo info in m_vecSerialInfo)
	{
		if (connName == info.name)
		{
			serialInfo = info;
			isExist = true;
			break;
		}
	}
	if (!isExist)
	{
		qWarning() << QStringLiteral("串口配置中不存在[%1]对象,无法重连").arg(connName);
		return false;
	}

	bool ret = false;

	if (!m_mapSerialPort.contains(connName))
	{
		ret = init_open_serial(serialInfo);
		return false;
	}

	m_mapSerialPort[connName]->setSerialInfo(serialInfo);

	if (bAsync)
	{
		QMetaObject::invokeMethod(m_mapSerialPort[connName], "onOpenPort", Qt::QueuedConnection,
			 Q_ARG(QString, connName));
	}
	else
	{
		QMetaObject::invokeMethod(m_mapSerialPort[connName], "onOpenPort", Qt::BlockingQueuedConnection,
			Q_RETURN_ARG(bool, ret), Q_ARG(QString, connName));
	}

	return ret;
}

void	SerialportCtrl::close_port(QString connName)
{
	QMetaObject::invokeMethod(m_mapSerialPort[connName], "onClosePort", Qt::QueuedConnection, Q_ARG(QString, connName));
}

bool	SerialportCtrl::write_data(QString connName, QString strSend, bool isHex, int msTimeOut, bool bAsync)
{

	bool ret = true;
	if (bAsync)
	{
		QMetaObject::invokeMethod(m_mapSerialPort[connName], "onWriteData", Qt::QueuedConnection,
			Q_ARG(QString, connName),
			Q_ARG(QString, strSend),
			Q_ARG(bool, isHex),
			Q_ARG(int, msTimeOut),
			Q_ARG(bool, bAsync));
	
	}else
	{


		QMetaObject::invokeMethod(m_mapSerialPort[connName], "onWriteData", Qt::BlockingQueuedConnection,
			Q_RETURN_ARG(bool, ret),
			Q_ARG(QString, connName),
			Q_ARG(QString, strSend),
			Q_ARG(bool, isHex),
			Q_ARG(int, msTimeOut),
			Q_ARG(bool, bAsync));
		
		
	}
	
	return ret;
}

bool	SerialportCtrl::read_data(QString connName, QString& strRecv,int msTimeOut)
{
	bool ret = false;
	QMetaObject::invokeMethod(m_mapSerialPort[connName], "onReadData", Qt::BlockingQueuedConnection,
		Q_RETURN_ARG(bool, ret),
		Q_ARG(QString, connName),
		Q_ARG(QString&, strRecv),
		Q_ARG(int, msTimeOut));
	
	return ret;
}

bool	SerialportCtrl::write_read_data(QString connName, QString strSend, QString& strRecv, bool isHex, int msTimeOut)
{
	bool ret = false;

	QMetaObject::invokeMethod(m_mapSerialPort[connName], "onWriteReadData", Qt::BlockingQueuedConnection,
		Q_RETURN_ARG(bool, ret),
		Q_ARG(QString, connName),
		Q_ARG(QString, strSend),
		Q_ARG(QString&, strRecv),
		Q_ARG(bool, isHex),
		Q_ARG(int, msTimeOut));
	
	return ret;
}

void	SerialportCtrl::setRecvHexData(QString connName, bool bHexData)
{
	m_mapSerialPort[connName]->setRecvHexData(bHexData);
}

void	SerialportCtrl::exitWork()
{

	QMap::const_iterator iter;  	
	for (iter = m_mapSerialPort.constBegin(); iter != m_mapSerialPort.constEnd(); iter++)
	{
		close_port(iter.key());
	}

}

 

例子程序下载地址,没有设置需要积分下载。如果下载需要有积分,不是我干的。

这是一个串口多线程的一个模块-C++文档类资源-CSDN下载这是一个串口多线程的一个模块更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/weixin_41516902/86272213

下面是一个上位机软件,有兴趣可以看一下

mobius_upper_computer_open: 用Qt写的上位机软件。主要功能是收集PLC信息,处理后发送给MES系统,或者将MES系统的一些消息转给PLC。支持多线程下的流程程序编辑。 (gitee.com)

你可能感兴趣的:(Qt,qt,c++)