文章目录
- 0 背景
- 1 原理
- 1.1 QThread的线程归属
- 1.2 Qtimer使用
- 1.3 TCP客户端使用
- 2 问题解决
- 2.1 解决思路
- 2.2 解决方法
- 3 完整的代码示例
- 3.1 tcp_client类
- 3.2 主界面类
- 附录
- 参考
0 背景
在子线程中,使用Qtimer来进行定时重连TCP服务器,总是会出现跨线程创建和使用问题。
具体问题如下:
上面的问题归结起来就是:
1,不能为属于不同线程的父对象创建子对象;
2,不能跨线程关闭和开启Qtimer。
1 原理
1.1 QThread的线程归属
我们使用如下代码测试,线程中的归属问题:
线程类的测试代码:
class DealTcpClientThreadOne : public QThread{Q_OBJECTpublic:DealTcpClientThreadOne(){qDebug() << "DealTcpClientThreadOne: " << QThread::currentThread();}void run() override{qDebug() << "DealTcpClientThreadOne::run, thread = " << QThread::currentThread();qDebug() << "DealTcpClientThreadOne::run, ThreadTest2 thread = " << this->thread();}
};
测试代码:
int main(int argc, char *argv[])
{QGuiApplication app(argc, argv);DealTcpClientThreadOne thread2;thread2.start();return app.exec();
}
测试的的结果为:
DealTcpClientThreadOne: QThread(0x600003bf01b0)
DealTcpClientThreadOne::run, thread = DealTcpClientThreadOne(0x7ff7bdec8748)
DealTcpClientThreadOne::run, ThreadTest2 thread = QThread(0x600003bf01b0)
可以看出Qthread的构造函数和run函数的运行的线程是不一样,不属于同一个。
1.2 Qtimer使用
QTimer特点:
1,不能跨线程启动和停止定时器。
2,不能在一个线程中启动定时器关联的对象,而在另一个线程释放(析构)此定时器关联的对象。
原因:定时器相关的逻辑和数据结构与线程关联,只能在同一个线程中。
Timer的任务超时,会把定时阻塞。
1.3 TCP客户端使用
主界面文件//头文件QThread m_dealTcpClientThreadOne;TcpClient* m_tcpClientOne//cpp文件QString ip = "192.168.0.222";int port = 10000;//客户端1m_tcpClientOne = new TcpClient(this, ip, port);m_tcpClientOne->moveToThread(&m_dealTcpClientThreadOne);//在主线程中创建connect(this, &MainInterface::startClientConnectTcpServer,m_tcpClientOne, &TcpClient::initializeFunction);//线程销毁connect(&m_dealTcpClientThreadOne, &QThread::finished,this, &TcpClient::deleteLater);//客户端收到的数据connect(m_tcpClientOne, &TcpClient::receiveData,this, [](QByteArray data){qDebug()<<"收到的数据:"<<data;});//客户端发送数据到服务器connect(this, &MainInterface::sendData2TcpServer,m_tcpClientOne, &TcpClient::sendData);//开启线程m_dealTcpClientThreadOne.start();//开启客户端重连计时emit startClientConnectTcpServer();
运行结果【完整运行代码,见末尾】:
DealTcpClientThreadOne() QThread(0xa082b0)
initializeClient() QThread(0xa082b0)
TcpClient::TcpClient QThread(0xa082b0)
initializeFunction() DealTcpClientThreadOne(0x8ffd48)
startClientReconnectTcpServerTimer: DealTcpClientThreadOne(0x8ffd48)
从上面代码中,我们可以得出m_tcpClientOne
【TcpClient类】和m_dealTcpClientThreadOne
【Qthread类】的构造函数在一个线程A中,经过moveToThread
【Qthread类】后,运行到了m_dealTcpClientThreadOne的run函数中的线程B中。
2 问题解决
2.1 解决思路
经过上面的分析,我们知道TcpClient类的m_tcpClientOne
对象和Qthread类的m_dealTcpClientThreadOne
对象的构造函数都属于主线程,而m_tcpClientOne
对象功能函数(收、发服务器数据的函数)都是运行在Qthread类的run函数的线程中。
因此为了让套接字和QTimer都归属于同一个线程,我们使用信号和槽函数,让m_tcpClientOne
对象中成员QTcpSocket
类和QTimer
类的对象在线程运行起来(m_dealTcpClientThreadOne.start();
)后再创建。
2.2 解决方法
关键代码为:
在主界面类MainInterface中构建信号和槽函数:
//connect(this, &MainInterface::startClientConnectTcpServer,m_tcpClientOne, &TcpClient::initializeFunction);
在客户端类TcpClient中创建套接字QTcpSocket
类和计时器QTimer
类:
void TcpClient::initializeFunction()
{qDebug()<<"initializeFunction()"<< QThread::currentThread();m_tcpSocket = new QTcpSocket();startClientReconnectTcpServerTimer();
//...}void TcpClient::startClientReconnectTcpServerTimer()
{qDebug() << "startClientReconnectTcpServerTimer: " << QThread::currentThread();//如果不带this,则需要自己销毁m_reconnectTimer = new QTimer(this);connect(m_reconnectTimer, &QTimer::timeout,this, &TcpClient::reConnectTcpServer);m_reconnectTimer->start(m_refreshTime);}
运行后的结果,如下图所示(都在同一个线程中运行):
3 完整的代码示例
3.1 tcp_client类
h文件:
#ifndef TCP_CLIENT_H
#define TCP_CLIENT_H#include <QObject>
#include <QTcpSocket>
#include <QByteArray>
#include <QTimer>class TcpClient : public QObject
{Q_OBJECT
public:TcpClient(QObject *object, QString ip, int port);~TcpClient();private:/**QAbstractSocket::UnconnectedState:未连接状态QAbstractSocket::HostLookupState:正在解析主机名QAbstractSocket::ConnectingState:正在尝试连接QAbstractSocket::ConnectedState:已连接QAbstractSocket::BoundState:已绑定QAbstractSocket::ClosingState:正在关闭QAbstractSocket::ListeningState:正在监听*/enum SocketState {UnconnectedState=0,HostLookupState=1,ConnectingState=2,ConnectedState=3,BoundState=4,ListeningState=5,ClosingState=6};QObject* m_object;QTcpSocket* m_tcpSocket;QByteArray m_receiveData;//定时重连服务器QTimer* m_reconnectTimer;//重连计时int m_refreshTime = 3000;QString m_ip;int m_port;public slots:/*** @brief initializeFunction:创建套接字、计时器、功能槽函数*/void initializeFunction();/*** @brief getConnectStatus:获得连接状态* @return*/int getConnectStatus();/*** @brief startClientReconnectTcpServerTimer:开始重连计时器*/void startClientReconnectTcpServerTimer();/*** @brief reConnectTcpServer:重连服务器* @return*/bool reConnectTcpServer();/*** @brief sendData:发送有数据给服务器* @param data*/void sendData(QByteArray data);
signals:void receiveData(QByteArray data);void startReConnectTcpServerTimer();void stopReConnectTcpServerTimer();
};
#endif // TCP_CLIENT_H
cpp类:
#include "tcp_client.h"#include <QThread>TcpClient::TcpClient(QObject* object, QString ip, int port)
{qRegisterMetaType<QAbstractSocket::SocketError>("QAbstractSocket::SocketError");qDebug()<<"TcpClient::TcpClient"<< QThread::currentThread();m_ip = ip;m_port = port;m_object = object;}void TcpClient::initializeFunction()
{qDebug()<<"initializeFunction()"<< QThread::currentThread();m_tcpSocket = new QTcpSocket();startClientReconnectTcpServerTimer();m_tcpSocket->connectToHost(m_ip, m_port);//成功连接connect(m_tcpSocket, &QTcpSocket::connected, this, [=]() {//发送上电信息QByteArray data;data += 0x02;m_tcpSocket->write(data);});//断开连接connect(m_tcpSocket, &QTcpSocket::disconnected,this, &TcpClient::reConnectTcpServer);//接受到服务器信息connect(m_tcpSocket, &QTcpSocket::readyRead, this, [&](){while (m_tcpSocket->bytesAvailable() > 0){m_receiveData = m_tcpSocket->readAll();qDebug()<<"收到的数据:"<<m_receiveData.toHex().toUpper();}emit receiveData(m_receiveData);});}TcpClient::~TcpClient()
{//自己销毁// m_reconnectTimer->disconnect();// m_reconnectTimer->deleteLater();// 结束连接m_tcpSocket->disconnectFromHost();m_tcpSocket->deleteLater() ;
}void TcpClient::sendData(QByteArray data){m_tcpSocket->write(data);
}void TcpClient::startClientReconnectTcpServerTimer()
{qDebug() << "startClientReconnectTcpServerTimer: " << QThread::currentThread();//如果不带this,则需要自己销毁m_reconnectTimer = new QTimer(this);connect(m_reconnectTimer, &QTimer::timeout,this, &TcpClient::reConnectTcpServer);m_reconnectTimer->start(m_refreshTime);}//重新连接服务器
bool TcpClient::reConnectTcpServer()
{qDebug() << "reConnectTcpServer: " << QThread::currentThread();if(m_tcpSocket->state() != QAbstractSocket::ConnectedState){m_tcpSocket->abort();m_tcpSocket->connectToHost(m_ip, m_port);if(m_tcpSocket->waitForConnected(2000)){qDebug() << "成功连接服务器:"<<m_tcpSocket->state();m_reconnectTimer->stop();return false;}else{qDebug() << "失败连接服务器:"<<m_tcpSocket->state();m_reconnectTimer->start(m_refreshTime);return true;}}else{return false;}
}int TcpClient::getConnectStatus()
{return m_tcpSocket->state();
}
3.2 主界面类
#ifndef MAIN_INTERFACE_H
#define MAIN_INTERFACE_H#include <QMainWindow>#include <QThread>
#include "tcp_client.h"QT_BEGIN_NAMESPACE
namespace Ui {
class MainInterface;
}
QT_END_NAMESPACEclass DealTcpClientThreadOne : public QThread{Q_OBJECTpublic:DealTcpClientThreadOne(){qDebug() << "DealTcpClientThreadOne() " << QThread::currentThread();}
};class MainInterface : public QMainWindow
{Q_OBJECTpublic:MainInterface(QWidget *parent = nullptr);~MainInterface();private:Ui::MainInterface *ui;private:DealTcpClientThreadOne m_dealTcpClientThreadOne;//QThread m_dealTcpClientThreadOne;TcpClient* m_tcpClientOne;void initializeClient();signals:void startClientConnectTcpServer();void sendData2TcpServer(QByteArray data);
};
#endif // MAIN_INTERFACE_H
#include "main_interface.h"
#include "ui_main_interface.h"MainInterface::MainInterface(QWidget *parent): QMainWindow(parent), ui(new Ui::MainInterface)
{ui->setupUi(this);this->setWindowTitle("自动重连TCp服务器客户端");//初始化客户端initializeClient();//发送数据到客户端//connect(ui->pushButton, &QPushButton::clicked,// this, [&](){// emit sendData2TcpServer(ui->lineEdit->text().toUtf8());// });}MainInterface::~MainInterface()
{//退出客户端m_dealTcpClientThreadOne.quit();//停止事件循环m_dealTcpClientThreadOne.wait();//阻塞知道线程结束delete ui;
}void MainInterface::initializeClient()
{QString ip = "192.168.0.222";int port = 10000;qDebug()<<"initializeClient()"<< QThread::currentThread();//客户端1m_tcpClientOne = new TcpClient(this, ip, port);m_tcpClientOne->moveToThread(&m_dealTcpClientThreadOne);//在主线程中创建connect(this, &MainInterface::startClientConnectTcpServer,m_tcpClientOne, &TcpClient::initializeFunction);//线程销毁connect(&m_dealTcpClientThreadOne, &QThread::finished,this, &TcpClient::deleteLater);//客户端收到的数据connect(m_tcpClientOne, &TcpClient::receiveData,this, [](QByteArray data){qDebug()<<"收到的数据:"<<data;});//客户端发送数据到服务器connect(this, &MainInterface::sendData2TcpServer,m_tcpClientOne, &TcpClient::sendData);//开启线程m_dealTcpClientThreadOne.start();//开启客户端重连计时emit startClientConnectTcpServer();}
附录
完整的代码项目
参考
Qt多线程问题分析及解决思路QObject: Cannot create children for a parent that is in a different thread
简单例子理解 Qt 中 QObject: Cannot create children for a parent that is in a different thread. 问题
Qt多线程中使用QTimer(常见问题汇总)
Qt Forum QObject: Cannot create children for a parent that is in a different thread.
QObject::killTimer: timers cannot be stopped from another thread-解决方案