1. 程式人生 > >Qt TCP通訊,多執行緒伺服器端

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