VS2022+QT6.7+NetWork(TCP服务器多客户端助手)

发布于:2025-08-30 ⋅ 阅读:(18) ⋅ 点赞:(0)

目录

一、前言

二、代码详解

三、gitee完整项目下载


一、前言

实现的功能:

1、打开TCP服务器监听客户端连接

2、当有多个客户端连接服务器时,TCP服务器会将客户端放到子线程中去,同时在UI中更新,并记录每一个客户端的套接字、线程ID

3、客户端数据的接收,TCP服务器消息广播给客户端

4、选择性的将服务器中已连接的客户端踢出

准备工作:

您需要在VS项目中引用NetWork模块

程序运行效果如下:

二、代码详解

QtWidgetsApplication3.h

#pragma once

#include <QtWidgets/QWidget>
#include "ui_QtWidgetsApplication3.h"
#include <QtNetwork/QTcpSocket>
#include <QtNetwork/QTcpServer>
#include <QtNetwork/QNetworkInterface>
#include <QtNetwork/QHostAddress>
#include <QByteArray>
#include <QThread>
#include <QStringListModel>
#include <QStandardItemModel>

class QtWidgetsApplication3 : public QWidget
{
	Q_OBJECT

public:
	QtWidgetsApplication3(QWidget* parent = nullptr);
	~QtWidgetsApplication3();
	QTcpSocket* tcpsocket;      //客户端对象套接字
	QTcpServer* tcpserver;      //服务器对象
	void open_connect_server(); //打开服务器
	void init();             //初始化
	void new_connect();      //新的客户端连接
	void kick_out();         //服务器踢下线
	void close_server();     //关闭服务器
	void send_data();        //发送数据
	void clear_data();       //清空数据
	
	QStringListModel* clientModel;  //用于ListView的模型
	QStandardItemModel* ItemModel = new QStandardItemModel(this); //存放所有连接的IP+线程
	QMap<QThread*, QTcpSocket*> threadMap; // 用于存储线程与连接信息的映射

public slots:
	void socket_info(QThread*, QString, QTcpSocket*);
	void socket_on_disconnect(QThread*, QString, QTcpSocket*);
	void socket_readAll(QThread*, QString, QTcpSocket*, QByteArray);

private:
	Ui::QtWidgetsApplication3Class ui;
};


class ClientThread : public QObject
{
	Q_OBJECT

public:
	ClientThread(QTcpSocket* socket, QObject* parent = 0);
	~ClientThread();

	QTcpSocket* tcpsocket;// 客户端套接字
	QThread* m_thread;    // 管理的子线程
	QString info;
	void run();
signals:
	void send_socket_info(QThread*, QString, QTcpSocket*);                // 发送客户端信息给UI更新
	void send_socket_on_disconnect(QThread*, QString, QTcpSocket*);       // 发送客户端断开信息给UI更新
	void send_socket_readAll(QThread*, QString, QTcpSocket*, QByteArray); // 发送客户端数据给UI更新

public slots:
	void readAll();       // 获取客户端数据
	void on_disconnect(); // 断开连接
};

QtWidgetsApplication3.cpp

#include "QtWidgetsApplication3.h"


QtWidgetsApplication3::QtWidgetsApplication3(QWidget* parent)
	: QWidget(parent)
{
	ui.setupUi(this);

	//初始化
	init();

	//初始化模型并绑定到ListView
	clientModel = new QStringListModel(this);
	ui.listView->setModel(clientModel); 

	//打开服务器
	connect(ui.open_server, &QPushButton::clicked, this, &QtWidgetsApplication3::open_connect_server);

	//服务器踢线
	connect(ui.pushButton_2, &QPushButton::clicked, this, &QtWidgetsApplication3::kick_out);

	//关闭服务器
	connect(ui.close_server, &QPushButton::clicked, this, &QtWidgetsApplication3::close_server);

	//发送消息给客户端
	connect(ui.pushButton, &QPushButton::clicked, this, &QtWidgetsApplication3::send_data);

	//清除数据
	connect(ui.clear, &QPushButton::clicked, this, &QtWidgetsApplication3::clear_data);
}

// 子线程的构造函数
ClientThread::ClientThread(QTcpSocket* socket, QObject* parent)
	: QObject(parent), tcpsocket(socket), m_thread(new QThread(this))
{
	info = socket->peerAddress().toString() + ":" + QString::number(socket->peerPort()); //获取客户端IP+端口
	connect(socket, &QTcpSocket::readyRead, this, &ClientThread::readAll);          //客户端数据
	connect(socket, &QTcpSocket::disconnected, this, &ClientThread::on_disconnect); //客户端断开连接
	connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater);   //客户端断开时自动安排socket对象删除
	connect(socket, &QTcpSocket::disconnected, m_thread, &QThread::quit);           //线程退出
}

