Qt QWebSocket网络编程

学习目标:Qt QWebSocket网络编程

学习前置环境

QT TCP多线程网络通信-CSDN博客

学习内容

WebSocket是一种通过单个TCP连接提供全双工通信信道的网络技术。2011年,IETF将WebSocket协议标准化为 RFC6455,QWebSocket可用于客户端应用程序和服务器应用程序。

它实现了浏览器与服务器全双工(full-duplex)通信,允许服务器主动发送信息到客户端。

主要特点:

  1. 与HTTP不同,WebSocket允许服务器主动发送数据给客户端,不需要客户端发起请求。

  2. 建立在TCP协议之上,服务器和客户端之间通过Ws(WebSocket)协议在单个端口上进行全双工通信。

  3. 支持以文本方式或者二进制方式传输数据。

  4. 协议建立在单个TCP连接上,服务器和客户端只需创建一个连接,且连接不会被关闭。

  5. 支持多种编程语言的客户端和服务器端库,如JavaScript,Java,C#,Python等。

常见应用场景:

  1. 聊天室:支持低延迟的实时对话。

  2. 在线game:需要实时同步游戏状态的同步引擎。

  3. 股票行情:需要推送即时行情给客户端的行情软件。

  4. 视频会议:需要语音和视频的低延迟实时通信。

  5. 实时协作编辑:如在线代码编辑器要求实时同步。

QWebSocket是Qt提供的WebSocket功能库。它建立在Qt网络模块之上,实现了RFC6455标准中的WebSocket协议。需要再Qmake文件中加入 QT+=websockets

  1. QWebSocket只支持Text/Binary两种消息格式,不支持其他扩展格式。如果需要补充其他自定义协议,需要开发者在应用层自己处理。

  2. 它支持使用标准的http/https端口80/443访问websocket服务,也支持wss(加密websocket)协议。所以可以很方便地与现有的web服务器交互。

  3. 由于它基于QT CP套接字实现,完全支持所有Qt网络功能,比如代理设置、SSL配置等。这一点相比一些底层的C接口更易用。

  4. 它同时支持主动和被动连接模式。主动连接通过connectToHost(),被动通过监听端口accept()接受新的链接。这两种模式都很方便。

  5. 对于QT GUI应用,可以很方便地进行消息接收与界面更新,避免了多线程编程的复杂性。比如直接在textMessageReceived()里更新界面就行了。

  6. QT5.10后支持了异步I/O,性能较以前有一点提升。对网延的支持也更好了。

QWebSocket常用成员函数


