1. 程式人生 > >Qt網路程式設計之QTCPSocket和QTCPServer例項(二)

Qt網路程式設計之QTCPSocket和QTCPServer例項(二)

設想有如下場景:若干的客戶端與伺服器端建立連線,建立連線後,伺服器端隨機發送字串給客戶端,客戶端列印輸出。該節案例使用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提高服務期端的效率,後面會詳細講解。