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.rhkb.cn/news/286489.html

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

相关文章

【Java常用API】带目的的爬虫

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏 …

学习SpringBoot笔记--知识点(1)

目录 SpringBoot介绍 创建一个最基础的springbooot项目 使用Spring Initializr创建springboot项目 Spring Boot 自动配置机制 SpringBoot常用注解 1.组件注册 2.条件注解 3.属性绑定 SpringBoot自动配置流程​编辑 学习SpringBoot的方法 ​编辑 SpringBoot日志配置…

西井科技与安通控股签署战略合作协议 共创大物流全新生态

2024年3月21日&#xff0c;西井科技与安通控股在“上海硅巷”新象限空间正式签署战略合作框架协议。双方基于此前在集装箱物流的成功实践与资源优势&#xff0c;积极拓展在AI数字化产品、新能源自动驾驶解决方案和多场景应用&#xff0c;以及绿色物流链等领域的深度探索、强强联…

视频汇聚平台EasyCVR启用图形验证码之后调用login接口的操作方法

视频综合管理平台EasyCVR视频监控系统支持多协议接入、兼容多类型设备&#xff0c;平台可以将区域内所有部署的监控设备进行统一接入与集中汇聚管理&#xff0c;实现对监控区域的实时高清视频监控、录像与存储、设备管理、云台控制、语音对讲、级联共享等&#xff0c;在监控中心…

Windows如何搭建 ElasticSearch 集群

单机 & 集群 单台 Elasticsearch 服务器提供服务&#xff0c;往往都有最大的负载能力&#xff0c;超过这个阈值&#xff0c;服务器 性能就会大大降低甚至不可用&#xff0c;所以生产环境中&#xff0c;一般都是运行在指定服务器集群中。 除了负载能力&#xff0c;单点服务器…

【Unity】从0到1的横版2d制作笔记-DAY3

确定碰撞体积 选择rigidbody2d&#xff0c;创建player重力 创建player碰撞体积 创建瓦片地图碰撞体积 使平台变成一个整体 ​​​​​ 设置Body Type为Static&#xff08;避免平台也因为重力影响下落&#xff09; 回到Player&#xff0c;在Rigidbody2D中设置为冻结旋转 Player设…

2016年认证杯SPSSPRO杯数学建模C题(第二阶段)如何有效的抑制校园霸凌事件的发生全过程文档及程序

2016年认证杯SPSSPRO杯数学建模 C题 如何有效的抑制校园霸凌事件的发生 原题再现&#xff1a; 近年来&#xff0c;我国发生的多起校园霸凌事件在媒体的报道下引发了许多国人的关注。霸凌事件对学生身体和精神上的影响是极为严重而长远的&#xff0c;因此对于这些情况我们应该…

SQL映射文件

一、SQL映射的xml文件 1.1 mapper元素 二、select 三、别名与Java映射 四、resultMap 啊

Java毕业设计 基于SSM网上二手书店系统

Java毕业设计 基于SSM网上二手书店系统 SSM jsp 网上二手书店系统 功能介绍 用户&#xff1a;首页 图片轮播 图书查询 图书分类显示 友情链接 登录 注册 图书信息 图片详情 评价信息 加入购物车 资讯信息 资讯详情 个人中心 个人信息 修改密码 意见信息 图书收藏 已经付款 邮…

Golang基础知识(笔记迁移)

golang 变量作用域 局部作用域&#xff1a;代码块、函数内的全局作用域&#xff1a;顶层作用域&#xff0c;代码块外的就是全局&#xff0c;如果变量名大写&#xff0c;则改变量整个程序都可以使用。 类型断言 golang的类型断言在变量后加上.(type)&#xff0c;如果类型断言…

怿星科技Neptune CHT-S测试系统,让智能座舱测试更加高效便捷

随着汽车“智能化”浪潮的推进&#xff0c;汽车的智能化水平正在持续刷新行业认知。在这股智能化潮流中&#xff0c;智能座舱作为客户体验最为直观的部分&#xff0c;其重要性不言而喻。倘若座舱设备出现死机、黑屏、卡顿等现象&#xff0c;都将对客户的使用体验产生非常大的影…

xmes前端问题,给form表单赋值后,再次从表单拿不到该值

xmes前端&#xff0c;给form表单赋值后&#xff0c;再次从表单拿不到该值&#xff0c;但页面可以展示 赋值 this.$[frm-main].$$([namefilm_num]).value filmNum ; 获取表单的值&#xff0c;这里拿不到之前赋的值 const reqData this.$[frm-main].serializeMyForm(); 原因&…

2.7、创建列表(List)

概述 列表是一种复杂的容器&#xff0c;当列表项达到一定数量&#xff0c;内容超过屏幕大小时&#xff0c;可以自动提供滚动功能。它适合用于呈现同类数据类型或数据类型集&#xff0c;例如图片和文本。在列表中显示数据集合是许多应用程序中的常见要求&#xff08;如通讯录、…

Copilot for Microsoft365使用体验

注&#xff1a;本文来自粉丝投稿。 上周进行了留言抽奖&#xff0c;粉丝获得了一周体验资格&#xff0c;并写下了使用体验&#xff0c;特此赠送1个月copilot使用资格。 留言赠送copilot for Microsoft365一周体验卡 每周一Copilot for Microsoft 365留言赠送 上周一通过陈老…

【CPP】智能指针

引言 智能指针是RAII思想的体现&#xff0c;有时候程序抛异常导致指针指向的内存资源未释放&#xff0c;造成内存泄漏&#xff0c;这时就需要用到智能指针&#xff0c;它可以出作用域自动调用析构函数释放内存资源 内存泄漏 什么是内存泄漏 什么是内存泄漏&#xff1a;内存泄…

基于GA优化的CNN-LSTM-Attention的时间序列回归预测matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1卷积神经网络&#xff08;CNN&#xff09;在时间序列中的应用 4.2 长短时记忆网络&#xff08;LSTM&#xff09;处理序列依赖关系 4.3 注意力机制&#xff08;Attention&#xff09; 5…

9.串口通信

串口基本认识 串行接口简称串口&#xff0c;也称串行通信接口或串行通讯接口&#xff08;通常指COM接口&#xff09;&#xff0c;是采用串行通信方 式的扩展接口。串行接口&#xff08;Serial Interface&#xff09;是指数据一位一位地顺序传送。其特点是通信线路简 单&#x…

【网络爬虫】(2) requests模块,案例:网络图片爬取,附Python代码

1. 基本原理 1.1 requests 模块 requests 是 Python 中一个非常流行的 HTTP 客户端库&#xff0c;用于发送所有的 HTTP 请求类型。它基于 urllib&#xff0c;但比 urllib 更易用。 中文文档地址&#xff1a;Requests: 让 HTTP 服务人类 — Requests 2.18.1 文档 &#xff0…

cookie、localStorage、sessionStorage 详解

目录 cookie 是什么&#xff1f; cookie 不可以跨域请求 cookie 的属性 会话cookie & 永久性cookie cookie 禁用 cookie 的应用 sessionStorage 是什么&#xff1f; 失效时间 存储内容的类型 存储的大小 存储的位置 sessionStorage 的应用 localStorage 是什么…

GTC 2024 火线评论:DPU 重构文件存储访问

编者按&#xff1a;英伟达2024 GTC 大会上周在美国加州召开&#xff0c;星辰天合 CTO 王豪迈在大会现场参与了 GPU 与存储相关的最新技术讨论&#xff0c;继上一篇《GTC 2024 火线评论&#xff1a;GPU 的高效存储利用》之后&#xff0c;这是他发回的第二篇评论文章。 上一篇文章…