目的:每個客戶端連接的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的自己的類聲明,代碼注釋個人以為挺詳細了:
02 |
class MyTcpServer : public QTcpServer |
06 |
explicit MyTcpServer(QObject *parent = 0); |
09 |
void connectClient( const int , const QString & , const quint16 ); |
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 ); |
14 |
void setData( const QByteArray & data, const int handle); |
15 |
void readDataSlot( const int , const QString &, const quint16, const QByteArray &); |
16 |
void sockDisConnectSlot( int handle, QString ip, quint16 prot); |
19 |
void incomingConnection(qintptr socketDescriptor); |
21 |
QMap< int ,myTcpSocket *> * tcpClient; |
接着是我們重寫void QTcpServer::incomingConnection(qintptr socketDescriptor)的實現:
01 |
void MyTcpServer::incomingConnection(qintptr socketDescriptor) |
03 |
myTcpSocket * tcpTemp = new myTcpSocket(socketDescriptor); |
04 |
QThread * thread = new QThread(tcpTemp); |
06 |
connect(tcpTemp,&myTcpSocket::readData, this ,&MyTcpServer::readDataSlot); |
07 |
connect(tcpTemp,&myTcpSocket::sockDisConnect, this ,&MyTcpServer::sockDisConnectSlot); |
08 |
connect( this ,&MyTcpServer::sentData,tcpTemp,&myTcpSocket::sentData); |
09 |
connect(tcpTemp,&myTcpSocket::disconnected, thread ,&QThread::quit); |
10 |
tcpTemp->moveToThread( thread ); |
13 |
tcpClient->insert(socketDescriptor,tcpTemp); |
14 |
qDebug() << "incomingConnection THREAD IS:" <<QThread::currentThreadId(); |
16 |
emit connectClient(tcpTemp->socketDescriptor(),tcpTemp->peerAddress().toString(),tcpTemp->peerPort()); |
用moveToThread來處理移動到新的線程。
其他的非重要的函數就不一一列出,但是別忘記在斷開連接的槽中釋放連接:
01 |
void MyTcpServer::setData( const QByteArray &data, const int handle) |
03 |
emit sentData(data,handle); |
06 |
void MyTcpServer::sockDisConnectSlot( int handle, QString ip, quint16 prot) |
08 |
qDebug() << "MyTcpServer::sockDisConnectSlot thread is:" << QThread::currentThreadId(); |
10 |
myTcpSocket * tcp = tcpClient->value(handle); |
11 |
tcpClient-> remove (handle); |
13 |
emit sockDisConnect(handle,ip,prot); |
其次就是繼承的QTcpSocket的聲明,直接上代碼把:
02 |
class myTcpSocket : public QTcpSocket |
06 |
explicit myTcpSocket(qintptr socketDescriptor,QObject *parent = 0); |
09 |
void readData( const int , const QString &, const quint16, const QByteArray &); |
10 |
void sockDisConnect( const int , const QString &, const quint16 ); |
13 |
void sentData( const QByteArray & , const int ); |
這個實現其實更簡單了、、、就把主要的信號槽連接部分附上把,還有發送數據需要注意下,我是用的Qt,其中信號槽用的新語法,而且配合的C++11的lambda表達式,你如果不清楚C++11,你最好先去看下C++11的文檔。
01 |
myTcpSocket::myTcpSocket(qintptr socketDescriptor, QObject *parent) : |
02 |
QTcpSocket(parent),socketID(socketDescriptor) |
04 |
this ->setSocketDescriptor(socketDescriptor); |
06 |
connect( this ,&myTcpSocket::readyRead, this ,&myTcpSocket::thisReadData); |
07 |
connect( this ,&myTcpSocket::readyRead, |
09 |
qDebug() << "myTcpSocket::myTcpSocket lambda readData thread is:" << QThread::currentThreadId(); emit readData(socketID, this ->peerAddress().toString(), this ->peerPort() , this ->readAll()); |
11 |
connect( this ,&myTcpSocket::disconnected, |
13 |
qDebug() << "myTcpSocket::myTcpSocket lambda sockDisConnect thread is:" << QThread::currentThreadId(); emit sockDisConnect(socketID, this ->peerAddress().toString(), this ->peerPort()); |
16 |
qDebug() << this ->socketDescriptor() << " " << this ->peerAddress().toString() |
17 |
<< " " << this ->peerPort() << "myTcpSocket::myTcpSocket thread is " <<QThread::currentThreadId(); |
20 |
void myTcpSocket::sentData( const QByteArray &data, const int id) |
25 |
qDebug() << "myTcpSocket::sentData" << QThread::currentThreadId(); |
整篇代碼中出現了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代碼。