Qt 多线程TCP客户端使用QTimer进行重连服务器———附带详细代码和讲解

文章目录

  • 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-解决方案

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/146092.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

如何通过思维链提升LLM推理能力?

思维链推理(Chain-of-Thought Reasoning)&#xff0c;因其彻底改变了模型处理复杂问题的解决方式&#xff0c;目前已成为人工智能领域最炙手可热的重大进展之一。 通过模拟推理过程&#xff0c;CoT训练大语言模型将复杂的问题拆解&#xff0c;并提供更清晰、更具逻辑的响应(re…

需求4:新加字段(进阶版)

关于加一个字段这种&#xff0c;我前几篇文章已经写过了。这篇文章的这个需求&#xff0c;也是写关于加字段的&#xff0c;只不过与前两篇文章不一样的是&#xff0c;这篇文章的这个需求讲的比较隐晦&#xff0c;需求没有直接跟你说要你加一个字段&#xff0c;要你自己想一下才…

(undone) 学习语音学中关于 i-vector 和 x-vector

来源&#xff1a;https://ieeexplore.ieee.org/stamp/stamp.jsp?tp&arnumber8461375 (这是一篇跟 X-vector 有关的论文) 这里有更适合初学者的两个资料: 1.https://www.youtube.com/watch?vR3rzN6JYm38 &#xff08;MIT教授的youtube视频&#xff09; 2.https://people.c…

【微信支付-服务商】SpringBoot集成微信服务商支付(多子商户集成)

SpringBoot集成微信服务商支付&#xff08;多子商户集成&#xff09; 前言一、前置工作1、获取商户平台的xxx核心参数2、关联对应的小程序&#xff08;appid&#xff09; 二、SpringBoot集成微信小程序1、引入pom依赖2、yml配置3、java代码文件3.1、Properties 配置类3.2 Confi…

基于JAVA+SpringBoot+Vue的学生干部管理系统

基于JAVASpringBootVue的学生干部管理系统 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末附源码下载链接&#x1f345; 哈…

windows打开可选功能窗口的方式(呜呜设置里面找不到可选功能只能这样找了)

打开方式 winR打开运行窗口&#xff0c;输入fodhelper&#xff0c;按下回车键 即可快速打开可选功能窗口

ChemChat——大语言模型与化学的未来,以及整合外部工具和聊天机器人的潜力

概述 论文地址&#xff1a;https://arxiv.org/abs/2309.16235 虽然近年来技术创新和变革日新月异&#xff0c;从根本上改变了我们对生物化学过程的认识&#xff0c;但化学领域仍花费大量时间和金钱–"10 年 "和 “3000 亿”–将新产品推向市场。这是由于实验室实验的…

发现编程的全新境界——明基RD280U显示器使用体验

前言 在大学的四年里&#xff0c;我几乎每天都泡在实验室&#xff0c;盯着电脑屏幕&#xff0c;一行行地码代码。那时&#xff0c;学校提供的显示器是非常基础的款式&#xff0c;功能简单&#xff0c;几乎没有任何特别之处&#xff0c;甚至配置也比较低。那个时候&#xff0c;…

Shader 中的光源

1、Shader 开发中常用的光源属性 Unity当中一共支持四种光源类型&#xff1a; 平行光&#xff08;Directional&#xff09;点光源&#xff08;Point&#xff09;聚光灯&#xff08;Spot&#xff09;面光源&#xff08;Area&#xff09;— 面光源仅在烘焙时有用 不管光源类型到…

通过MCGS在ARMxy边缘计算网关上实现物流自动化

随着电子商务和智能制造的快速发展&#xff0c;物流行业面临着前所未有的挑战与机遇。高效的物流系统不仅可以加快货物周转速度&#xff0c;降低运营成本&#xff0c;还能显著提升客户满意度。 1. ARMxy BL340系列简介 ARMxy BL340系列是针对工业自动化领域设计的一款高性能、…

2024年最新苹果cms升级插件【泛目录专用】

