Qt实现简易的多线程TCP服务器(附源码)

news/2024/5/17 7:35:20

目录

一.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.mrgr.cn/p/34546356

相关文章

实验二 数据描述

c语言程序设计——实验报告二 实验项目名称:实验二 数据描述 实验项目类型:验证性 实验日期:2023年3月21日 一、实验目的 1、掌握C语言数据类型,熟悉如何定义一个整型、字符型和实型的变量,以及对它们赋值的方法。 2、掌握不同数据类型之间赋值的规律。 3、学会使用C的有…

三级数据库技术考点(详解!!)

1、 答疑:【解析】分布式数据库系统按不同层次提供的分布透明性有:分片透明性;②位置透明性;③局部映像透明性&#xff0c;位置透明性是指数据分片的分配位置对用户是透明的&#xff0c;用户编写程序时只需 要考虑数据分片情况&#xff0c;不需要了解各分片在各个场地的分配情…

实例、构造函数、原型、原型对象、prototype、__proto__、原型链……

学习原型链和原型对象&#xff0c;不需要说太多话&#xff0c;只需要给你看看几张图&#xff0c;你自然就懂了。 prototype 表示原型对象__proto__ 表示原型 实例、构造函数和原型对象 以 error 举例 图中的 error 表示 axios 抛出的一个错误对象&#xff08;实例&#xff0…

hiveserver2拒绝连接

一、报错内容 二、解决办法 基本都是core-site.xml文件中没做好代理导致的。 在文件中添加如下配置<property><name>hadoop.proxyuser.xxx.hosts</name><value>*</value></property><property><name>hadoop.proxyuser.xxx.gro…

SpringCloud微服务集成Dubbo

1、Dubbo介绍 Apache Dubbo 是一款易用、高性能的 WEB 和 RPC 框架,同时为构建企业级微服务提供服务发现、流量治理、可观测、认证鉴权等能力、工具与最佳实践。用于解决微服务架构下的服务治理与通信问题,官方提供了 Java、Golang 等多语言 SDK 实现。使用 Dubbo 开发的微服…

修改 RabbitMQ 默认超时时间

MQ客户端正常运行&#xff0c;突然就报连接错误&#xff0c; 错误信息写的很明确&#xff0c;是客户端连接超时。 不过很疑虑&#xff0c;为什么会出现连接超时呢&#xff1f;代码没动过&#xff0c;网络也ok&#xff0c;也设置了心跳和重连机制。 最终在官网中找到了答案&am…

vs2022安装和使用教程(详细)

vs2022和vs2019一样强大&#xff0c;C/C&#xff0c;Python&#xff0c;F#&#xff0c;ios&#xff0c;Android&#xff0c;Web&#xff0c;Node.js&#xff0c;Azure&#xff0c;Unity&#xff0c;HTML&#xff0c;JavaScript等开发都可以执行&#xff0c;大家快来使用它吧~ 如…

《自动机理论、语言和计算导论》阅读笔记:p49-p67

《自动机理论、语言和计算导论》学习第4天,p49-p67总结,总计19页。 一、技术总结 1.Deterministic Finite Automata(DFA) vs Nondeterministic Finite Automata(NFA) (1)DFA定义(2)NFA定义A "nonedeterministic" finite automata has the power to be in several s…

HTTPS:原理、使用方法及安全威胁

文章目录 一、HTTPS技术原理1.1 主要技术原理1.2 HTTPS的工作过程1.2.1 握手阶段1.2.2 数据传输阶段 1.3 CA证书的签发流程1.4 HTTPS的安全性 二、HTTPS使用方法三、HTTPS安全威胁四、总结 HTTPS&#xff08;全称&#xff1a;Hyper Text Transfer Protocol over Secure Socket …

如何开始定制你自己的大型语言模型

2023年的大型语言模型领域经历了许多快速的发展和创新,发展出了更大的模型规模并且获得了更好的性能,那么我们普通用户是否可以定制我们需要的大型语言模型呢? 首先你需要有硬件的资源,对于硬件来说有2个路径可以选。高性能和低性能,这里的区别就是是功率,因为精度和消息…

探索跨海大桥新境界:3D可视化技术的魔力

跨海大桥3D可视化,不仅仅是一场技术的革新,更是桥梁建设领域的一次划时代飞跃。跨海大桥3D可视化,不仅仅是一场技术的革新,更是桥梁建设领域的一次划时代飞跃。让我们先来想象一个场景:站在海岸边,望着眼前辽阔的海面,一座雄伟的跨海大桥如巨龙般蜿蜒伸展,连接着两岸。…

cesium加载.tif格式文件

最近项目中有需要直接加载三方给的后缀名tif格式的文件 <script src"https://cdn.jsdelivr.net/npm/geotiff"></script> 或者 yarn add geotiff npm install geotiff 新建tifs.js import GeoTIFF, { fromBlob, fromUrl, fromArrayBuffer } from geotif…

macOS 编译 openssl + libcurl

libcurl库 但是不支持 https 协议 现在加上openssl 来支持https 首先下载 openssl 源码https://www.openssl.org/source我这边下载的是3.0.13 编译openssl 参考这个https://zhuanlan.zhihu.com/p/628437266 主要命令 ./Configure darwin64-x86_64-cc --prefix="/Users/a…

C++ 控制语句(一)

一 顺序结构 程序的基本结构有三种&#xff1a; 顺序结构、分支结构、循环结构 大量的实际问题需要通过各种控制流程来解决。 1.1 顺序结构 1.2 简单语句和复合语句 二 循环 2.1 for循环 语句流程图 注意&#xff1a;使用for语句的灵活性 三 while语句 四 do while语句

天翼云发布边缘安全加速平台AccessOne,四大产品能力助力企业安全高速发展

AccessOne依托中国电信分布式边缘资源,基于CDN底座,将云原生安全能力注入分布式边缘节点,实现网络底层对性能、安全、算力原子能力编排,并以“安全与加速、零信任、边缘接入、开发者平台”四大产品能力,一站式响应客户加速与防护需求,助力政企轻松管理自身业务。本文分享…

Alfred使用AppleScript来实现一键隐藏功能(老板键)

set appNames to {"WeChat","QQ"} -- 将要隐藏的进程名称放入数组中tell application "System Events"repeat with appName in appNamesset appProcess to first process whose name is appNameset appId to id of appProcesstell process appNa…

玩转云端|演唱会一票难求?快用天翼云边缘安全加速平台AccessOne!

天翼云AccessOne基于覆盖全球的海量边缘节点,能够智能分离动静态内容,通过智能负载均衡技术,让静态内容在边缘节点进行缓存,保障用户就近接入边缘节点获取资源;对于动态内容,可通过智能选路、协议优化等技术,选择最/佳链路回源,同时提供协议优化、链路优化等多项优化技…

每日一题 --- 有效的字母异位词[力扣][Go]

有效的字母异位词 题目&#xff1a;242. 有效的字母异位词 给定两个字符串 *s* 和 *t* &#xff0c;编写一个函数来判断 *t* 是否是 *s* 的字母异位词。 **注意&#xff1a;**若 *s* 和 *t* 中每个字符出现的次数都相同&#xff0c;则称 *s* 和 *t* 互为字母异位词。 示例 …

XPath攻略:从入门到精通,元素查找不再难

简介 XPath 是一种用于在 XML 文档中检索信息的语言。它通过路径表达式导航 XML 文档,广泛应用于各种场景。XPath 的灵活性和强大功能使其成为在 XML 结构中准确定位和提取数据的重要工具。 XPath 使用场景 Web 自动化测试:XPath 在 Web 自动化测试中广泛应用,XPath 提供了一…

ssm小区车库停车系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 ssm小区车库停车系统是一套完善的信息系统&#xff0c;结合springMVC框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模…