Qt实现简易的多线程TCP服务器(支持多个客户端连接)附源码

目录

一.UI界面的设计

二.服务器的启动

三.实现自定义的TcpServer类

1.在widget中声明自定义TcpServer类的成员变量

2.在TcpServer的构造函数中对于我们声明的m_widget进行初始化,m_widget我们用于后续的显示消息等,说白了就是主界面的更新显示等

四.实现自定义的TcpSocket类

1.TcpSocket.h   先忽略掉信号与槽函数,关注构造函数与qintptr类型的 m_sockDesc

五.实现自定义线程类

1.主要关注run函数,其中run函数是继承QThread中的虚函数,需要我们进行重写

2.实现某个客户端断开连接时通过信号与槽让主界面改变

3.实现有新的客户端连接时主界面更新

六.服务器收到多客户端消息进行显示的流程实现

七.服务器发送消息给某个客户端流程

八.服务器发送信息后,要在主页面信息消息更新显示的流程

注意:

效果演示:

源码下载地址:


在初学Qt 中Tcp服务器与客户端的时候,发现每次服务器只能和最后一个连接的客户端进行通信,因为没有用到多线程以及TcpServer中虚函数incomingConnection(),当新的客户端连接的时候,会自动调用incomingConnection函数,在里面产生新的线程来处理通信。

以下来讲讲这个简易的多线程Tcp服务器的实现

一.UI界面的设计

其中包括2个Label,一个LineEdit,两个pushbutton,上面是一个TextBrowser用于服务器显示通信记录,下面一个TextEdit用于发送信息。这样一个简单的界面就搭建完成了~~~

二.服务器的启动

首先我们肯定需要实现点击启动服务器按钮来启动服务器

1.在界面中右击启动按钮 ----> 转到槽

2.实现逻辑,这里直接放代码,其中serverisworking 是我在widget.h 中声明的一个bool类型,用于判断服务器是否启动,同时更改按钮的文本显示内容,以及弹出对话框提示。

//点击开始启动服务器
void Widget::on_pushButton_StartServer_clicked()
{//如果服务器没有启动if (!this->serverisworking) {if(this->m_tcpserver->listen(QHostAddress::Any,ui->lineEdit_Port->text().toUShort())){QMessageBox::information(this,"成功!","启动成功!");ui->pushButton_StartServer->setText("关闭服务器");this->serverisworking = true;}else {QMessageBox::critical(this,"失败!","服务器启动失败请检查设置!");}}//如果服务器正在启动else if(this->serverisworking) {m_tcpserver->close();if(!m_tcpserver->isListening()){ui->pushButton_StartServer->setText("启动服务器");this->serverisworking = false;QMessageBox::information(this,"提示","关闭成功!");}else {QMessageBox::critical(this,"错误","关闭失败,请重试!");return;}}
}

三.实现自定义的TcpServer类

1.首先我们搞清楚,这个类是负责干嘛的?这个类我们要继承QTcpServer,重写虚函数incomingConnetion

2.这是头文件,如果有报错,注意看使用#pragma once没有,因为可能涉及到头文件重复包含

//tcpserver.h
#pragma once
#ifndef TCPSERVER_H
#define TCPSERVER_H#include <QObject>
#include<QTcpServer>#include <widget.h>
#include"serverthread.h"class TcpServer : public QTcpServer
{Q_OBJECT
public:explicit TcpServer(QObject *parent = nullptr);private://重写虚函数void incomingConnection(qintptr sockDesc);private://用来保存连接进来的套接字,存到ComboBox中QList<qintptr> m_socketList;//再包含一个widget对象Widget *m_widget;};#endif // TCPSERVER_H

1.在widget中声明自定义TcpServer类的成员变量

//widget.h
#pragma once
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include<QMessageBox>
#include"tcpserver.h"namespace Ui {
class Widget;
}class TcpServer;class Widget : public QWidget
{Q_OBJECTpublic:explicit Widget(QWidget *parent = nullptr);~Widget();public://当有新连接的时候在页面上显示void showNewConnection(qintptr sockDesc);//断开时显示void showDisconnection(qintptr sockDesc);//当服务器发送消息后,通知窗口更新消息void UpdateServerMsg(qintptr Desc,const QByteArray &msg);private slots://按钮被触发void on_pushButton_StartServer_clicked();void on_pushButton_Trans_clicked();public slots://当服务器收到客户端发送的消息后,更新消息void RecvMsg(QString Desc,const QByteArray &msg);signals :void  sendData(qintptr Desc ,const QByteArray &msg);private:Ui::Widget *ui;TcpServer *m_tcpserver;bool serverisworking;
};#endif // WIDGET_H

