基于Qt的多线程TCP即时通讯软件的设计与实现

这篇具有很好参考价值的文章主要介绍了基于Qt的多线程TCP即时通讯软件的设计与实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

本文将从涉及到主要技术开始,讲解使用Qt来实现一个支持多客户端链接的多线程TCP服务器及其客户端的设计与实现的解决方案。
注:本文使用的开发环境为Qt5.15.2, 使用MSVC2019_64编译器, C++11及以上

Demo演示

qt 多线程tcp服务器,项目作品,qt,tcp/ip,网络
qt 多线程tcp服务器,项目作品,qt,tcp/ip,网络
qt 多线程tcp服务器,项目作品,qt,tcp/ip,网络


设计与开发

接下来我将会详细讲解客户端和服务端的设计与实现的关键细节。完整的源代码在文章最后。

基本技术学习与验证

QTcpServer 与 QTcpSocket

注:如果你已经熟知Qt的TCP套接字的使用可以跳过这一部分。
Qt的TCP套接字编程相较于传统的套接字编程更加的简单方便。而且,其与Qt自身的信号槽机制和多线程相结合使得异步无阻塞的套接字编程实现更加的简单。如果你了解TCP协议的基本流程,以及传统的TCP套接字编程,那么Qt更加简单的语法将会使你欲罢不能。

首先来解释一下,“为什么有一个QTcpServer,传统的不是只有一个socket吗?”
其实QTcpServer和QTcpSocket都是对原生的socket的封装,只是QTcpServer对服务器接收新连接的socket做了更好的处理。
如果是原生的socket,你一定也会创建两个socket,一个负责监听并处理新的socket接入,另一个根据监听来创建与客户端的TCP连接,使得服务器可以与客户端通讯。如果只有一个socket那么一个客户端接入之后服务端变失去了监听并接受新的连接的效能。
所以,在Qt中直接将这样的两种职能的socket封装成了QTcpServer与QTcpSocket。

QTcpServer

见名知意其是处理TCP套接字的服务器端,与套接字本身并无太大的区别,Qt对其的封装使得你可以更好的实现客户端套接字的接入处理,而不同于传统的套接字编程都是直接使用。通过下面的语句即可创建一个QTcpServer

QTcpServer *tcpServer = new QTcpServer();

创建完成一个服务端的套接字,为了使其监听指定的IP和端口,使用 listen()方法即可

tcpServer->listen(QHostAddress::Any, quint16(8848));

这样便可以监听从 任何IP 发到 本机8848端口 的TCP套接字
不同于传统的socket编程需要先绑定bind()再启动监听listen(),Qt将其封装在一起.

当有新的TCP套接字接入时,即有一个QTcpSocket请求连接时,
相应的QTcpServer将会自动创建一个与客户端连接的QTcpSocket的SocketDescriptor,并发出newConnection()信号。描述符你可以理解成标识码,即每个SocketDescriptor表示了不同的socket连接的具体信息,其本质是一个指针指向了一个QTcpSocket的核心数据块。

使用如下方法,即可让一个空的QTcpSocket完成配置,使其与客户端的socket连接。如果连接成功该QTcpSocket会发出一个connected()信号,客户端相应的socket也会发出同样的信号。

QTcpSocket *tcpSocket = new QTcpSocket();
tcpSocket = tcpServer->nextPendingConnection();

相应的信号槽可以这么写

connect(tcpServer, &QTcpServer::newConnection(), [&](){
	tcpSocket = tcpServer->nextPendingConnection();
});

QTcpSocket

创建一个QTcpSocket,并发出连接请求

QTcpSocket *tcpSocket = new QTcpSocket();
tcpSocket->connectToHost(QHostAddress("127.0.0.1"), quint16(8848));

如果成功,那么tcpSocket将发出connected()信号。
发送数据使用write()方法即可

tcpSocket->write("Hello World!");

QString mesg = "Hello World!";
tcpSocket->write(mesg.toUtf8().data());

当服务器回送消息到达客户端后,客户端相应的QTcpSocket将会发出readyRead()信号,使用readAll()即可读取内容。

connect(tcpSocket, &QTcpSocket::readyRead, [&](){
	QString mesg = tcpSocket->readAll();
});

