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

news2024/9/20 22:45:48

文章目录

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

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

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

相关文章

U盘显示未被格式化:深度解析、恢复策略与预防之道

现象透视&#xff1a;U显示未被格式化的迷局 在日常的数字生活中&#xff0c;U盘作为我们随身携带的数据仓库&#xff0c;承载着无数重要的文件与回忆。然而&#xff0c;当U盘突然弹出“未被格式化”的警告时&#xff0c;这份便捷瞬间转化为焦虑与不安。这一提示不仅意味着U盘…

uboot:源码分析-启动第一阶段-start.S解析

start.S引入 进入start.S文件中&#xff0c;发现57行中就是_start标号的定义处 SourceInsight中添加行号 在SI中&#xff0c;如果我们知道我们要找的文件的名字&#xff0c;但是我们又不知道他在哪个目录下&#xff0c;我们要怎样找到并打开这个文件&#xff1f;方法是在SI中先…

多重指针变量(n重指针变量)实例分析

0 前言 指针之于C语言&#xff0c;就像子弹于枪械。没了子弹的枪械虽然可以用来肉搏&#xff0c;却失去了迅速解决、优雅解决战斗的能力。但上了膛的枪械也非常危险&#xff0c;时刻要注意是否上了保险&#xff0c;使用C语言的指针也是如此&#xff0c;要万分小心&#xff0c;…

usemeno和usecallback区别及使用场景

1. useMemo 用途: useMemo 用于缓存计算结果。它接受一个函数和依赖项数组&#xff0c;只有当依赖项发生变化时&#xff0c;才会重新计算该函数的返回值。否则&#xff0c;它会返回缓存的值。 返回值: useMemo 返回的是函数执行后的结果。 使用场景: 当一个计算量大的函数在每…

Java面试篇基础部分-线程的基本方法

线程的基本方法有wait()、notify()、notifyAll()、sleep()、join()、yield()等等,这些方法都是用来控制线程的运行,并且可以实质性的影响到线程的状态变化情况。 让线程等待的方法:wait()方法 调用wait()方法的线程会进入到WAITING状态,只有等到其他线程通知或者线程被中…

【数据结构-差分】力扣1589. 所有排列中的最大和

有一个整数数组 nums &#xff0c;和一个查询数组 requests &#xff0c;其中 requests[i] [starti, endi] 。第 i 个查询求 nums[starti] nums[starti 1] … nums[endi - 1] nums[endi] 的结果 &#xff0c;starti 和 endi 数组索引都是 从 0 开始 的。 你可以任意排列…

【Java面向对象二】static的注意事项

文章目录 前言一、关于static的三个注意点总结 前言 记录static的学习注意事项。 一、关于static的三个注意点 1、类方法中可以直接访问类的成员&#xff0c;不可用直接访问实例成员。 2、实例方法中既可以直接访问类成员&#xff0c;也可以直接访问实例成员。 3、实例方法…

105.WEB渗透测试-信息收集-FOFA语法(5)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;104.WEB渗透测试-信息收集-FOFA语法&#xff08;4&#xff09; 还有一个能查看信息的地方…

linux下的分布式Minio部署实践

Linux下的分布式Minio部署实践 分布式Minio部署可以将多块硬盘&#xff08;位于相同机器或者不同机器&#xff09;组成一个对象存储服务&#xff0c;避免单机环境下硬盘容量不足、单点故障等问题。 1. 简介 在当前的云计算和大数据时代&#xff0c;IT系统通常的设计理念都是…

英伟达Jim Fan预测:未来2~3年机器人将迎来“GPT-3时刻”

在这个科技不断进步的时代&#xff0c;我们终将迎来“与机器人共存”的未来。你认为&#xff0c;未来会是人机和平共处&#xff0c;还是《终结者》式未来&#xff1f; 随着科技发展&#xff0c;这个未来似乎近在咫尺。昨日外媒 The Decoder 发文报道&#xff0c;在最近的一次红…

Jenkins自动化部署后端项目看这篇就够了

本文主要讲解&#xff0c;使用Jenkins自动化部署后端工程。讲解怎么自动化部署前后的分离项目中的后端工程。 前提条件&#xff1a;本地需要Jenkins&#xff0c;如果你不知道怎么安装&#xff0c;可以看我的另外一篇文章。 Jenkins实现自动部署的步骤&#xff1a; 先拉取git…

Jboss 低版本JMX Console未授权

漏洞描述 此漏洞主要是由于JBoss中/jmx-console/HtmlAdaptor路径对外开放&#xff0c;并且没有任何身份验证机制&#xff0c;导致攻击者可以进⼊到 jmx控制台&#xff0c;并在其中执⾏任何功能。 影响范围 Jboss4.x以下 环境搭建 cd vulhub-master/jboss/CVE-2017-7504 d…

力扣题解2414

大家好&#xff0c;欢迎来到无限大的频道。 今日继续给大家带来力扣题解。 题目描述&#xff08;中等&#xff09;&#xff1a; 最长的字母序连续字符串的长度 ​ ​字母序连续字符串 是由字母表中连续字母组成的字符串。换句话说&#xff0c;字符串 "abcdefghijklm…

linux 最简单配置免密登录

需求&#xff1a;两台服务器互信登录需要拉起对端服务 ip&#xff1a; 192.168.1.133 192.168.1.137 一、配置主机hosts&#xff0c;IP及主机名&#xff0c;两台都需要 二、192.168.1.137服务器&#xff0c;生成密钥 ssh-keygen -t rsa三、追加到文件 ~/.ssh/authorized_key…

分布式中间件-Pika一个高效的分布式缓存组件

文章目录 Pika简介Pika特性Pika解决的问题及应用场景Pika架构之存储引擎部署模式1、主从模式2、分布式集群模式 Pika快速上手1、二进制包方式2、源码编译方式2.1 支持的平台2.2 依赖的库软件2.3 编译过程2.4 启动 Pika2.5 清空已编译的结果2.6 Pika 的开发调试 3、容器化3.1 使…

【2025】儿童疫苗接种预约小程序(源码+文档+解答)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

【C++指南】inline内联函数详解

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《C指南》 期待您的关注 目录 引言 C为什么引入了inline来替代C语言中的宏 inline的基本用法 定义inline函数 inline的优势与…

DAY20信息打点-红蓝队自动化项目资产侦察武器库部署企查产权网络空间

2.自动化-网络空间-AsamF 1.去GitHub上下载项目之后使用CMD打开 2.输入命令AsamF_windows_amd64.exe -v生成配置文件 3.AsamF会在~/.config/asamf/目录下生成config.json文件 C:\Users\Acer\.config\asamf 5.根据文档输入命令去查询所需信息&#xff08;已经没有用了&#x…

C/C++通过CLion2024进行Linux远程开发保姆级教学

目前来说&#xff0c;对Linux远程开发支持相对比较好的也就是Clion和VSCode了&#xff0c;这两个其实对于C和C语言开发都很友好&#xff0c;大可不必过于纠结使用那个&#xff0c;至于VS和QtCreator&#xff0c;前者太过重量级了&#xff0c;后者更是不用说&#xff0c;主要用于…

解锁自动化新境界:KeymouseGo,让键盘和鼠标动起来!

文章目录 解锁自动化新境界&#xff1a;KeymouseGo&#xff0c;让键盘和鼠标动起来&#xff01;背景&#xff1a;为何选择KeymouseGo&#xff1f;KeymouseGo简介安装KeymouseGo简单函数使用应用场景常见问题与解决方案总结 解锁自动化新境界&#xff1a;KeymouseGo&#xff0c;…