origin()
即 websocket=new QWebSocket("C1我是客户端",QWebSocketProtocol::VersionLatest,this);
websocket->origin()  -》 C1我是客户端void connectToHost(const QUrl &url) - 用于连接到指定主机的websocket服务,这个函数是异步的。
void close() - 关闭与服务器的连接。
void textMessageReceived(const QString &message) - 收到文本消息时触发的信号,其参数就是收到的文本消息内容。
void binaryMessageReceived(const QByteArray &message) - 收到二进制消息时触发的信号,参数是原始二进制数据。
void error(QAbstractSocket::SocketError socketError) - 发生错误时触发的信号,参数是错误类型。
void stateChanged(QAbstractSocket::SocketState state) - 连接状态变化时触发,可以得知连接是否建立等。
void textMessageSent(qint64 numBytes) - 发送文本消息完成后触发,numBytes是字节数。
void bytesWritten(qint64 bytes) - 消息发送过程中的写入回调, bytes是一个部分发送出去的字节数。
void abort() - 主动断开连接。
bool waitForConnected(int msec = 30000) - 阻塞等待连接建立成功。QString hostName() - 获取当前连接的主机名,常用于判断连接是否成功。
quint16 port() - 获取主机端口号。
bool openMode() - 判断当前是否为主动连接还是被动接受模式。
void writeTextMessage(const QString &text) - 发送文本消息,相比textMessage等更直观。
void writeMessage(const QByteArray &data) - 发送二进制数据。
qint64 bytesAvailable() - 查看接收缓存中可读取字节数。
qint64 readBufferSize() - 设置双向数据接收缓存大小。
void pauseIncomingPayload() - 暂停接收消息流。
void resumeIncomingPayload() - 恢复接收。
bool isValid() - 检查连接是否有效。另外,作为QT套接字,它还支持一些通用功能:
void setProxy() - 设置代理。
void encrypt() - 设置SSL安全连接。
void flush() - 强制输出缓存写出。
bool waitForBytesWritten() - 等待数据发送完毕。void QWebSocket::sendTextMessage(const QString &message)  用于发送文本消息
使用这个函数发送文本消息主要有以下几点需要注意的地方:
1发送文本消息前请确保WebSocket连接已经建立。可以通过ReadyState判断连接状态。
2发送的消息内容必须是纯文本,不支持转义编码等更多格式。
3一条消息发送完毕后,会触发textMessageSent()信号通知。
4可以通过waitForBytesWritten()等待数据完全发送出去。
5发送数据顺序可能与收到响应顺序不一致,需要应用层自己处理序号等。
6若消息较大,建议使用write或send到套接字后flush,而不是sendTextMessage。
7跨平台考虑,消息内容编码最好使用QString而不是QByteArray。
8使用该函数发送的文本消息类型,服务端一般对应文本框接受。
9可以绑定消息发送断开连接的异常处理等。

sendTextMessage和writeTextMessage这两个函数都可以用来发送文本消息,但它们有一些区别:

  1. 函数定义不同:
  • sendTextMessage属于QWebSocket的成员函数;

  • writeTextMessage是QAbstractSocket的成员函数,QWebSocket继承于QAbstractSocket。

  1. 发送效率不同:
  • sendTextMessage内部会将消息先转成QByteArray,再通过write函数发送,多了一次转换;

  • writeTextMessage直接写入需要发送的QString,效率略高。

  1. 异步支持不同:
  • sendTextMessage是同步操作,发送完毕后再返回;

  • writeTextMessage支持异步调用,可以通过Lambda指定回调函数。

  1. 错误处理不同:
  • sendTextMessage不会返回错误信息,只能通过信号错误处理;

  • writeTextMessage可以获取返回的错误码判断发送情况。

  1. 使用场景不同:
  • sendTextMessage专注WebSocket,适合 WebSocket API 的调用方式;

  • writeTextMessage更通用,可用于其他QAbstractSocket子类。

总的来说:

  • sendTextMessage使用更简单,封装良好适合基本用法;

  • writeTextMessage效率略高,支持更多特性如异步和错误处理,适合性能或控制需求较高的场景。

实现项目

客户端与客户端私聊通信,客户端与服务端之间通信。

 核心代码

服务端

流程:创建QWebServer,绑定新连接回调,监听断开。

新连接回调:有新连接 加入集合,给新连接socket绑定离线和接收以及错误的回调。

发送消息按钮:对all和one进行分类处理,遍历set集合,使用sendTextMesg发送

