文章目录
- 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_OBJECT
public:
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;
//客户端1
m_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_NAMESPACE
class DealTcpClientThreadOne : public QThread{
Q_OBJECT
public:
DealTcpClientThreadOne(){
qDebug() << "DealTcpClientThreadOne() " << QThread::currentThread();
}
};
class MainInterface : public QMainWindow
{
Q_OBJECT
public:
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();
//客户端1
m_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-解决方案