Qt封裝QTcpServer參考資料--QTcpServer多線程實現


目的:每個客戶端連接的tcpSocket分別分配一個專門的線程來處理。

實現時分別繼承QTcpServer和QTcpScoket實現出自己需要的類。

繼承QTcpServer為每個客戶端連接時分配線程,並接受處理tcpScoket的信號和槽、、還有發送信息,儲存連接信息等。

繼承QTcpScoket為處理通信數據和增加信號的參數,以便和tcpServer更好的配合。

首先是繼承並重寫QTcpServer的incomingConnection函數去自己實現tcpsocket連接的建立和分配。

其文檔的默認描述為:

This virtual function is called by QTcpServer when a new connection is available. The socketDescriptor argument is the native socket descriptor for the accepted connection.

The base implementation creates a QTcpSocket, sets the socket descriptor and then stores the QTcpSocket in an internal list of pending connections. Finally newConnection() is emitted.

Reimplement this function to alter the server's behavior when a connection is available.

If this server is using QNetworkProxy then the socketDescriptor may not be usable with native socket functions, and should only be used with QTcpSocket::setSocketDescriptor().

Note: If you want to handle an incoming connection as a new QTcpSocket object in another thread you have to pass the socketDescriptor to the other thread and create the QTcpSocket object there and use its setSocketDescriptor() method.

譯文(谷歌翻譯和自己簡單的更正):

當QTcpServer有一個新的連接時這個虛函數被調用。該socketDescriptor參數是用於接受連接的本地套接字描述符。

該函數會創建一個QTcpSocket,並設置套接字描述符為socketDescriptor,然后存儲QTcpSocket在掛起連接的內部清單。最后newConnection()被發射。

重新實現這個函數來改變服務器的行為,當一個連接可用。

如果該服務器使用QNetworkProxy那么socketDescriptor可能無法與原生socket函數使用,並且只能用QTcpSocket:: setSocketDescriptor()中使用。

注意:如果你想處理在另一個線程一個新的QTcpSocket對象傳入連接,您必須將socketDescriptor傳遞給其他線程,並創建了QTcpSocket對象存在並使用其setSocketDescriptor()方法。

所以我們必須先重寫這個函數:

下面先附上繼承QTcpServer的自己的類聲明,代碼注釋個人以為挺詳細了:

01 //繼承QTCPSERVER以實現多線程TCPscoket的服務器。
02 class MyTcpServer : public QTcpServer
03 {
04     Q_OBJECT
05 public:
06     explicit MyTcpServer(QObject *parent = 0);
07     ~MyTcpServer();
08 signals:
09     void connectClient(const int const QString & ,constquint16 );//發送新用戶連接信息
10     void readData(const int,const QString &, quint16, const QByteArray &);//發送獲得用戶發過來的數據
11     void sockDisConnect(const int ,const QString &,const quint16 );//斷開連接的用戶信息
12     void sentData(const QByteArray &,const int);//向scoket發送消息
13 public slots:
14     void setData(const QByteArray & data, const int  handle);//想用戶發送消息
15     void readDataSlot(const intconst QString &, const quint16,const QByteArray &);//發送獲得用戶發過來的數據
16     void sockDisConnectSlot(int handle, QString ip, quint16 prot);//斷開連接的用戶信息
17  
18 protected:
19     void incomingConnection(qintptr socketDescriptor);//覆蓋已獲取多線程
20 private:
21     QMap<int,myTcpSocket *> * tcpClient;
22 };

接着是我們重寫void QTcpServer::incomingConnection(qintptr socketDescriptor)的實現:

01 void MyTcpServer::incomingConnection(qintptr socketDescriptor)
02 {
03     myTcpSocket * tcpTemp = new myTcpSocket(socketDescriptor);
04     QThread * thread new QThread(tcpTemp);//把線程的父類設為連接的,防止內存泄漏
05     //可以信號連接信號的,我要捕捉線程ID就獨立出來函數了,使用中還是直接連接信號效率應該有優勢
06     connect(tcpTemp,&myTcpSocket::readData,this,&MyTcpServer::readDataSlot);//接受到數據
07     connect(tcpTemp,&myTcpSocket::sockDisConnect,this,&MyTcpServer::sockDisConnectSlot);//斷開連接的處理,從列表移除,並釋放斷開的Tcpsocket
08     connect(this,&MyTcpServer::sentData,tcpTemp,&myTcpSocket::sentData);//發送數據
09     connect(tcpTemp,&myTcpSocket::disconnected,thread,&QThread::quit);//斷開連接時線程退出
10     tcpTemp->moveToThread(thread);//把tcp類移動到新的線程
11     thread->start();//線程開始運行
12  
13     tcpClient->insert(socketDescriptor,tcpTemp);//插入到連接信息中
14     qDebug() <<"incomingConnection THREAD IS:" <<QThread::currentThreadId();
15     //發送連接信號
16     emit connectClient(tcpTemp->socketDescriptor(),tcpTemp->peerAddress().toString(),tcpTemp->peerPort());
17  
18 }

用moveToThread來處理移動到新的線程。

