Qt TCP通訊,多執行緒伺服器端
相信許多初學Qt的同學都會和我一樣遇到這樣的問題:
一、Qt TCP通訊在使用nextPendingConnect後,伺服器端就只會與最後接入的客戶端通訊,這個時候就會考慮繼承QThread實現多執行緒,從而實現多個客戶端與伺服器端通訊,每當一個新的客戶端連線時,通過標識碼socketDescriptor,實現與對應的客戶端通訊。
void server::incomingConnection(int socketDescriptor)
{
socketList.append(socketDescriptor);
serverThread *thread = new serverThread(socketDescriptor, 0);
connect(thread, SIGNAL(started()), dlg, SLOT(showConnection()));
connect(thread, SIGNAL(disconnectTCP(int)), dlg, SLOT(showDisconnection(int)));
connect(thread, SIGNAL(revData(QString)), dlg, SLOT(revData(QString)));
connect(thread, SIGNAL(finished()), thread , SLOT(deleteLater()));
connect(dlg, SIGNAL(sendData(QString, int)), thread, SLOT(sendData(QString, int)));
thread->start();
}
二、雖然多執行緒伺服器端的例子書上和網上很多(雖然基本一樣= =!), 都是簡單的時間伺服器,只實現簡單的傳送功能,而且每個客戶端發一次就斷開了,但是許多時候我們都要使用完整的收發功能。對於傳送實現還比較簡單隻需要根據socketDescriptor和write函式就可以將資訊傳送到指定的客戶端:
void serverThread::sendData(QString data, int id)
{
if (id == socketDescriptor)
{
tso->write(data.toLocal8Bit());
}
}
接收方面,許多人第一時間就會想到連線readReady()訊號,這個時候問題又發生了,經過一番qDebug發現readReady()訊號根本就沒觸發。到這裡網上的資料也少了,在許多資料都提到阻塞式接收和waitForReadyRead(),但是具體的都沒寫了,就一個函式要怎麼用啊,多少給個例子唄,然而怎麼找都沒有。然後我就在Qt文件裡找這個函式,居然就發現了一個例子:
int numRead = 0, numReadTotal = 0;
char buffer[50];
forever {
numRead = socket.read(buffer, 50);
// do whatever with array
numReadTotal += numRead;
if (numRead == 0 && !socket.waitForReadyRead())
break;
}
果然還是官方的靠譜,趕緊把自己的程式改改,然後就可以接受資料了,然後就沒有然後了。
void serverThread::run()
{
tso = new QTcpSocket;
if (!tso->setSocketDescriptor(socketDescriptor))
return;
connect(tso, &QTcpSocket::disconnected, this, &serverThread::disconnectToHost);
QByteArray data;
forever
{
data = tso->readAll();
QString msg = QString::fromLocal8Bit(data);
if (tso->waitForReadyRead())
{
if (msg.length() != 0)
{
msg = tso->peerAddress().toString() + ':'+ msg;
emit revData(msg);
}
}
}
}
當然這種方法比較low,而且在實現send的時候會出現一個線上程中新開一個執行緒的警告,所以為了達到更好的效果,我們可以繼承TcpSocket類,在裡面實現資料的收發,而且這樣也不需要使用到阻塞。
修改後serverThread的部分原始碼:
void serverThread::run()
{
sock = new MySocket(socketDescriptor, 0);
if (!sock->setSocketDescriptor(socketDescriptor))
return ;
connect(sock, &MySocket::disconnected, this, &serverThread::disconnectToHost);
connect(sock, SIGNAL(revData(QString)), this, SLOT(recvData(QString)));
connect(this, SIGNAL(sendDat(QString,int)), sock, SLOT(sendMsg(QString,int)));
exec();
}
void serverThread::sendData(QString data, int id)
{
if (data == "")
return ;
emit sendDat(data, id);
}
void serverThread::recvData(QString msg)
{
emit revData(msg);
}
這裡執行緒中只是一個訊號轉發的功能。
在MySocket類中只需要像普通一樣實現資料收發就行啦:
MySocket::MySocket(int socketDescriptor, QObject *parent)
: QTcpSocket(parent), socketDescriptor(socketDescriptor)
{
connect(this, SIGNAL(readyRead()),
this, SLOT(recvData()));
}
void MySocket::sendMsg(QString msg, int id)
{
if (id == socketDescriptor)
{
write(msg.toLocal8Bit());
}
}
void MySocket::recvData()
{
QByteArray data;
data = readAll();
QString msg = peerAddress().toString() + ':'+ QString::fromLocal8Bit(data);
emit revData(msg);
}
程式執行圖如下:
因為是自己平常除錯用,所以埠號是寫死了的,需要動態設定埠的同學,就自己多加幾個控制元件,多寫幾行程式碼啦。
程式碼下載,由於CSDN的下載有點坑,請移步GitHub:
https://github.com/DragonPang/QtMultiThreadTcpServer