#include "widget.h"
#include "ui_widget.h"
//这个是服务器端
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);/** mode
NonSecureMode: 不安全模式,即不使用SSL/TLS进行通信。这是默认值。
SecureMode: 安全模式,以SSL/TLS安全通信。客户端和服务端之间的连接将使用SSL握手建立安全通道。
AutomaticallyAcceptServerCertificates: 自动接受服务器证书。在SecureMode下,客户端无法验证证书时,自动接受服务器发来的证书以建立连接。
VerifyNone: 不验证证书。以SecureMode运行,但不会验证客户端和服务端使用的证书。
*/webServer = new QWebSocketServer("testWebServer",QWebSocketServer::NonSecureMode,this);QObject::connect(webServer,&QWebSocketServer::newConnection,this,&Widget::MyselfNewConnectCallBackSlot);webServer->listen(QHostAddress::Any,8888);
}
void Widget::MyselfNewConnectCallBackSlot(){//新连接回调if(webServer->hasPendingConnections()){QWebSocket* websocket=webServer->nextPendingConnection();ui->msgtext->append(websocket->origin()+"客户端已连接到服务器");sockets<<websocket;QListWidgetItem * item =new QListWidgetItem;item->setText(websocket->origin());ui->clinetls->addItem(item);//绑定离开QObject::connect(websocket,&QWebSocket::disconnected,this,[websocket,this](){ui->msgtext->append(websocket->origin()+"客户端断开服务器连接");sockets.removeOne(websocket);for(int i=0;i<ui->clinetls->count();i++){QListWidgetItem *item=ui->clinetls->item(i);if(item->text()==websocket->origin()){ui->clinetls->removeItemWidget(item);delete item;break;}}websocket->deleteLater();});//接受消息回调QObject::connect(websocket,&QWebSocket::textMessageReceived,this,[this](const QString &msg){QJsonDocument doc =QJsonDocument::fromJson(msg.toLatin1().data());if(doc.isNull()){QWebSocket* websocket =qobject_cast<QWebSocket*>(sender());//sender 触发信号的源头ui->msgtext->append("收到客户端消息["+websocket->origin()+"]--->"+msg);}else{//客户端之间的单发消息QJsonObject obj=doc.object();QString dst=doc["dst"].toString();for (auto& socket : sockets) {if(dst == socket->origin()){socket->sendTextMessage(msg);}}}});QObject::connect(websocket,QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error),this,[this](QAbstractSocket::SocketError error){QWebSocket* web =qobject_cast<QWebSocket*>(sender());ui->msgtext->append(web->origin()+"出错"+web->errorString());});}}
Widget::~Widget()
{delete ui;for(auto socket:sockets){socket->close();}webServer->close();
}void Widget::on_sendpb_clicked()
{QString send =ui->sendtext->toPlainText().trimmed();if(send.isEmpty())return;if(ui->allradio->isChecked()){//群发if(sockets.size()==0)return;foreach(auto &socket,sockets){socket->sendTextMessage(send);}ui->msgtext->append("服务器给所有连接发送:"+send);}else{ //私发  取客户端名称 找 发if(!ui->clinetls->currentItem())return;QString cname =ui->clinetls->currentItem()->text();for(auto &socket:sockets){if(socket->origin()==cname){socket->sendTextMessage(send);ui->msgtext->append("服务端给["+socket->origin()+"]发送--->"+send);break;}}}ui->sendtext->clear();
}

客户端

点击按钮实现websocket连接流程:创建websocket,绑定各种回调,通过open(url)连接

发送消息按钮:对私发和服务器发进行分类,私发封装json格式,然后再发送。

#include "widget.h"
#include "ui_widget.h"
#include<QMessageBox>
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);websocket=nullptr;}Widget::~Widget()
{delete ui;
}void Widget::on_sendpb_clicked()
{if(!websocket && !websocket->isValid())return;QString send=ui->sendtext->toPlainText().trimmed();if(send.isEmpty())return;QString cname =ui->onetextmsg->text().trimmed();if(cname.isEmpty()){ //发给服务器websocket->sendTextMessage(send);ui->msgtext->append("发送消息:"+send);}else{//私聊发QJsonObject obj;obj["src"]=websocket->origin();obj["dst"]=cname;obj["msg"]=send;//将一个QJsonObject类型的obj转换成JSON字符串。 Compact参数指定格式化方式为紧凑格式(每个元素占一行)。紧凑格式输出结构清晰,容量小,适合传输和存储。QString str(QJsonDocument(obj).toJson(QJsonDocument::Compact));websocket->sendTextMessage(str);}ui->sendtext->clear();}void Widget::on_connect_clicked()
{if(websocket == nullptr){if(ui->server_addr->text().trimmed().isEmpty()){QMessageBox::critical(this,"错误","服务器名称不能为空,请重新检查!",QMessageBox::Yes);return;}//用于移除字符串开头和结尾处的空白字符。 输入: " test " trimmed()后的结果: "test"  VersionLatest使用最新的websocket协议版本。websocket=new QWebSocket(ui->client_name->text().trimmed(),QWebSocketProtocol::VersionLatest,this);//连接回调QObject::connect(websocket,&QWebSocket::connected,this,[this](){ui->msgtext->append("已经连接上"+websocket->peerAddress().toString());isConnecting=true;ui->connect->setText("断开服务器");});//断开回调QObject::connect(websocket,&QWebSocket::disconnected,this,[this](){ui->msgtext->append("已"+websocket->peerAddress().toString()+"断开连接");isConnecting=false;ui->connect->setText("连接服务器");});QObject::connect(websocket,QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error),this,[this](QAbstractSocket::SocketError){ui->msgtext->append(websocket->origin()+"出错"+websocket->errorString());});//接受消息回调 网上知名bug 当你连续发送 A:A*100 B:200时 接收方:A*100200 通过消息队列方式发送即可connect(websocket,&QWebSocket::textMessageReceived,this,[this](const QString &msg){QJsonDocument jsd=QJsonDocument::fromJson(msg.toUtf8().data());if(jsd.isNull()) //解析失败 即没有 c1:c2 客户端与客户端私聊{ui->msgtext->append("收到消息:"+msg);}else{QJsonObject jsobj=jsd.object();ui->msgtext->append("收到来自"+jsobj["src"].toString()+"的消息:"+jsobj["msg"].toString());}},Qt::QueuedConnection);}if(isConnecting){ //连接是成功的websocket->close();websocket->deleteLater();websocket=nullptr;} else{websocket->open(QUrl(ui->server_addr->text().trimmed()));}
}

总结

总的来说,QWebSocket作为QT网络库中的一个组件,提供了一整套用于开发WebSocket客户端和服务端的便利API。

它的主要优点有:

  1. 完全面向对象的设计,API简单易用。

  2. 与QT网络其他组件高度集成,如SSL/代理支持都很好。

  3. 采用事件驱动模型,不需要开发者处理底层细节如多线程等。

  4. 和Qt GUI应用天然集成,消息与界面更新直接调用即可。

  5. 提供了WebSocket基础规范完整实现,开箱即用方便开发。

  6. 性能也不错,特别是QT5.10后支持了异步I/O调用方式。

  7. 丰富的示例和开源项目可供参考,入门门槛低。

而一些需要注意的点包括:

  1. 不支持一些扩展的websocket协议格式,需要自行实现。

  2. 消息发送和接收的顺序匹配需要自行控制。

  3. 文件与流式大数据传输支持不够友好直接。

  4. 无法改变底层使用的智能指针和内存管理机制。

  5. 对新的C++标准特性支持相对保守一些。

总体来说,对于大多数基于Tcp的WebSocket应用来说,QWebSocket提供了一个非常优秀而成熟的选择。开发效率高, bug少。对QT应用来说也是首选。如果有更高级别需求,可以考虑其他底层实现。但对绝大部分案例,QWebSocket已经足够好用了。

最后附上源代码链接
对您有帮助的话,帮忙点个star

Qt demo: 学习qt过程 (gitee.com)

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

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

相关文章

怎么把便签主面板置顶 便签主面板置顶方法

作为一名经常需要处理大量信息和任务的作家&#xff0c;我发现便签记事真的是我的救星。无论是临时灵感、会议要点还是待办事项&#xff0c;便签都能帮我快速记录&#xff0c;让我不再遗漏任何重要信息。而且&#xff0c;便签的应用场景也非常广泛&#xff0c;无论是在电脑前写…

在JavaScript中,什么是解构赋值(destructuring assignment)?

聚沙成塔每天进步一点点 本文回顾 ⭐ 专栏简介在JavaScript中&#xff0c;什么是解构赋值&#xff08;destructuring assignment&#xff09;&#xff1f;1. 引言2. 解构赋值的概念3. 数组解构赋值3.1 基本语法3.2 跳过元素3.3 默认值3.4 交换变量值 4. 对象解构赋值4.1 基本语…

goaccess分析json格式日志

一.安装使用yum安装&#xff0c;yum install goaccess 二.主要介绍格式问题 1.nginx日志格式如下&#xff1a; log_format main escapejson {"time_local":"$time_local", "remote_addr":"$remote_addr", "r…

回收站清空了怎么恢复回来?8个数据恢复方法汇总分享!

在日常工作中&#xff0c;我们常常会遇到一个令人头痛的问题&#xff1a;回收站清空了怎么恢复回来&#xff1f;这种情况其实比想象中更常见。有时在整理桌面时可能会不小心彻底清理文件&#xff0c;或者误开启了回收站的自动清理功能&#xff0c;甚至可能因为病毒或bug而意外丢…

人工智能内容创作中RAG方法综述

论文链接&#xff1a;https://arxiv.org/pdf/2402.19473v1 尽管AIGC取得了显著的性能&#xff0c;但仍面临着如保持最新和长尾知识困难、数据泄露风险以及训练和推理成本高昂等挑战。检索增强生成&#xff08;RAG&#xff09;作为一种范式应运而生&#xff0c;通过从可用数据存…

【ACM珠海分会,IEEE Fellow加盟,CPS出版】第四届管理科学和软件工程国际学术会议(ICMSSE 2024,7月19-21)

第四届管理科学和软件工程国际学术会议(ICMSSE 2024)由ACM珠海分会&#xff0c;广州番禺职业技术学院主办&#xff1b;全国区块链行业产教融合共同体&#xff0c;AEIC学术交流中心承办&#xff0c;将于2024年7月19-21日于广州召开。 会议旨在为从事管理与软件工程领域的专家学…

realsense D435l+mid360标定

目录 一、安装realsense环境 二、获取realsense D450L相机内参 三、标定雷达和相机 1.下载livox_camera_calib 2.修改配置参数 3.使用fastlio生成点云 4.标定 一、安装realsense环境 git clone https://github.com/IntelRealSense/librealsense.git cd librealsense //更…

【文科类cpci/cnki会议,主题广泛】第九届现代管理、教育与社会科学国际学术会议(MMET2024)

会议信息 点击跳转会议官网&#xff08;更多会议信息可添加会议官网下方负责老师-杨老师&#xff09; 大会时间&#xff1a;2024年09月20-22日 大会地点&#xff1a;中国-厦门 提交检索&#xff1a;CPCI&#xff0c;CNKI (知网检索快速稳定) 点击一键投稿 点击一键参会&a…

模拟开关应用注意事项

应用模拟开关时&#xff0c;开关时间是一个重要的考虑因素&#xff0c;但是&#xff0c;不能将开关时间与建立时间相 混淆。导通时间和关断时间只是从控制输入到开关切换间的传播延迟的一种衡量指标&#xff0c;主要由驱动和电平转换电路中的时间延迟导致。tON和tOFF两个值一般…

基于单片机的温湿度感应智能晾衣杆系统设计

&#xff3b;摘 要&#xff3d; 本设计拟开发一种湿度感应智能晾衣杆系统 &#xff0c; 此新型晾衣杆是以单片机为主控芯片 来控制的实时检测系统 &#xff0e; 该系统使用 DHT11 温湿度传感器来检测大气的温湿度 &#xff0c; 然后通过单 片机处理信息来控制 28BYJ &…

网络安全防御 -- 防火墙安全策略用户认证综合实验

实验拓扑&#xff1a; 实验目的&#xff1a; 1、DMZ区内的服务器&#xff0c;办公区仅能在办公时间内(9:00-18:00)可以访问&#xff0c;生产区的设备全天可以访问。 2、生产区不允许访问互联网&#xff0c;办公区和游客区允许访问互联网。 3、办公区设备10.0.2.10不允许访问DM…

sql常用语句:

1.联合查询 对表中的数据进行限制&#xff1b; 2.从一个表复制到另一个表 SELECT INTO 将数据复制到一个新表&#xff08;有的 DBMS 可以覆盖已经存在的表&#xff0c;这依赖于 所使用的具体 DBMS&#xff09; SELECT *&#xff08;字段&#xff09; INTO CustCopy FROM Cu…

高仿imtoken钱包源码/获取助记词/获取私钥/自动归集

简介&#xff1a; 高仿imtoken钱包/获取助记词/获取私钥/自动归集 带双端&#xff0c;无纯源码 下载源码

从微分方程组构建 bbr 模型

描述分析 bbr 的文字自 2016 年底起至今从空白到泛滥&#xff0c;我自己在期间贡献了不少&#xff0c;本文又是一篇&#xff0c;但不同的是&#xff0c;本文尝试用闭环的数学模型给出一个 bbr 的全貌&#xff0c;顺便和 aimd 做对比。 先看带宽特性 bw(t)&#xff0c;设瓶颈带…

等保2.0丨5分钟速览:小白都能理解的等保2.0简介

等保2.0的概念 等保2.0全称网络安全等级保护2.0制度&#xff0c;是我国网络安全领域的基本国策、基本制度。以1.0的规范为基础&#xff0c;等级保护标准以积极的防御为重点&#xff0c;由被动的防御发展为安全可信、动态感知和全过程的事前、事中和事后的全过程的全方位的审核…

函数式接口、匿名内部类、lambda表达式

一、函数式接口 只有一个抽象方法的接口叫函数式接口&#xff0c;不能有两个&#xff0c;也不能有方法实现。 FunctionalInterface注解标记&#xff0c;在idea中可以用这个注解验证是不是函数式接口。实现函数式接口可以转成lambda表达式。 二、匿名内部类 匿名内部类的格式&a…

7.11 cf div3 C

Problem - C - Codeforces 操作 根据给定的索引数组ind和字符串c&#xff0c;按照一定的顺序修改字符串s中对应位置的字符。具体来说&#xff0c;第i次操作会修改s中索引为indi的位置的字符&#xff0c;将其设置为ci。 将c字符串按照从小到大排序&#xff0c;替换ind数组所表…

可观察性优势:掌握当代编程技术

反馈循环是我们开发人员工作的关键。它们为我们提供信息&#xff0c;并让我们从用户过去和现在的行为中学习。这意味着我们可以根据过去的反应进行主动开发。 TestComplete 是一款自动化UI测试工具&#xff0c;这款工具目前在全球范围内被广泛应用于进行桌面、移动和Web应用的…

【DRAM存储器三十三】LPDDR4介绍--寻址、pin定义、命令真值表

👉个人主页:highman110 👉作者简介:一名硬件工程师,持续学习,不断记录,保持思考,输出干货内容 参考资料:《镁光LPDDR4数据手册》 、《JESD209-4B》 目录 LPDDR4的寻址 LPDDR4的pin脚定义 命令真值表 LPDDR4的寻址

【深度学习入门篇 ②】Pytorch完成线性回归!

&#x1f34a;嗨&#xff0c;大家好&#xff0c;我是小森( &#xfe61;ˆoˆ&#xfe61; )&#xff01; 易编橙终身成长社群创始团队嘉宾&#xff0c;橙似锦计划领衔成员、阿里云专家博主、腾讯云内容共创官、CSDN人工智能领域优质创作者 。 易编橙&#xff1a;一个帮助编程小…