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

 

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