消息传送完毕后,使用disconnectedFromHost()方法来断开TCP连接,客户端和服务端对应的套接字都会发出disconnected()信号。

tcpSocket->disconnectFromHost();
connect(tcpSocket, &QTcpSocket::disconnected, [&](){
	qDebug() << "Disconnected!";
});

多线程

一个常见的问题,我看到(同时这让我畏缩)人们了解和使用Qt线程和如何对代码做一些他们认为正确的工作。人们展现自己的代码,或用自己的代码写例子,往往最终让我的思维定格在:

你这样做是错误的

—— Bradley T. Hughes:"You’re doing it wrong"

https://blog.csdn.net/hustyangju/article/details/9493485

如果你已经对多线程有了一定的了解请先看看上面这篇文章,然后看看下面这篇转载的文章(没有找到原文,但是这篇转载的质量很高)。如果你跟我一样是实现多线程时遇到了麻烦,那么这两篇文章应该可以解开你的所有疑惑,而不用看完本文余下的内容,就可以继续你自己的实现。
https://blog.csdn.net/lynfam/article/details/7081757
如果你还不甚熟悉Qt的多线程,那就忽略这些,继续看下去。

多线程基础

Qt中的多线程有两种写法,第一种是自定义一个类(例如ChatThread)继承于QThread,重写run()方法来实现。run()就是你希望线程干什么事就写在这里。
例如:

#include <QObject>
#include <QThread>

class ChatThread : public QThread
{
    Q_OBJECT
public:
    explicit ChatThread(QObject *parent = nullptr);
    void run() override;
signals:
	void showSignal();
};

ChatThread::ChatThread(QObject *parent) : QThread{parent}
{
}

void ChatThread::run()
{
	emit showSignal();
}

这样你就自定义了一个线程,接下来在主线程中,创建线程,并使用start()启动它就行了

#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    ChatThread* chatThread = new ChatThread();
    QObject::connect(chatThread, &ChatThread::showSignal, [&](){
		qDebug() << "ChatThread running!";
	});
	chatThread->start();
    return a.exec();
}

但是,一般不建议使用这种方法。

推荐下面的方法
第二种方法:使用moveToThread()方法
我们认为QThread只是程序的一种控制转移。就是说我们应该将一些复杂的、耗时长的计算、业务处理与GUI界面分离,放入QThread中来正确的使用线程。而不是继承QThread,再在继承的类(例如ChatThread)中实现业务逻辑。

更加正确的写法应该是自定义继承QObject的业务类实现业务逻辑

class ChatBusiness : public QObject
{
    Q_OBJECT
public:
    explicit ChatBusiness(QObject *parent = nullptr);
    void mainBusiness();
signals:
	void startSignal();
};

ChatBusiness::ChatBusiness(QObject *parent) : QThread{parent}
{
	connect(this, &ChatBusiness::startSignal, this, &ChatBusiness::mainBusiness);
}

在需要的地方创建,移入线程中,然后发出信号启动线程

void someFunction()
{
	//some code
	//need that time cost process(ChatBusiness)
	ChatBusiness *chatBusiness = new chatBusiness();
	QThread *thread = new thread();
	chatBusiness->moveToThread(thread);
	emit chatBusiness->startSignal();//start the thread
}

信号槽与多线程

下面这篇文章很好的阐释了信号槽与多线程以及事件循环的相关内容。
Qt 的线程与事件循环:https://blog.csdn.net/lynfam/article/details/7081757
简单来说,

  1. connect()信号槽连接写在哪里不造成任何影响
  2. 信号发出者槽函数在同一个线程时, 采用Qt::DirectConnection,立即直接调用
  3. 信号发出者槽函数不在同一个线程时, 采用Qt::QueuedConnection,异步调用

而我们需要实现的多线程TCP就是属于第二类。

多线程TCP

我们这里只是实现简单的多线程TCP服务器。就是说新的链接接入,就为其创建一个线程单独处理。所以我们需要自定义新链接接入的处理逻辑。
这一点可以通过继承QTcpServer,实现自己的MyTcpServer并重写incomingConnection(qintptr handle)来实现