其他的我们先不去讨论,这里我们就声明一个TcpServer类型的m_tcpserver 以及一个bool类型的serverisworking用来判断服务器是否在工作

2.在TcpServer的构造函数中对于我们声明的m_widget进行初始化,m_widget我们用于后续的显示消息等,说白了就是主界面的更新显示等

TcpServer::TcpServer(QObject *parent) : QTcpServer(parent)
{m_widget = dynamic_cast<Widget *>(parent);}

那么,问题来了,我们要重写TcpServer的incomingConnection函数,里面要涉及到线程,那么我们需要去写一个自定义的线程类

//当有新的连接进来的时候会自动调用这个函数,不需要你去绑定信号槽
void TcpServer::incomingConnection(qintptr sockDesc)
{//将标识符保存进listm_socketList.append(sockDesc);//产生线程用于通信ServerThread *thread = new ServerThread(sockDesc);//窗口中显示有新的连接m_widget->showNewConnection(sockDesc);//线程中发出断开tcp连接,触发widget中显示断开connect(thread, &ServerThread::disconnectTCP, this,[=]{m_widget->showDisconnection(sockDesc);});//当socket 底层有readyread信号的时候  -> 发送socket_getmsg信号  -> 发送socket_getmsg_thread//将socket_getmsg_thread 与 widget中 RecvMsg 绑定,RecvMsg 用于处理将收到的消息进行显示connect(thread,&ServerThread::socket_getmsg_thread,this->m_widget,&Widget::RecvMsg);//当点击发送的时候-> 产生一个SendData 信号  -> 调用线程中SendDataSlot函数用于发送sendData信号来使socket来发送消息connect(this->m_widget,&Widget::sendData,thread,&ServerThread::sendDataSlot);//当服务器给客户端发送下消息后,会产生一个writeover信号-> 触发线程发送writeover信号给 Tcpserver -> Tcpserver中widget更新消息connect(thread,&ServerThread::writeover,[=](qintptr Desc,const QByteArray &msg){m_widget->UpdateServerMsg(Desc,msg);});thread->start();
}

我们要实现线程类,线程的工作是需要每个线程里面都有一个不同的Tcpsocket类实例,但是我们要在不同的线程里面 工作不同的socket,那么我们可以在一个线程中 去使用套接字标识符(socketDesc)去区分不同的套接字,然后服务器也可以通过不同的套接字标识符去进行与不同的套接字进行通信,所以我们需要先去实现一个自定义的TcpSocket类

四.实现自定义的TcpSocket类

1.TcpSocket.h   先忽略掉信号与槽函数,关注构造函数与qintptr类型的 m_sockDesc

为什么是qintptr类型呢,我们查看官方文档,使用qintpt类型作为参数更方便,

#pragma once
#ifndef SERVERSOCKET_H
#define SERVERSOCKET_H#include <QObject>
#include<QTcpSocket>class ServerSocket : public QTcpSocket
{Q_OBJECT
public:explicit ServerSocket(qintptr socketDesc,QObject *parent = nullptr);signals:void socket_getmsg(QString Desc, const QByteArray &msg);void writeover(qintptr Desc,const QByteArray &msg);public slots:void sendData(qintptr Desc, const QByteArray &data);private:qintptr m_sockDesc;
};#endif // SERVERSOCKET_H

五.实现自定义线程类

1.主要关注run函数,其中run函数是继承QThread中的虚函数,需要我们进行重写