苹果CMS是一款专为视频内容管理而设计的系统&#xff0c;近年来在视频站点搭建中逐渐成为热门选择。其直观的用户界面和灵活的管理功能&#xff0c;使得无论是新手还是专业开发者都能轻松上手。 苹果CMS提供了多种主题和模板&#xff0c;用户可以根据自身需求进行定制&#xf…

北京买新能源车,天津上牌攻略

背景说明 我是在北京买的新能源汽车&#xff08;增程式&#xff09;&#xff0c;因没有摇上北京车牌号&#xff0c;所以计划在天津上牌。前期问了一些代理&#xff0c;要是帮忙弄的话得花500元左右&#xff0c;要是自己搞定的话&#xff0c;大约150元左右&#xff08;130元的车…

计算机毕业设计Spark+Hive旅游景点推荐 旅游推荐系统 景区游客满意度预测与优化 Apriori算法 景区客流量预测 旅游大数据 景点规划

流程&#xff1a; 1.DrissionPage自动化爬虫框架采集旅游数据约10万条存入mysql数据库、.csv文件作为数据集(旅游数据、用户数据、评论数据)&#xff1b; 2.使用pandasnumpy或MapReduce对数据进行数据清洗&#xff0c;生成最终的.csv文件并上传到hdfs(含nlp情感分析)&#xff1…

【每日刷题】Day127

【每日刷题】Day127 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 349. 两个数组的交集 - 力扣&#xff08;LeetCode&#xff09; 2. LCR 022. 环形链表 II - 力扣&a…

软设9.20

1 已知一个文件中出现的各字符及其对应的频率如下表所示。若采用定长编码&#xff0c;则该文件中字符的码长应为()。若采用Hufman编码&#xff0c;则字符序列“face”的编码应为()。 1.&#xff08;&#xff09; A.2 B.3 C.4 D.5 2.&#xff08;&#xff09; A.110001001101…

工程师 - PFM介绍

在电子电路设计中&#xff0c;PFM&#xff08;Pulse Frequency Modulation&#xff0c;脉冲频率调制&#xff09;是一种调制技术&#xff0c;其主要特点是在负载变化时调整脉冲的频率&#xff0c;而保持脉冲的宽度&#xff08;时间长度&#xff09;相对恒定。与PWM&#xff08;…

详解Vue事件总线的原理与应用:EventBus

Vue 事件总线 - 组件通信的桥梁 引言 在 Vue.js 开发中&#xff0c;组件通信是一个重要的话题。Vue 提供了多种方式来实现不同组件之间的通信&#xff0c;譬如Props、 $emit、Ref实例、Vuex状态管理及事件总线等等&#xff0c;可谓是五花八门&#xff0c;它们之间使用各有优缺…

4款音频转文字在线转换工具帮你解锁新的记录模式。

越来越多的人都知道使用一些工具来将音频直接转换成文字&#xff0c;这样便省去了手动输入的麻烦。而且使用音频进行记录也能够提高工作的效率&#xff0c;像会议记录&#xff0c;课堂教学记录&#xff0c;采访录音等。如果大家有需要将自己的音频转成文字&#xff0c;可以试试…

STM32 使用 CubeMX 实现按键外部中断

目录 问题背景知识参考需要改什么注意尽量不要在中断函数使用 循环函数做延时中断函数中延时方法调试 问题 我想实现按钮触发紧急停止类似功能&#xff0c;需要使用按键中断功能。 背景知识 GPIO 点亮 LED。stm32cubemx hal学习记录&#xff1a;GPIO输入输出。STM32—HAL库 …

[c++进阶(八)]STL容器适配器之queue

1.前言 和stack一样&#xff0c;队列也没有把他放在容器的一栏里面&#xff0c;而是把他放在容器适配器的一栏。这也是因为queue是使用了别人的相关接口&#xff0c;空间然后来封装自己的内容&#xff0c;最后再给上层用户使用。 2.队列 队列的性质就是先进先出&#xff0c;他…