void MyTcpServer::incomingConnection(qintptr handle)
{
	//create subThreaad
    QThread *thread = new QThread(this);
    ChatBusiness *chatBusiness = new ChatBusiness();
    chatBusiness->moveToThread(thread);
    //def handle of start
    connect(chatBusiness, &ChatBusiness::start, chatBusiness, &ChatBusiness::mainBusiness);
    thread->start();
    //send the SocketDescriptor
    emit chatBusiness->start(handle);
}

void ChatBusiness::mainBusiness(qintptr handle)
{
    QTcpSocket *tcpSocket = new QTcpSocket(this);
    tcpSocket->setSocketDescriptor(handle);
}

上面代码中,在子线程中业务处理类的处理开始是mainBusiness(qintptr handle)方法,其将传递的SocketDescriptor赋给创建的socket

至此你便了解所有的基本核心技术


通讯设计

从功能演示中我们看到,用户通过客户端发送自己的昵称,服务器进行认证注册;用户输入发送的目标,点击发送,服务器转发消息到相应的用户。其通讯流程如下图设计:
qt 多线程tcp服务器,项目作品,qt,tcp/ip,网络
由于是直接使用的TCP通讯,而不是应用层协议,需要自己根据需要设计通讯格式。
在本程序中通讯格式设计成:

服务器回送系统消息:
[username,15]@[Server,15] [ServerMesgType,20]

ServerMesgType 解释
RegisterSuccessful 名称注册成功
RegisterFailed 名称注册失败,重名
TargetOffline 接收方不在线

服务器转发用户消息:
[targetName,15]@[username,15] [Mesg]

用户发送系统消息(注册名称):
[username,15]@[Server,15] [ServerMesgType,20]

ServerMesgType 解释
Register 请求注册名称

客户端设计

界面设计

登录窗口
qt 多线程tcp服务器,项目作品,qt,tcp/ip,网络
聊天主窗口
qt 多线程tcp服务器,项目作品,qt,tcp/ip,网络
此外为了保证UI显示在不同分辨率的显示器、不同的缩放比例下有着较好的适配,我们需要如下设置

添加新的qrcqt 多线程tcp服务器,项目作品,qt,tcp/ip,网络
qt 多线程tcp服务器,项目作品,qt,tcp/ip,网络
然后在文件夹中创建 /etc/qt.conf 文件
qt 多线程tcp服务器,项目作品,qt,tcp/ip,网络
qt.conf 文件中添加如下内容

[Platforms]
WindowsArguments = dpiawareness=0
WindowsArguments = fontengine=freetype

第一行是让程序的缩放程度跟随系统控制
第二行是使用freetype字体,使得缩放时的字体不会出现明显的锯齿

同时在main.cpp文件中添加,来使得缩放时图像不会有锯齿

QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);

qt 多线程tcp服务器,项目作品,qt,tcp/ip,网络


网络设计

客户端的网络实现比较简单,只需要使用QTcpSocket。实现其readyRead(),connected(),disconnected()信号的处理即可。
这里不再赘述。


服务器设计

无论从功能需要还是性能要求,服务端做成控制台应用即可,无需使用Qt的界面。

服务器架构设计

qt 多线程tcp服务器,项目作品,qt,tcp/ip,网络
即:

  • MyTcpServer作为服务器主要的调度,接受新的链接、注册名称、转发消息
  • 重写M有TCPServer的incomingConnection(qintptr handle)方法,创建ChatBusiness业务对象,并移入子线程启动
  • ChatBusiness创建对应的MyTcpSocket套接字与客户端连接
  • 实现自己的MyTcpSocket使其之间内部就可以处理消息的发送,转发系统请求到ChatBusiness


这样客户端和服务器的设计和实现细节便全部描述完毕。你可以一次尝试自己开发,或者在文章的最后部分下载工程源码

打包与部署

在完成编码后我们需要测试,或者给朋友玩玩之类,就需要对程序打包部署。
首先我们以Release模式重新构建项目,然后将构建好的对应的.exe文件拷贝到一个单独的文件夹
例如.../Client/client-Demo.exe