//serverthread.h
#pragma once
#ifndef SERVERTHREAD_H
#define SERVERTHREAD_H#include <QObject>#include <QThread>
#include<serversocket.h>class ServerThread : public QThread
{Q_OBJECT
public://构造函数初始化套接字标识符explicit ServerThread(qintptr sockDesc,QObject *parent = nullptr);void run() override;~ServerThread();signals:void disconnectTCP(qintptr m_sockDesc);void sendData(qintptr Desc, const QByteArray& msg);void socket_getmsg_thread(QString Desc,const QByteArray &msg);void  writeover(qintptr Desc,const QByteArray &msg);public  slots:void sendDataSlot(qintptr Desc, const QByteArray& msg);private:qintptr m_socketDesc;ServerSocket *m_socket;};#endif // SERVERTHREAD_H

2.实现某个客户端断开连接时通过信号与槽让主界面改变

1)我们在run函数中,其实就是对某个对应的用来通信套接字运行一个线程,所以我们在run中,先对m_socket进行初始化,将自身的m_socketDesc 作为参数传给m_socket的有参构造。并且使用TcpSocket的setSocketDescriptor方法对m_socket进行绑定标识符,这样我们每个线程内工作的套接字都是不同的

  m_socket = new ServerSocket(this->m_socketDesc);//绑定套接字标识符绑定给自定义套接字对象if (!m_socket->setSocketDescriptor(this->m_socketDesc)) {return ;}

2)在线程中,当该线程中的套接字断开时,底层会发射出disconnected信号,我们线程可以此信号与一个用来发射信号的槽函数绑定起来,实现当套接字发送disconnect信号的时候,线程发射出一个disconnectTcp这样一个自定义信号通知服务器套接字断开,server在调用widget成员的方法实现在主界面中显示断开连接

 //run()中://当套接字断开时,发送底层的disconnected信号connect(m_socket, &ServerSocket::disconnected, this, [=]{//此信号可以出发server的槽函数然后再调用widget中combobox清除该socketDescemit disconnectTCP(this->m_socketDesc);//让该线程中的套接字断开连接m_socket->disconnectFromHost();//断开连接//线程退出this->quit();

//incommingConnection中//线程中发出断开tcp连接,触发widget中显示断开connect(thread, &ServerThread::disconnectTCP, this,[=]{m_widget->showDisconnection(sockDesc);});
//widget.cpp中
//用以显示连接断开
void Widget::showDisconnection(qintptr sockDesc)
{ui->textBrowser_ServerMess->append(QString::number(sockDesc)+"断开了连接");//通过信号传递的标识符,将其删除int index = ui->comboBox_CilentID->findData(sockDesc);ui->comboBox_CilentID->removeItem(index);
}

3.实现有新的客户端连接时主界面更新

当有新的客户端连接的时候,会自动调用server中的incommingConnect函数,直接在此函数中调用widget->showNewconnection函数

//incomingConnection函数中://窗口中显示有新的连接m_widget->showNewConnection(sockDesc);
//widget.cpp
void Widget::showNewConnection(qintptr sockDesc)
{ui->textBrowser_ServerMess->append("有新的连接!,新接入"+QString::number(sockDesc));ui->comboBox_CilentID->addItem(QString("%1").arg(sockDesc), sockDesc);
}

 通过这两个连接就可以直接实现有新的客户端连接时主界面更新。

六.服务器收到多客户端消息进行显示的流程实现