void ClientThread::run()
{
	emit send_socket_info(QThread::currentThread(), info, tcpsocket);
}

// 子线程的析构函数
ClientThread::~ClientThread()
{

}

// 接收数据
void ClientThread::readAll()
{
	emit send_socket_readAll(QThread::currentThread(), info, tcpsocket, tcpsocket->readAll());
}

// 客户端断开连接
void ClientThread::on_disconnect()
{
	emit send_socket_on_disconnect(QThread::currentThread(), info, tcpsocket);
}

QtWidgetsApplication3::~QtWidgetsApplication3()
{

}

TCP_server.cpp

#include "QtWidgetsApplication3.h"

// 1、打开服务器
void QtWidgetsApplication3::open_connect_server()
{
	// 创建服务器
	tcpserver = new QTcpServer(this);

	// 当有新的客户端连接时,newConnection信号被触发
	connect(tcpserver, &QTcpServer::newConnection, this, &QtWidgetsApplication3::new_connect);

	// 监听指定IP+端口
	if (tcpserver->listen(QHostAddress((ui.comboBox->currentText())), ui.lineEdit_port->text().toUShort()))
	{
		QString message = QString("正在监听:%1-%2").arg(ui.comboBox->currentText()).arg(ui.lineEdit_port->text());
		ui.textEdit->append(message);
		ui.open_server->setEnabled(false);
		ui.close_server->setEnabled(true);
	}
}

// 2、客户端新连接
void QtWidgetsApplication3::new_connect()
{
	tcpsocket = tcpserver->nextPendingConnection();     //获取客户端套接字
	QThread* thread = new QThread();                    //创建线程
	ClientThread* client = new ClientThread(tcpsocket); //创建对象
	client->moveToThread(thread);                       //将客户端移动到线程中
	thread->start();                                    //启动线程

	//开启线程,在run中将线程号、客户端IP+端口、Socket套接字发送给UI更新
	connect(thread, &QThread::started, client, &ClientThread::run);

	//更新列表和模型,发送数据更新UI
	connect(client, &ClientThread::send_socket_info, this, &QtWidgetsApplication3::socket_info);

	//客户端主动离线
	connect(client, &ClientThread::send_socket_on_disconnect, this, &QtWidgetsApplication3::socket_on_disconnect);

	//客户端数据接收
	connect(client, &ClientThread::send_socket_readAll, this, &QtWidgetsApplication3::socket_readAll);
}

// 3、关闭服务器
void QtWidgetsApplication3::close_server()
{
	tcpserver->deleteLater();           // 稍后删除自己
	tcpserver->close();                 // 关闭服务器

	// 1. 清理所有线程和Socket连接
	for (auto thread : threadMap.keys()) {
		QTcpSocket* socket = threadMap[thread];
		if (socket) {
			socket->abort(); // 断开连接
			socket->deleteLater(); // 释放Socket资源
		}

		if (thread) {
			thread->quit(); // 退出线程
			thread->wait(); // 等待线程结束
			thread->deleteLater(); // 释放线程资源
		}
	}

	// 2. 清空映射表
	threadMap.clear();

	// 3. 清空ListView的数据模型
	if (ItemModel) {
		ItemModel->clear();
	}

	// 4. 在textEdit中显示服务器关闭信息
	ui.textEdit->append("服务器已关闭,所有连接已清理");
	ui.open_server->setEnabled(true); // 启用打开服务器按钮
	ui.close_server->setEnabled(false); // 禁用关闭服务器按钮
}

// 4、更新列表和模型
void QtWidgetsApplication3::socket_info(QThread* thread,QString info, QTcpSocket* tcpsocket)
{
	threadMap[thread] = tcpsocket; // 保存线程和IP
	QString threadStr = QString("IP: %1 - 线程: %2").arg(info).arg((quintptr)thread, 0, 16);
	QStandardItem* item = new QStandardItem(threadStr);
	
	item->setCheckable(true);         // 设置可勾选属性
	item->setCheckState(Qt::Checked); // 默认勾选
	ItemModel->appendRow(item);
    ui.listView->setModel(ItemModel); 
}