然后在Qt的 安装目录 找到项目使用的对应的 编译器文件夹
例如:
qt 多线程tcp服务器,项目作品,qt,tcp/ip,网络
确认有该工具
qt 多线程tcp服务器,项目作品,qt,tcp/ip,网络
在该目录打开命令行,输入.\windeployqt.exe Client.exe的完整路径,例如

 .\windeployqt.exe C:\Client\Client-Demo2.exe

之后使用Enigma Virtual Box打包即可,如果在其他电脑上不能运行。。。那就根据提示再打包。例如提示找不到xxxx.dll,就把该dll放到Client.exe同级文件夹下,再使用Enigma Virtual Box打包。
嗯,是的,感觉很是愚蠢。但是这是相对来说比较省事的方法了。

如果涉及Qt之外的库,那就需要用depends.exe来查找依赖了。
可以参考这篇文章:QT+Opencv 程序打包发布:https://blog.csdn.net/qq_43599883/article/details/106251915


缺陷与改进

  1. 客户端交互体验较差。没有Enter快捷键;无法切换聊天窗口等。
  2. 客户端界面不够美观。没有使用QSS等进行界面设计。
  3. 无法传输图像等多媒体数据。
  4. 服务器性能设计不足。一个socket一个线程有点过于奢侈。
  5. 服务器没有存储消息等数据存储能力,应该与数据库结合实现。
  6. 明文传输数据。应当考虑使用https传输,或者自己实现AES对称加密消息,RSA加密密钥来传输。
  7. TCP连接链路拥塞。应当使用QTimer来发送“心跳”包确认链路通畅,以避免连接中断而disconnected()信号没有发送的情况。
  8. 开发中没有使用Github等工具合理的管理程序版本。
  9. 实现过程不够规范。缺少对类和类之间关系的建模、文档模型化,导致编写时常常忘记链接信号槽、实现相应的槽函数等。(身为软件工程出身这太不应该,欲速则不达〒▽〒)
  10. 客户端连接接服务器的IP不可变化。
  11. 软件部署方式难以更新。
  12. 额,欢迎提出建议…

部分内容已经在下一个版本的开发日程中,之后会在本博客中更新新的工程源码,欢迎持续关注。ヾ(≧▽≦*)o


工程源码与代码仓库

版本1:https://github.com/SWULWJ/AirChat
版本2:AirChat2.0


参考源

QT 使用全局缩放进行全分辨率适配(QT_SCALE_FACTOR):https://blog.csdn.net/u014410266/article/details/107488789
Qt Windows高清DPI自适应分辨率缩放:https://blog.csdn.net/startl/article/details/105862817
Qt 的线程与事件循环:https://blog.csdn.net/lynfam/article/details/7081757
Qt多线程中的信号与槽:https://blog.csdn.net/qq_29344757/article/details/78136829
TCP_UDP_Assistant.zip (一个很厉害的例子):https://download.csdn.net/download/yxy244/12030948
QT+Opencv 程序打包发布:https://blog.csdn.net/qq_43599883/article/details/106251915
Qt应用程序在windows和Linux操作系统下的打包发布:https://blog.csdn.net/qq_41488943/article/details/104963916

RSA算法原理:https://zhuanlan.zhihu.com/p/48249182
AES加密算法的详细介绍与实现:https://blog.csdn.net/qq_28205153/article/details/55798628

- - - - AirChat2.0 - - - -
Qt–堆栈窗口(QStackedWidget)的使用:https://blog.csdn.net/trailbrazer/article/details/54344590
QT5 使用163邮箱发送邮件:https://blog.csdn.net/wangdeyu1994/article/details/78693427
Qt. QSqlDatabase Class Documentation: https://doc.qt.io/qt-5/qsqldatabase.html


低劣的转载真的太多了。。。


感谢阅读!

有疑问或者认为有错误请留言,谢谢!文章来源地址https://www.toymoban.com/news/detail-794894.html

到了这里,关于基于Qt的多线程TCP即时通讯软件的设计与实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++毕业设计——基于 C+++TCP+Websocket的即时通信系统设计与实现(毕业论文+程序源码)——即时通信系统

    大家好,今天给大家介绍基于 C+++TCP+Websocket的即时通信系统设计与实现,文章末尾附有本毕业设计的论文和源码下载地址哦。需要下载开题报告PPT模板及论文答辩PPT模板等的小伙伴,可以进入我的博客主页查看左侧最下面栏目中的自助下载方法哦 文章目录: 随着网络通信和

    2024年02月08日
    浏览(40)
  • QT下的多线程TCP客户端和服务器

    qt下的QTcpSocket在同一个线程使用时没有问题的,但是如果进行跨线程,很容易出现问题。那么有什么方法可以跨线程进行使用吗? 答案是肯定的:使用QThread的movetothread可以完成扩线程接收。 首先是基于QTcpSocket的类 头文件tcpsocket.h 然后是cpp文件tcpsocket.cpp 再次基础上,创建

    2024年01月17日
    浏览(50)
  • 【基于Qt和OpenCV的多线程图像识别应用】

    这是一个简单的小项目,使用Qt和OpenCV构建的多线程图像识别应用程序,旨在识别图像中的人脸并将结果保存到不同的文件夹中。这个项目结合了图像处理、多线程编程和用户界面设计。 用户可以通过界面选择要识别的文件夹和保存结果的文件夹。然后,启动识别进程。图像

    2024年02月05日
    浏览(39)
  • IM即时通讯源码/im源码基于uniapp框架从0开始设计搭建在线聊天系统

    随着人社交产品的不断发展,即时通讯聊天这门技术也越来越重要,很多人都开启了学习通讯技术,本文就介绍了即时通讯的基础内容。 在开始设计和搭建聊天系统之前,需要确定所需技术栈。常用的技术栈包括前端、后端和数据库。例如,前端可以选择uni-app,后端可以选

    2024年02月04日
    浏览(57)
  • 基于Java+SpringBoot+vue+elementui 实现即时通讯管理系统

    博主介绍: 计算机科班人,全栈工程师,掌握C、C#、Java、Python、Android等主流编程语言,同时也熟练掌握mysql、oracle、sqlserver等主流数据库,能够为大家提供全方位的技术支持和交流。 目前工作五年,具有丰富的项目经验和开发技能。提供相关的学习资料、程序开发、技术解

    2024年02月19日
    浏览(44)
  • 掷骰子的多线程应用程序2基于互斥量的线程同步(复现《Qt C++6.0》)

    说明:在复现过程中出现两点问题(1)run()函数中对m_diceValued的赋值(2)do_timeOut()函数中没有对m_seq、m_diceValued进行定义。修改后的复现程序如下所示: 主线程: .h .cpp 工作线程: .h .cpp

    2024年02月07日
    浏览(83)
  • 即时通讯聊天软件DDOS攻击怎么防护

    即时通讯聊天软件DDOS攻击怎么防护?随着即时通讯聊天软件的普及,DDoS攻击也越来越常见。DDoS攻击是一种利用大量计算机发起的分布式拒绝服务攻击,它可以让即时通讯聊天软件无法正常运行,导致用户无法正常使用。针对这种情况,即时通讯聊天软件需要采取一系列措施来

    2023年04月27日
    浏览(46)
  • C++基于开源Modbus Tcp 通讯应用客户端(稳定高效,多线程后台状态读取,不卡顿)

    使用多线程后台批量刷寄存器的状态,在某种程度上保证了上层接口读取的时候,不会卡顿, 整体应用效果比较友好。程序应用简单稳定高效,是一个比较不错的尝试。 代码如下: 代码如下: https://download.csdn.net/download/u013083044/87062401

    2024年02月16日
    浏览(50)
  • python实现TCP数据通讯,socket 客户端断开依然保持监听,多线程。

    服务端: 客户端: 服务端采用 try...except .. 形式,代码如下:

    2024年02月14日
    浏览(42)
  • 解锁安全高效办公——私有化部署的WorkPlus即时通讯软件

    在当今信息时代,高效的沟通与协作对于企业的成功至关重要。然而,随着信息技术的发展,保护敏感信息和数据安全也变得越来越重要。为了满足企业对于安全沟通和高效办公的需求,我们隆重推出私有化部署的WorkPlus即时通讯软件,为您的企业提供一站式解决方案。 私有

    2024年02月11日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包