1. 程式人生 > >QT之TCP客戶端執行緒通訊

QT之TCP客戶端執行緒通訊

摘要

最近因為工作上的需求要利用到tcp,之前接觸的比較多的是C語言的tcp通訊,轉到QT之後多多少少有些不適應,因為QT把TCP通訊的功能封裝好了。讓我們一起看看TCP客戶端加上執行緒是如何實現的。

執行緒

因為C語言的影響,我會首選執行緒來實現TCP客戶端。因為GUI程式設計下不知道程序是否操作的好這是其一,

其二是因為執行緒與程序相比對系統的資源開銷比較小,利於優化,減少介面的卡頓提高使用者體驗這是很重要

的一點。然後就是GUI下執行緒會迴圈執行,因此把客戶端的連線功能扔給執行緒去做再合適不過了。這樣次客

戶端就能主動的連線伺服器端,就算連線失敗也能繼續重連。

我使用的是QT5版本,所以也只討論QT5,看過網上各位大牛的總結後我才領悟到,QT下的執行緒是如何建立的。

首先你得在.pro工程檔案裡

QT       += network

//然後再
#include <QTcpSocket>
#include <QtNetwork>
#include <QThread>

兩種方法:

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"<<endl;
    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觸發客戶端執行緒通訊的完整例子了,感謝您的閱讀,因為我也是剛學習沒多久如果有錯誤的地方還望指正(可私信或者評論),大家共同學習,謝謝:)