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