Qt網路程式設計之QTCPSocket和QTCPServer例項(二)
阿新 • • 發佈:2019-02-02
設想有如下場景:若干的客戶端與伺服器端建立連線,建立連線後,伺服器端隨機發送字串給客戶端,客戶端列印輸出。該節案例使用TCP程式設計。
伺服器端-單執行緒
標頭檔案
#pragma once
//////////////////////////////////////////////////////////////////////////
//tcp服務端-單執行緒處理客戶端連線
#include <QAbstractSocket>
#include <QObject>
class QTcpServer;
class SimpleTcpSocketServerDemo : public QObject
{
Q_OBJECT
public:
SimpleTcpSocketServerDemo();
private slots:
void sendData();
void displayError(QAbstractSocket::SocketError socketError);
private:
QStringList m_oData;
QTcpServer *m_pTcpServer;
};
void testSimpleTcpSocketServerDemo();
原始檔
#include "SimpleTcpSocketServerDemo.h"
#include <assert.h>
#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>
#include <QDataStream>
SimpleTcpSocketServerDemo::SimpleTcpSocketServerDemo()
{
//初始換原始資料
m_oData << tr("You've been leading a dog's life. Stay off the furniture.")
<< tr("You've got to think about tomorrow.")
<< tr("You will be surprised by a loud noise." )
<< tr("You will feel hungry again in another hour.")
<< tr("You might have mail.")
<< tr("You cannot kill time without injuring eternity.")
<< tr("Computers are not intelligent. They only think they are.");
//1. 建立TCP物件
m_pTcpServer = new QTcpServer(this);
//2. 新連線、錯誤訊號
connect(m_pTcpServer, &QTcpServer::newConnection, this, &SimpleTcpSocketServerDemo::sendData);
connect(m_pTcpServer, &QTcpServer::acceptError, this, &SimpleTcpSocketServerDemo::displayError);
//3. 啟動服務端
if (!m_pTcpServer->listen(QHostAddress::Any, 8888))
{
qDebug() << "m_pTcpServer->listen() error";
assert(false);
}
}
void SimpleTcpSocketServerDemo::sendData()
{
//獲取服務端資料
QString sWriteData = m_oData.at(qrand() % m_oData.size());
//獲取與客戶端通訊的socket
QTcpSocket* pClientConnection = m_pTcpServer->nextPendingConnection();
//從客戶端讀資料
QString sReadData = pClientConnection->readAll();
qDebug() << "SimpleTcpSocketServerDemo::readDataFromClient " << pClientConnection;
//與客戶端寫資料
qDebug() << "SimpleTcpSocketServerDemo::writeDataToClient " << sWriteData;
pClientConnection->write(sWriteData.toUtf8());
// //與客戶端斷開連線
// connect(pClientConnection, &QTcpSocket::disconnected, this, &SimpleTcpSocketServerDemo::deleteLater);
// pClientConnection->disconnectFromHost();
}
void SimpleTcpSocketServerDemo::displayError(QAbstractSocket::SocketError socketError)
{
qDebug() << "SimpleTcpSocketServerDemo::displayError " << socketError;
}
void testSimpleTcpSocketServerDemo()
{
//這樣寫會記憶體洩漏,如此寫方便測試。
SimpleTcpSocketServerDemo* pSimpleTcpSocketServer = new SimpleTcpSocketServerDemo;
}
客戶端
標頭檔案
#pragma once
//////////////////////////////////////////////////////////////////////////
//客戶端
#include <QObject>
#include <QAbstractSocket>
#include <QRunnable>
#include <QThreadPool>
class QTcpSocket;
class SimpleTcpSocketClientDemo : public QObject
{
Q_OBJECT
public:
SimpleTcpSocketClientDemo();
private slots:
void connected();
void readyRead();
void error(QAbstractSocket::SocketError socketError);
private:
QTcpSocket* m_pTcpSocket;
};
class ClientRunnable : public QRunnable
{
public:
void run();
};
void testSimpleTcpSocketClientDemo();
原始檔
#include "SimpleTcpSocketClientDemo.h"
#include <QTcpSocket>
#include <QDebug>
SimpleTcpSocketClientDemo::SimpleTcpSocketClientDemo()
{
//1. 建立TCP套接字物件
m_pTcpSocket = new QTcpSocket(this);
//2. 已連線、資料可讀、失敗訊號連線
connect(m_pTcpSocket, &QTcpSocket::connected, this, &SimpleTcpSocketClientDemo::connected);
connect(m_pTcpSocket, &QIODevice::readyRead, this, &SimpleTcpSocketClientDemo::readyRead);
typedef void (QAbstractSocket::*QAbstractSocketErrorSignal)(QAbstractSocket::SocketError);
connect(m_pTcpSocket, static_cast<QAbstractSocketErrorSignal>(&QTcpSocket::error), this, &SimpleTcpSocketClientDemo::error);
//3. 與伺服器端建立連線
m_pTcpSocket->connectToHost("127.0.0.1", 8888);
//4. 同步處理-等待資料可讀
m_pTcpSocket->waitForReadyRead();
}
void SimpleTcpSocketClientDemo::readyRead()
{
qDebug() << "SimpleTcpSocketClientDemo::readyRead " << m_pTcpSocket->readAll();
}
void SimpleTcpSocketClientDemo::connected()
{
qDebug() << "SimpleTcpSocketClientDemo::connected successfully";
}
void SimpleTcpSocketClientDemo::error(QAbstractSocket::SocketError socketError)
{
qDebug() << "SimpleTcpSocketClientDemo::error " << socketError;
}
void ClientRunnable::run()
{
//這樣寫會記憶體洩漏,如此寫方便測試。
SimpleTcpSocketClientDemo* pSimpleTcpSocketClient = new SimpleTcpSocketClientDemo;
}
#define CLINET_COUNT 2000 //客戶端的數量
void testSimpleTcpSocketClientDemo()
{
QTime oTime;
oTime.start();
//同步執行緒池的方式模擬多個客戶端與伺服器端互動
for (int nIndex = 0; nIndex < CLINET_COUNT; ++nIndex)
{
ClientRunnable* pRunnable = new ClientRunnable;
pRunnable->setAutoDelete(false);
QThreadPool::globalInstance()->start(pRunnable);
}
QThreadPool::globalInstance()->waitForDone(30 * 1000);
qDebug() << "connect count: " << CLINET_COUNT << "total time: " << (double)oTime.elapsed() / double(1000) << "s";
}
測試結果-單執行緒
伺服器端
SimpleTcpSocketServerDemo::readDataFromClient QTcpSocket(0x2f27f308)
SimpleTcpSocketServerDemo::writeDataToClient "You will feel hungry again in another hour."
SimpleTcpSocketServerDemo::readDataFromClient QTcpSocket(0x2eb61cf0)
SimpleTcpSocketServerDemo::writeDataToClient "You might have mail."
.........
客戶端
SimpleTcpSocketClientDemo::connected successfully
SimpleTcpSocketClientDemo::readyRead "You might have mail."
SimpleTcpSocketClientDemo::connected successfully
SimpleTcpSocketClientDemo::readyRead "You will feel hungry again in another hour."
.........
connect count: 2000 total time: 3.926 s
通過測試輸出,可以看到伺服器端與客戶端建立了正確的連線並且資料交換。
- 實際測試資料:2000個連線,耗時4s左右,CPU使用率10%左右。
通過閱讀伺服器端,發現單執行緒處理客戶端的連線效率較低。伺服器端可修改為多執行緒處理客戶端連線,程式碼如下:
伺服器端-多執行緒
標頭檔案
#pragma once
//////////////////////////////////////////////////////////////////////////
//伺服器端-多執行緒處理客戶端連線
#include <QTcpServer>
#include <QThread>
class MultiThreadTcpSocketServerDemo : public QTcpServer
{
public:
MultiThreadTcpSocketServerDemo();
//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.
virtual void incomingConnection(qintptr handle);
private:
QStringList m_oData;
};
//處理執行緒
class ServerHandleThread : public QThread
{
Q_OBJECT
public:
ServerHandleThread(qintptr handle, const QString& sWriteData);
virtual void run();
private:
qintptr m_nHandle;
QString m_sWriteData;
};
void testMultiThreadTcpSocketServerDemo();
//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.
virtual void incomingConnection(qintptr handle); //該虛擬函式是重點
原始檔
#include "MultiThreadTcpSocketServerDemo.h"
#include <QDebug>
#include <QTcpSocket>
MultiThreadTcpSocketServerDemo::MultiThreadTcpSocketServerDemo()
{
//初始換原始資料
m_oData << tr("You've been leading a dog's life. Stay off the furniture.")
<< tr("You've got to think about tomorrow.")
<< tr("You will be surprised by a loud noise.")
<< tr("You will feel hungry again in another hour.")
<< tr("You might have mail.")
<< tr("You cannot kill time without injuring eternity.")
<< tr("Computers are not intelligent. They only think they are.");
}
void MultiThreadTcpSocketServerDemo::incomingConnection(qintptr handle)
{
//獲取服務端資料
QString sWriteData = m_oData.at(qrand() % m_oData.size());
qDebug() << "MultiThreadTcpSocketServerDemo::incomingConnection" << handle;
ServerHandleThread* pThread = new ServerHandleThread(handle, sWriteData);
connect(pThread, &ServerHandleThread::finished, pThread, &ServerHandleThread::deleteLater);
pThread->start();
}
ServerHandleThread::ServerHandleThread(qintptr handle, const QString& sWriteData)
:m_sWriteData(sWriteData), m_nHandle(handle)
{
}
void ServerHandleThread::run()
{
//1. 建立與客戶端通訊的TCP套接字
QTcpSocket oTcpSocket;
if (!oTcpSocket.setSocketDescriptor(m_nHandle))
{
qDebug() << "oTcpSocket.setSocketDescriptor error";
return;
}
//2. 向客戶端寫資料
qDebug() << "MultiThreadTcpSocketServerDemo::readDataFromClient" << &oTcpSocket;
qDebug() << "MultiThreadTcpSocketServerDemo::writeDataToClient" << m_sWriteData;
oTcpSocket.write(m_sWriteData.toUtf8());
oTcpSocket.disconnectFromHost();
oTcpSocket.waitForDisconnected();
}
void testMultiThreadTcpSocketServerDemo()
{
//1. 建立伺服器端套接字
MultiThreadTcpSocketServerDemo* m_pTcpServer = new MultiThreadTcpSocketServerDemo();
//2. 啟動服務端
if (!m_pTcpServer->listen(QHostAddress::Any, 8888))
{
qDebug() << "m_pTcpServer->listen() error";
}
}
測試結果-多執行緒
客戶端
SimpleTcpSocketClientDemo::connected successfully
SimpleTcpSocketClientDemo::readyRead "You might have mail."
SimpleTcpSocketClientDemo::connected successfully
SimpleTcpSocketClientDemo::readyRead "You will feel hungry again in another hour."
.........
connect count: 2000 total time: 6.403 s
- 實際測試資料:2000個連線,耗時6.5s左右,CPU使用率20%左右。
可見伺服器端採用多執行緒可充分利用CPU,但是頻繁的切換執行緒也會效能下降(耗時)。
通過本案例的程式碼實現可以瞭解TCP伺服器端/客戶端程式設計的基本思路。並且驗證了伺服器端單執行緒和多執行緒的效率對比。
在windows中,可通過IOCP提高服務期端的效率,後面會詳細講解。