其他的非重要的函數就不一一列出,但是別忘記在斷開連接的槽中釋放連接:

01 void MyTcpServer::setData(const QByteArray &data, const int handle)
02 {
03     emit sentData(data,handle);
04 }
05  
06 void MyTcpServer::sockDisConnectSlot(int handle, QString ip, quint16 prot)
07 {
08     qDebug() <<"MyTcpServer::sockDisConnectSlot thread is:" << QThread::currentThreadId();
09  
10     myTcpSocket * tcp = tcpClient->value(handle);
11     tcpClient->remove(handle);//連接管理中移除斷開連接的socket
12     delete tcp;//釋放斷開連接的資源、、子對象線程也會釋放
13     emit sockDisConnect(handle,ip,prot);
14 }

其次就是繼承的QTcpSocket的聲明,直接上代碼把:

01 //繼承QTcpSocket以處理接收到的數據
02 class myTcpSocket : public QTcpSocket
03 {
04     Q_OBJECT
05 public:
06     explicit myTcpSocket(qintptr socketDescriptor,QObject *parent = 0);
07  
08 signals:
09     void readData(const int,const QString &,constquint16,const QByteArray &);//發送獲得用戶發過來的數據
10     void sockDisConnect(const int ,const QString &,const quint16 );//斷開連接的用戶信息
11 public slots:
12     void thisReadData();//處理接收到的數據
13     void sentData(const QByteArray & ,const int);//發送信號的槽
14 private:
15     qintptr socketID;//保存id,== this->socketDescriptor();但是this->socketDescriptor()客戶端斷開會被釋放,
16                         //斷開信號用this->socketDescriptor()得不到正確值
17 };

這個實現其實更簡單了、、、就把主要的信號槽連接部分附上把,還有發送數據需要注意下,我是用的Qt,其中信號槽用的新語法,而且配合的C++11的lambda表達式,你如果不清楚C++11,你最好先去看下C++11的文檔。

01 myTcpSocket::myTcpSocket(qintptr socketDescriptor, QObject *parent) :
02     QTcpSocket(parent),socketID(socketDescriptor)
03 {
04     this->setSocketDescriptor(socketDescriptor);
05     //轉換系統信號到我們需要發送的數據、、直接用lambda表達式了,qdebug是輸出線程信息
06     connect(this,&myTcpSocket::readyRead,this,&myTcpSocket::thisReadData); //連接到收到數據的處理函數
07     connect(this,&myTcpSocket::readyRead, //轉換收到的信息,發送信號
08             [this](){
09                 qDebug() <<"myTcpSocket::myTcpSocket lambda readData thread is:" << QThread::currentThreadId();                 emit readData(socketID,this->peerAddress().toString(),this->peerPort() ,this->readAll());//發送用戶發過來的數據
10             });
11     connect(this,&myTcpSocket::disconnected, //斷開連接的信號轉換
12             [this](){
13                 qDebug() <<"myTcpSocket::myTcpSocket lambda sockDisConnect thread is:" << QThread::currentThreadId();                 emit sockDisConnect(socketID,this->peerAddress().toString(),this->peerPort());//發送斷開連接的用戶信息
14             });
15  
16     qDebug() << this->socketDescriptor() << " " << this->peerAddress().toString()
17                 << " " << this->peerPort() << "myTcpSocket::myTcpSocket thread is " <<QThread::currentThreadId();
18 }
19  
20 void myTcpSocket::sentData(const QByteArray &data, const int id)
21 {
22     //如果是服務器判斷好,直接調用write會出現跨線程的現象,所以服務器用廣播,每個連接判斷是否是自己要發送的信息。
23     if(id == socketID)//判斷是否是此socket的信息
24     {
25         qDebug() << "myTcpSocket::sentData" << QThread::currentThreadId();
26         write(data);
27     }
28 }

整篇代碼中出現了n個qDebug() << ,這個是我為了查看運行所在的線程所設,實際應用中這些都沒用的、、你自己刪除把、、自己測試的例子和源碼我還是保留了、、畢竟時間長了都忘得,留着那天一看就一目了然的、、

這個每個連接分配一個線程,連接太多很耗資源的、、您可以自己更改下,把多個連接移到一個線程,但是那樣,你需要保存線程信息,更要小心線程的分配和釋放時、、可以自己做下、、我也歡迎大家來探討、、

最新更新:添加線程管理類(應該算個線程池),單例類。可預先設置線程數或者每個線程處理多少連接。原來的代碼主要變動在新建斷開連接處更新了、、詳細請見代碼。

全部的代碼測試例子,服務端和客戶端,我傳到了我的github上了,附上項目地址:https://github.com/dushibaiyu/QtTcpThreadServer

 轉自:https://www.dushibaiyu.com/2013/12/qtcpserver%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%AE%9E%E7%8E%B0.html

http://www.qtcn.org/bbs/read-htm-tid-60285.html

jerry該作者寫的還是不錯的,可惜需要qt5以上,使用qt4.8的話還是自己理解透qtcpserver、qttcpsocket、信號槽、movetothread后自己寫一個吧。稍后會補上本人寫的qt4.8代碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM