Qt多線程socket服務器開發

前言

閒話少說,因需要寫一個上位機程序,本來我是打算用純C++開發的,奈何我不是程序員科班出身,對多線程+win socket那一套不是很熟悉,平時寫寫簡單的測試案例還可以,但是要我寫出一個能穩定運行的落地的項目,純C++開發對我來說太難了。其實Qt我也不是很熟悉,但Qt畢竟大多模塊幫你封裝好了,適用qt可以大大縮短開發週期。

qt多線程

網上關於qt多線程的大多都是繼承QThread,然後重寫run函數。這種方式其實還是應用c++共享內存多線程通信那一套思想,實現起來比較複雜。Qt官網推薦使用繼承QObject,然後把對象movetToThread的方式,充分利用Qt的信號槽機制進行主線程和子線程之間的通信,這種方式有點像Go語言的channel機制,非常方便且簡單。使用Qt的多線程需記住以下幾個思想:

1. 線程所有者

子線程是在主線程中創建的,應該使用主線程控制子線程的生命週期,比如在主線程中結束子線程(quit),而不是在子線程中自己結束自己。

2. 線程資源

Qt文檔特別強調,資源只屬於創建它的那個線程。

代碼實現

1. 服務端頭文件

基本思想:QTcpServer有一個newConnection信號,監聽時,每次有新的連接都會發出這個信號。我們建立一個槽函數來處理這個信號,當有新的連接時,把新的客戶端socket添加到服務器的vector中,服務器管理所有客戶端socket。當有客戶端斷開連接時,客戶端socket會發出disconnected信號,我們建立槽函數來處理這個信號。具體的服務器頭文件如下:

typedef struct tagMsg
{
	int port;
	QString ip;
	QByteArray buf;
	int size;//! buf的字節數
}Msg;

typedef struct tagClient
{
	int port;
	QString ip;
	QTcpSocket* sock;
}Client;

class CTcpServer : public QObject
{
	Q_OBJECT

public:
	CTcpServer(QObject *parent = nullptr);
	~CTcpServer();

signals:
	//! 有新的客戶端
	void signal_newClient(const QString ip, const int port);
	//! 發送給客戶端的消息
	void signal_newMsg(const Msg);
	//! 客戶端斷開連接
	void signal_disConncet(const QString ip, const int port);

public slots:
	void init();
	//! 監聽
	void onConnect(const QString ip, const int port);
	void recvData();
	void sendData(Msg);
	
private slots:
	//! 處理新的客戶端連接
	void newConnect();
	//! 處理客戶端斷開連接
	void onDisConnect();

private:
	QTcpServer* _server;
	QList<Client> _clients;
	QTcpSocket* _currentSock;
};

這裏需要重點說明一下那個init槽函數。 前面有說過,Qt資源只屬於創建它的那個線程。QTcpServer不能在構造函數中初始化,因爲這個CCtcpServer是在主線程中創建的,即屬於主線程,如果在構造函數中創建QTcpServer,則其也屬於主線程,但是那些槽函數屬於子線程,在子線程中直接調用主線程的資源是不允許的(可以想象一下這個被允許的話,如果主線程創建了很多子線程,每個子線程都可以直接調用主線程的資源,那麼主線程的資源是不是就不安全了)。所以,要想在子線程中調用QTcpServer,那麼這個tcp必須在子線程中建立,這就要求在子線程start的時候創建這個資源。

2. 服務端實現代碼

CTcpServer::CTcpServer(QObject *parent)
	: QObject(parent)
{
	_server = nullptr;
	_currentSock = nullptr;
}

CTcpServer::~CTcpServer()
{
	if (nullptr != _server)
	{
		delete _server;
		_server = nullptr;
	}
}

void CTcpServer::init()
{
	if (_server != nullptr)
	{
		delete _server;
		_server = nullptr;
	}
	else
	{
		_server = new QTcpServer;
	}
	
}

void CTcpServer::onConnect(const QString ip, const int port)
{
	if (_server != nullptr)
	{
		if (!_server->isListening())
		{
			QHostAddress addr(ip);
			_server->listen(addr, port);
			connect(_server, &QTcpServer::newConnection, this, &CTcpServer::newConnect);
		}
	}
}

void CTcpServer::newConnect()
{
	_currentSock = _server->nextPendingConnection();
	//! 獲取遠端客戶端ip和端口號
	QString ip = _currentSock->peerAddress().toString();
	int port = _currentSock->peerPort();
	emit signal_newClient(ip, port);
	Client tmp;
	tmp.ip = ip;
	tmp.port = port;
	tmp.sock = _currentSock;
	_clients.append(tmp);
	connect(_currentSock, &QTcpSocket::readyRead, this, &CTcpServer::recvData);
	connect(_currentSock, &QTcpSocket::disconnected, this, &CTcpServer::onDisConnect);
}

void CTcpServer::recvData()
{
	for (QList<Client>::iterator it = _clients.begin(); it != _clients.end(); ++it)
	{
		if ((it->sock)->isReadable())
		{
			QByteArray buf = (it->sock)->readAll();
			if (!buf.isEmpty())
			{
				//! 獲取遠端socket的ip和端口號
				QString ip = (it->sock)->peerAddress().toString();
				int port = (it->sock)->peerPort();
				Msg msg;
				msg.buf = buf;
				msg.ip = ip;
				msg.port = port;
				emit signal_newMsg(msg);
			}
		}
	}
}

void CTcpServer::onDisConnect()
{
	for (QList<Client>::iterator it = _clients.begin(); it != _clients.end(); ++it)
	{
		if (QAbstractSocket::UnconnectedState == it->sock->state())
		{
			emit signal_disConncet(it->ip, it->port);
			it = _clients.erase(it, it + 1);
			it--;
		}
	}
}

void CTcpServer::sendData(Msg msg)
{
	qDebug() << "sendData\n";
	QString ip = msg.ip;
	int port = msg.port;
	for (QList<Client>::iterator it = _clients.begin(); it != _clients.end(); ++it)
	{
		if (it->ip == ip && it->port == port)
		{
			
			qDebug() << "發送字節:" << it->sock->write(msg.buf) << "\n";
		}
	}
}

 3. 主窗口

在主窗口中我們把子線程的started信號與CTcpServer的init信號連接,確保QTcpServer在子線程中被創建,如下所示。

	_tcp_server = new CTcpServer;
	_server_thread = new QThread;
	_tcp_server->moveToThread(_server_thread);
	connect(_server_thread, &QThread::started, _tcp_server, &CTcpServer::init);
	_server_thread->start();

最終的界面如下圖所示 。

完整程序如下連接:

https://download.csdn.net/download/LI15951364431/12309265

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章