//serversocket.cpp
ServerSocket::ServerSocket(qintptr socketDesc,QObject *parent) : QTcpSocket(parent)
{this->m_sockDesc = socketDesc;connect(this,&ServerSocket::readyRead,this,[=]{QString name = QString::number(m_sockDesc);QByteArray msg = readAll();emit socket_getmsg(name,msg);});
}
//serverthread::run()中//套接字发出有消息的信号,然后触发线程中发出有消息的信号connect(m_socket, &ServerSocket::socket_getmsg, this,[=](QString Desc,const QByteArray &msg){emit socket_getmsg_thread(Desc,msg);});
//server.cpp//当socket 底层有readyread信号的时候  -> 发送socket_getmsg信号  -> 发送socket_getmsg_thread//将socket_getmsg_thread 与 widget中 RecvMsg 绑定,RecvMsg 用于处理将收到的消息进行显示connect(thread,&ServerThread::socket_getmsg_thread,this->m_widget,&Widget::RecvMsg);
//widget.cpp
//当客户端发送消息,服务器收到后,显示消息
void Widget::RecvMsg(QString Desc,const QByteArray &msg)
{ui->textBrowser_ServerMess->append(Desc+":"+msg);
}

实现收到客户端消息进行显示

七.服务器发送消息给某个客户端流程