// 5、踢人
void QtWidgetsApplication3::kick_out()
{
	// 遍历所有项,检查哪些被选中
	for (int i = 0; i < ItemModel->rowCount(); ++i) {
		QStandardItem* item = ItemModel->item(i);
		if (item && item->checkState() == Qt::Checked) {
			// 解析线程ID
			QString threadStr = item->text();
			QString threadIdStr = threadStr.split("线程: ").last();
			bool ok;
			quintptr threadId = threadIdStr.toULongLong(&ok, 16);
			if (ok) {
				// 查找对应的线程和socket
				QThread* targetThread = nullptr;
				QTcpSocket* targetSocket = nullptr;

				for (auto thread : threadMap.keys()) {
					if ((quintptr)thread == threadId) {
						targetThread = thread;
						targetSocket = threadMap[thread];
						break;
					}
				}

				if (targetSocket) {
					targetSocket->abort(); // 立即关闭连接,丢弃所有待发送数据
					ui.textEdit->append(QString("已断开连接,线程ID: %1").arg(threadIdStr));
					
					// 清理线程
					if (targetThread) {
						targetThread->quit();
						targetThread->wait();
						targetThread->deleteLater(); //根据需要决定是否删除线程
					}

					// 从模型中移除该项
					ItemModel->removeRow(i);
					// 从映射中移除
					threadMap.remove(targetThread);
					i--; // 因为移除了一行,索引需要调整
				}
			}
		}
	}
}

// 6、客户端主动断开连接
void QtWidgetsApplication3::socket_on_disconnect(QThread* thread, QString info, QTcpSocket* tcpsocket)
{
	// 查找对应的线程
	QThread* targetThread = nullptr;
	for (auto thread : threadMap.keys()) {
		if (threadMap[thread] == tcpsocket) {
			targetThread = thread;
			break;
		}
	}

	if (targetThread) {
		// 从列表中查找并移除对应的项
		QString threadIdStr = QString("%1").arg((quintptr)targetThread, 0, 16);
		for (int i = 0; i < ItemModel->rowCount(); ++i) {
			QStandardItem* item = ItemModel->item(i);
			if (item && item->text().contains(threadIdStr)) {
				ItemModel->removeRow(i);
				break;
			}
		}

		// 清理线程
		targetThread->quit();
		targetThread->wait();
		targetThread->deleteLater();

		// 从映射表中移除
		threadMap.remove(targetThread);

		// 在textEdit中显示信息
		ui.textEdit->append(QString("客户端 %1 主动断开连接,线程ID: %2").arg(info).arg(threadIdStr));
	}
}

// 7、接收数据
void QtWidgetsApplication3::socket_readAll(QThread* thread, QString info, QTcpSocket* tcpsocket, QByteArray data)
{
	// 查找对应的客户端信息(IP和线程ID)
	QString threadIdStr;
	QThread* targetThread = nullptr;

	// 从映射表中查找线程
	for (auto thread : threadMap.keys()) {
		if (threadMap[thread] == tcpsocket) {
			targetThread = thread;
			threadIdStr = QString("%1").arg((quintptr)thread, 0, 16);
			break;
		}
	}

	// 显示接收信息到textEdit
	if (!info.isEmpty() && !threadIdStr.isEmpty()) {
		ui.textEdit->append(QString("[接收] 客户端 %1 (线程ID: %2): %3")
			.arg(info)
			.arg(threadIdStr)
			.arg(QString(data)));
	}
}

// 8、发送数据
void QtWidgetsApplication3::send_data()
{
	// 检查是否有客户端连接
	if (threadMap.isEmpty()) {
		ui.textEdit->append("[广播失败] 没有任何客户端连接");
		return;
	}

	QString sendData=ui.lineEdit->text();
	foreach(QTcpSocket * socket, threadMap.values()) {
		if (socket && socket->state() == QTcpSocket::ConnectedState) {
			socket->write(sendData.toUtf8());
			socket->flush(); //立即发送
		}
	}
	ui.textEdit->append(QString("[广播] 所有客户端"));
}

// 9、初始化
void  QtWidgetsApplication3::init()
{
	// 默认禁用关闭服务器按钮
	ui.close_server->setEnabled(false); 

	// 获取本机所有IP地址,更新到comboBox
	QList<QHostAddress> ipAddresses = QNetworkInterface::allAddresses();
	for (const QHostAddress& address : ipAddresses) {
		if (address.protocol() == QAbstractSocket::IPv4Protocol
			&& address != QHostAddress::LocalHost) {
			ui.comboBox->addItem(address.toString());
		}
	}
}

// 10、清除数据
void QtWidgetsApplication3::clear_data()
{
	ui.textEdit->clear();
}

三、gitee完整项目下载

https://gitee.com/zjq11223344/qt-widgets-application3https://gitee.com/zjq11223344/qt-widgets-application3


网站公告

今日签到

点亮在社区的每一天
去签到