QT之TCP客户端线程通信

摘要

最近因为工作上的需求要利用到tcp,之前接触的比较多的是C语言的tcp通信,转到QT之后多多少少有些不适应,因为QT把TCP通信的功能封装好了。让我们一起看看TCP客户端加上线程是如何实现的。

线程

因为C语言的影响,我会首选线程来实现TCP客户端。因为GUI编程下不知道进程是否操作的好这是其一,

其二是因为线程与进程相比对系统的资源开销比较小,利于优化,减少界面的卡顿提高用户体验这是很重要

的一点。然后就是GUI下线程会循环执行,因此把客户端的连接功能扔给线程去做再合适不过了。这样次客

户端就能主动的连接服务器端,就算连接失败也能继续重连。

我使用的是QT5版本,所以也只讨论QT5,看过网上各位大牛的总结后我才领悟到,QT下的线程是如何创建的。

首先你得在.pro工程文件里

QT       += network

//然后再
#include 
#include 
#include 

两种方法:


m_Thread = new QThread(); //创建一个线程
m_SMU = new SMUThread(); //你自己写的一个类
m_SMU->moveToThread(m_Thread);  //把m_SMU的类扔到线程里,让它去跑
m_Thread->start();        //线程开始

第一种继承

class TimeThread : public QThread
{
}
是继承Thread类 这样写法要注意的是,run()函数里面才是线程执行的地方,因此如果要循环常搭配while(1)。

任何继承于QThread的线程都是通过继承QThread的run函数来实现多线程的,因此,必须重写QThread的run函数,把复杂逻辑写在QThread的run函数中。

QThread在不调用exec()情况下是exit函数和quit函数是没有作用的,要马上终止一个线程可以使用terminate函数,但这个函数存在非常不安定因素,不推荐使用。当run()函数返回的以后,线程会退出(exit)。如果没有调用exec()函数,该线程中不存在任何运行状态的事件循环(event loop)。

正确的方法是自己在run()函数里面加一个bool来进行判断,修改bool类型的时候要加锁。这也大多数要修改线程参数普遍常用的方法。继承QThread的函数在运行完run函数后就视为线程完成,会发射finish信号。

第二种继承

class TimeThread : public QObject
{
}

现在Qt官方并不是很推荐继承QThread来实现多线程方法,而是极力推崇继承QObject的方法来实现,当然用哪个方法实现要视情况而定,别弄错了就行,估计Qt如此推崇继承QObject的方法可能是QThread太容易用错的原因。

QObject是Qt框架的基本类,但凡涉及到信号槽有关的类都是继承于QObject。QObject是一个功能异常强大的类,它提供了Qt关键技术信号和槽的支持以及事件系统的支持,同时它提供了线程操作的接口,也就是QObject是可以选择不同的线程里执行的。

用QObject来实现多线程有个非常好的优点,就是默认就支持事件循环(Qt的许多非GUI类也需要事件循环支持,如QTimer、QTcpSocket),QThread要支持事件循环需要在QThread::run()中调用QThread::exec()来提供对消息循环的支持,否则那些需要事件循环支持的类都不能正常发送信号,因此如果要使用信号和槽,那就直接使用QObject来实现多线程。

1/写一个继承QObject的类,对需要进行复杂耗时逻辑的入口函数声明为槽函数

2/此类在旧线程new出来,不能给它设置任何父对象

3/同时声明一个QThread对象,在官方例子里,QThread并没有new出来,这样在析构时
就需要调用QThread::wait(),如果是堆分配的话,可以通过deleteLater来让线程自杀


4/把obj通过moveToThread方法转移到新线程中,此时object已经是在线程中了

5/把线程的finished信号和object的deleteLater槽连接,这个信号槽必须连接,否则会内存泄漏

6/正常连接其他信号和槽(在连接信号槽之前调用moveToThread,不需要处理connect
的第五个参数,否则就显示声明用Qt::QueuedConnection来连接)

7/初始化完后调用'QThread::start()来启动线程

8/在逻辑结束后,调用QThread::quit退出线程的事件循环

客户端

本次需求因为要实现客户端功能,因此不用声明一个服务器对象,逻辑相对简单。

SMUTcpSocket = new QTcpSocket(this);
connect(this->SMUTcpSocket,SIGNAL(connected()),this,SLOT(on_conn()));
connect(this->SMUTcpSocket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(showError(QAbstractSocket::SocketError)));

捕捉连接成功和连接失败的信号。
new的时候用this指针作为参数让socket对象周期和父类一致(父死子死),确保不会内存泄漏,当然保险还是要在析构函数里delete掉。

在这里我们要实现的是客户端的不断重连逻辑,因此要在线程里添加定时器。

connect(this->m_timer, SIGNAL(timeout()),this,SLOT(tryToConnect()));
m_timer->setInterval(9000);
m_timer->start();

void SMUThread::tryToConnect()
{
    qDebug()<<"2222222222"<
    SMUTcpSocket->abort();    //清除上次连接
    SMUTcpSocket->connectToHost("10.10.0.34",50040);
}

debug打印是否有执行,个人觉得比断点好用,看自己习惯。
定时器9秒会触发一次connectTOHost逻辑,那怎么判断是否链接成功呢?
答案就是

SIGNAL(connected())

因为QT封装的很好,所以直接用信号捕捉就行了。

void SMUThread::on_conn()
{
     m_timer->stop();
     m_second->stop();
    qDebug()<<"连接SMU成功!"<<endl;
    QString str = "连接SMU成功!";
    emit sendString(str);
}

然后就让计时器在连接成功后停掉,这样子即便是线程循环不会一直触发。顺带一提的是,在线程类里面构造函数只会执行一次。
emit 发射信号QString字符串。

在父窗口 mainwindow中:

    m_SMUThread = new QThread();
    m_SMU = new SMUThread();
    connect(m_SMU,SIGNAL(sendString(QString)),this,SLOT(Auxiliary_information(QString)));
    m_SMU->moveToThread(m_SMUThread);
    m_SMUThread->start();

发射出来的信号会触发Auxiliary_information(QString)函数,然后

void MainWindow::Auxiliary_information(QString str)
{
    ui->label_8->setText(str);
}

字符串会显示在label中这里写图片描述

这就是一个避免button触发客户端线程通信的完整例子了,感谢您的阅读,因为我也是刚学习没多久如果有错误的地方还望指正(可私信或者评论),大家共同学习,谢谢:)

你可能感兴趣的:(qt开发)