void Widget::on_pushButton_Trans_clicked()
{if(serverisworking){//如果连接个数大于0,发送发送消息的信号if(ui->comboBox_CilentID->count() >0){//发射 发送信号emit sendData( ui->comboBox_CilentID->currentText().toInt(), ui->textEdit_SendMess->toPlainText().toUtf8());qDebug()<<"发送了sendData信号"<<endl;ui->textEdit_SendMess->clear();}}else {QMessageBox::critical(this,"错误","请检查连接");return;}}
//Tcpserver.cpp  incomingConnection中//当点击发送的时候-> 产生一个SendData 信号  -> 调用线程中SendDataSlot函数用于发送sendData信号来使socket来发送消息connect(this->m_widget,&Widget::sendData,thread,&ServerThread::sendDataSlot);
void ServerThread::sendDataSlot(qintptr Desc, const QByteArray &msg)
{emit sendData(Desc, msg);
}
//run()中connect(this,&ServerThread::sendData,m_socket,&ServerSocket::sendData);
void ServerSocket::sendData(qintptr Desc, const QByteArray &msg)
{if (Desc == m_sockDesc && !msg.isEmpty()) {this->write(msg);//发送完毕,发出信号,通知主页面更新聊天框emit writeover(Desc,msg);}
}

八.服务器发送信息后,要在主页面信息消息更新显示的流程

void ServerSocket::sendData(qintptr Desc, const QByteArray &msg)
{if (Desc == m_sockDesc && !msg.isEmpty()) {this->write(msg);//发送完毕,发出信号,通知主页面更新聊天框emit writeover(Desc,msg);}
}
//serverthread.cpp//socket 发送 writeorver 通知线程发送writeover 用来提醒server中的widget更新消息connect(m_socket,&ServerSocket::writeover,this,[=](qintptr Desc, const QByteArray& msg){emit writeover(Desc,msg);});
//server.cpp//当服务器给客户端发送下消息后,会产生一个writeover信号-> 触发线程发送writeover信号给 Tcpserver -> Tcpserver中widget更新消息connect(thread,&ServerThread::writeover,[=](qintptr Desc,const QByteArray &msg){m_widget->UpdateServerMsg(Desc,msg);});
//widget.cpp
//当服务器发送消息后,通知主窗口更新信号
void Widget::UpdateServerMsg(qintptr Desc, const QByteArray &msg)
{ui->textBrowser_ServerMess->append("服务器:"+msg+" to "+QString::number(Desc));
}

注意:

注册自定义信号参数,否则信号槽机制使用时会出现保存

#include "widget.h"
#include <QApplication>int main(int argc, char *argv[])
{qRegisterMetaType<qintptr>("qintptr");QApplication a(argc, argv);Widget w;w.show();return a.exec();
}

效果演示:

源码下载地址:

yuanzhaoyi/My_project at master (github.com)icon-default.png?t=N7T8https://github.com/yuanzhaoyi/My_project/tree/master

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/770412.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

卡尔曼滤波器_3.3

目标 了解卡尔曼滤波在目标跟踪中的应用知道卡尔曼滤波的原理&#xff1a;预测阶段和更新阶段 卡尔曼滤波器&#xff08;Kalman Filter&#xff09;是一种利用线性系统理论和概率统计原理&#xff0c;对含有噪声的动态系统进行状态估计的最优滤波器。它由匈牙利裔美国电气工程…

web前端之3D标签动画、指定范围的随机数、动态设置css变量、文档片段对象、反向动画

MENU 效果图htmlJavaScriptstyle 效果图 html <div class"container"></div>JavaScript // 祝词 var words [健康码常绿,股票飙红,生意兴隆,财源广进,心想事成,永远十八,身体健康,大富大贵,大吉大利,万事如意,美梦成真,吉祥如意,鸿运当头,五福临门,吉…

正式发布:VitePress 1.0 现代化静态站点生成器!

大家好&#xff0c;我是奇兵&#xff0c;今天介绍一下现代化静态站点生成器!&#xff0c;希望能帮到大家。 3 月 21 日&#xff0c; 由 Vue 团队出品的现代化静态站点生成器 VitePress 正式发布 1.0 版本&#xff01;它专为构建快速、以内容为中心的网站而生&#xff0c;能够轻…

Apache Spark

一、Apache Spark 1、Spark简介 Apache Spark是用于大规模数据 (large-scala data) 处理的统一 (unified) 分析引擎。 Spark官网 Spark最早源于一篇论文Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing,该论文是由加州大学柏…

C# 将 Word 转文本存储到数据库并进行管理

目录 前言 1. 创建数据库表格 2. 安装必需的 NuGet 包 3. 转换 Word 文档为文本 4. 将文本存储到数据库 5. 完整示例 前言 C# 是一种通用的编程语言&#xff0c;可以用于开发各种类型的应用程序&#xff0c;包括处理文本和数据库管理。在这篇文章中&#xff0c;我将向您…

问卷调查数据分析指南!掌握方法,精准把握用户需求!

“我们可以用自定义报表、交叉报表、自定义过滤器等放方式进行问卷调查数据分析。“ 问卷调查的过程中&#xff0c;问卷设计、问卷分发、问卷收集可能都不是让我们最头疼的。经过几天的奋战&#xff0c;终于拿到了数据&#xff0c;但是问题也随之而来。收集上来的问卷信息&…

linux操作系统——线程控制+线程封装

1.理解用户级线程 我们前面用到的所有跟线程有关的接口全部都不是系统直接提供的接口&#xff0c;而是原生线程库pthread提供的接口。我们前面谈到了由于用户只认线程&#xff0c;而linux操作系统是通过用轻量级进程模拟线程&#xff0c;并不是真正的线程&#xff0c;所以linu…

C# for/foreach 循环

一个 for 循环是一个允许您编写一个执行特定次数的循环的重复控制结构。 语法 C# 中 for 循环的语法&#xff1a; for ( init; condition; increment ) {statement(s); } 下面是 for 循环的控制流&#xff1a; init 会首先被执行&#xff0c;且只会执行一次。这一步允许您声…

【数据结构】非线性结构——二叉树

文章目录 前言1.树型结构1.1树的概念1.2树的特性1.3树的一些性质1.4树的一些表示形式1.5树的应用2.二叉树 2.1 概念2.2 两种特殊的二叉树2.3 二叉树的性质2.4 二叉树的存储2.5 二叉树的基本操作 前言 前面我们都是学的线性结构的数据结构&#xff0c;接下来我们就需要来学习非…

Stable Diffusion 进阶教程 - 二次开发(制作您的文生图应用)

目录 1. 引言 2. 基于Rest API 开发 2.1 前置条件 2.2 代码实现 2.3 效果演示 2.4 常见错误 3. 总结 1. 引言 Stable Diffusion作为一种强大的文本到图像生成模型&#xff0c;已经在艺术、设计和创意领域引起了广泛的关注和应用。然而&#xff0c;对于许多开发者来说&#xff…

iOS开发进阶(九):OC混合开发嵌套H5应用并互相通信

文章目录 一、前言二、嵌套H5应用并实现双方通信2.1 WKWebView 与JS 原生交互2.1.1 H5页面嵌套2.1.2 常用代理方法2.1.3 OC调用JS方法2.1.4 JS调用OC方法 2.2 JSCore 实现原生与H5交互2.2.1 OC调用H5方法并传参2.2.2 H5给OC传参 2.3 UIWebView的基本用法2.3.1 H5页面嵌套2.3.2 …

Spring Boot | SpringBoo“开发入门“

目录 : 1.SpringBoot的“介绍”SpringBoot”概述” &#xff1a;SpringBoot”简介“SpringBoot的“优点” 2. SpringBoot入门程序环境准备使用 “Maven”方式构建SpringBoot 项目使用“Spring Initializr”方式构建Spring Boot 项目 3. “单元测试” 和“热部署”单元测试热部署…

微服务day06 -- Elasticsearch的数据搜索功能。分别使用DSL和RestClient实现搜索

1.DSL查询文档 elasticsearch的查询依然是基于JSON风格的DSL来实现的。 1.1.DSL查询分类 Elasticsearch提供了基于JSON的DSL&#xff08;Domain Specific Language&#xff09;来定义查询。常见的查询类型包括&#xff1a; 查询所有&#xff1a;查询出所有数据&#xff0c;一…

‘npm‘ 不是内部或外部命令,也不是可运行的程序

npm认识三年了&#xff0c;今天才知道这是node.js的命令 也就是说&#xff0c;想要在cmd里面运行 npm 命令&#xff0c;但就的安装node.js 1. node.js安装 没有安装包的先下载安装包&#xff1a;下载 | Node.js 中文网 (nodejs.cn) 下载之后双击打开&#xff0c;一路安装确…

基于Arduino IDE 野火ESP8266模块 EEPROM 存储开发

一、操作存储器 我们可以使用ESP8266模块的EEPROM&#xff0c;也就是可读可擦存储器&#xff0c;可以掉电不丢失地帮我们存储一些数据。ESP8266微控制器有一个闪存区(Flash memory) 来模拟Arduino的EEPROM。这是微控制器中一个特殊的内存位置&#xff0c;即使在主板关闭后&…

vscode添加gitee

1.创建仓库 2.Git 全局设置 3.初始化仓库 2.1 打开vscode打开需要上传到给git的代码文件 2.2.点击左边菜单第三个的源代码管理->初始化仓库 4.点击加号暂存所有更改 5.添加远程仓库 5.1 添加地址&#xff0c;回车 5.2 填写库名&#xff0c;回车 6.提交和推送 6.1 点击✔提交…

SpringBoot学习之ElasticSearch下载安装和启动(Mac版)(三十一)

本篇是接上一篇Windows版本,需要Windows版本的请看上一篇,这里我们继续把Elasticsearch简称为ES,以下都是这样。 一、下载 登录Elasticsearch官网,地址是:Download Elasticsearch | Elastic 进入以后,网页会自动识别系统给你提示Mac版本的下载链接按钮 二、安装 下载…

【分布式】——CAPBASE理论

CAP&BASE理论 ⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记链接&#x1f449;https://github.com/A-BigTree/tree-learning-notes ⭐⭐⭐⭐⭐⭐ Spring专栏&#x1f449;https://blog.csdn.net/weixin_53580595/category_12279588.html Sprin…

JMeter元件作用域和执行顺序

JMeter元件作用域和执行顺序 元件的基本介绍基本元件总结 作用域的基本介绍作用域的原则元件执行顺序Jmeter第一个案例&#xff1a; Jmeter三个重要组件&#xff08;重点&#xff09;线程组特点线程组分类线程组的属性案例分析 HTTP请求案例一&#xff08;使用HTTP请求路径来传…

基于ArkUI框架开发-ImageKnife渲染层重构

ImageKnife是一款图像加载缓存库&#xff0c;主要功能特性如下&#xff1a; ●支持内存缓存&#xff0c;使用LRUCache算法&#xff0c;对图片数据进行内存缓存。 ●支持磁盘缓存&#xff0c;对于下载图片会保存一份至磁盘当中。 ●支持进行图片变换&#xff1a;支持图像像素…