当前位置: 首页 > news >正文

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)

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Nginx -Web服务器/反向代理/负载均衡
  • Selenium WebDriver中的显式等待与隐式等待:深入理解与应用
  • LabVIEW学习-LabVIEW储存Excel表格
  • 新版k8s拉取镜像失败问题
  • Python基础学习笔记——异常
  • python实现openssl的EVP_BytesToKey及AES_256_CBC加解密算法
  • “存算分离“和“湖仓一体“
  • Ansible 安装及使用说明
  • (补充)IDEA项目结构
  • Linux重要知识点
  • JMeter案例分享:通过数据验证的错误,说说CSV数据文件设置中的线程共享模式
  • Flutter和React Native(RN)的比较
  • 【无需公网IP】在树莓派上搭建Web站点
  • 亚马逊云科技EC2简明教程
  • 每日Attention学习11——Lightweight Dilated Bottleneck
  • 【mysql】环境安装、服务启动、密码设置
  • DOM的那些事
  • go语言学习初探(一)
  • Laravel5.4 Queues队列学习
  • Logstash 参考指南(目录)
  • Map集合、散列表、红黑树介绍
  • MyEclipse 8.0 GA 搭建 Struts2 + Spring2 + Hibernate3 (测试)
  • WePY 在小程序性能调优上做出的探究
  • 对JS继承的一点思考
  • 工作中总结前端开发流程--vue项目
  • 函数式编程与面向对象编程[4]:Scala的类型关联Type Alias
  • 经典排序算法及其 Java 实现
  • 前端学习笔记之原型——一张图说明`prototype`和`__proto__`的区别
  • 使用权重正则化较少模型过拟合
  • 事件委托的小应用
  • 微信开放平台全网发布【失败】的几点排查方法
  • 物联网链路协议
  • 用简单代码看卷积组块发展
  • 《TCP IP 详解卷1:协议》阅读笔记 - 第六章
  • 交换综合实验一
  • 直播平台建设千万不要忘记流媒体服务器的存在 ...
  • ​力扣解法汇总1802. 有界数组中指定下标处的最大值
  • #基础#使用Jupyter进行Notebook的转换 .ipynb文件导出为.md文件
  • $$$$GB2312-80区位编码表$$$$
  • $HTTP_POST_VARS['']和$_POST['']的区别
  • (33)STM32——485实验笔记
  • (NO.00004)iOS实现打砖块游戏(九):游戏中小球与反弹棒的碰撞
  • (Python) SOAP Web Service (HTTP POST)
  • (八)Flask之app.route装饰器函数的参数
  • (草履虫都可以看懂的)PyQt子窗口向主窗口传递参数,主窗口接收子窗口信号、参数。
  • (附源码)c#+winform实现远程开机(广域网可用)
  • (一)、python程序--模拟电脑鼠走迷宫
  • (已解决)什么是vue导航守卫
  • (原創) 如何刪除Windows Live Writer留在本機的文章? (Web) (Windows Live Writer)
  • (转)C#调用WebService 基础
  • (转)ORM
  • (转)使用VMware vSphere标准交换机设置网络连接
  • .mat 文件的加载与创建 矩阵变图像? ∈ Matlab 使用笔记
  • .Net Core 生成管理员权限的应用程序
  • .net连接MySQL的方法