WebSocket 通信说明与基于 ESP-IDF 的 WebSocket 使用

一、 WebSocket 出现的背景

最开始 客户端(Client)服务器(Server) 通信使用的是 HTTP 协议,HTTP 协议有一个的缺陷为:通信只能由客户端(Client)发起。

在一些场景下,这种单向请求的特点,注定了当 服务器(Server) 有连续的状态变化时, 客户端(Client) 要获知就非常麻烦。 客户端(Client) 只能使用轮询的方式,即每隔一段时间,就发出一个询问,来了解 服务器(Server) 有没有新的信息,最典型的场景就是聊天室。

轮询的方式导致效率很低,非常浪费资源, 客户端(Client) 必须不停地发起连接,或者 HTTP 连接始终打开。为此工程师们一直在思考更好的解决方案,因此 WebSocket 就这样诞生了。


二、WebSocket 的优缺点

WebSocket 优势:

  • 实时性: 由于 WebSocket 的持久化连接,它可以实现实时的数据传输,避免了 Web 应用程序需要不断地发送请求以获取最新数据的情况。
  • 双向通信: WebSocket 协议支持双向通信,这意味着 服务器(Server) 可以主动向 客户端(Client) 发送数据,而不需要 客户端(Client) 发送请求。
  • 减少网络负载: 由于 WebSocket 的持久化连接,它可以减少 HTTP 请求的数量,从而减少了网络负载。

WebSocket 的劣势:

  • 需要浏览器和 服务器(Server) 都支持: WebSocket 是一种相对新的技术,需要浏览器和服务器都支持。一些旧的浏览器和 服务器(Server) 可能不支持 WebSocket。
  • 需要额外的开销: WebSocket 需要在服务器上维护长时间的连接,这需要额外的开销,包括内存和 CPU。
  • 安全问题: 由于 WebSocket 允许 服务器(Server) 主动向 客户端(Client) 发送数据,可能会存在安全问题。 服务器(Server) 必须保证只向合法的 客户端(Client) 发送数据。

三、WebSocket 协议概述

WebSocket 协议是一种基于 TCP 的协议,用于在 客户端(Client)服务器(Server) 之间建立持久连接,并且可以在这个连接上实时地交换数据。WebSocket 协议有自己的握手协议,用于建立连接,也有自己的数据传输格式。

客户端(Client) 发送一个 WebSocket 请求时,服务器(Server) 将发送一个协议响应以确认请求。在握手期间, 客户端(Client)服务器(Server) 将协商使用的协议版本、支持的子协议、支持的扩展选项等。一旦握手完成,连接将保持打开状态, 客户端(Client)服务器(Server) 就可以在连接上实时地传递数据。

WebSocket 协议使用的是 双向数据传输,即 客户端(Client)服务器(Server) 都可以在任意时间向对方发送数据,而不需要等待对方的请求。它支持传输 二进制数据文本数据,并可以自由地在它们之间进行转换。

WebSocket 通信过程

一个 WebSocket 连接包含以下四个主要阶段:

1、连接建立阶段(Connection Establishment)

在这个阶段, 客户端(Client)服务器(Server) 之间的 WebSocket 连接被建立。 客户端(Client) 发送一个 WebSocket 握手请求, 服务器(Server) 响应一个握手响应,然后连接就被建立了。握手过程 如下:
WebSocket 为了兼容 HTTP 协议,是在 HTTP 协议的基础之上进行升级得到的。在客户端(Client)服务器(Server) 端建立 HTTP 连接之后,客户端(Client) 会向 服务器(Server) 端发送一个升级到 WebSocket 的协议,如下所示:

GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

通过设置 Upgrade 和 Connection 这两个 header,表示准备升级到 WebSocket 了。

除了这里列的属性之外,其他的 HTTP 自带的 header 属性都是可以接受的。

服务器(Server) 端收到客户端(Client) 的请求之后,会返回给客户端(Client) 一个响应,告诉客户端(Client) 协议已经从 HTTP 升级到 WebSocket 了。返回的响应可能是这样的:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

这里的 Sec-WebSocket-Accept 是根据客户端(Client) 请求中的 Sec-WebSocket-Key 来生成的。具体而言是将客户端(Client) 发送的 Sec-WebSocket-Key 和 字符串 “258EAFA5-E914-47DA-95CA-C5AB0DC85B11” 进行连接。然后使用 SHA1 算法求得其 Hash 值,最后将 Hash 值进行 base64 编码即可,当 服务器(Server) 端返回 Sec-WebSocket-Accept 之后,客户端(Client) 可以对其进行校验,已完成整个握手过程。

2、连接开放阶段(Connection Open)

在这个阶段,WebSocket 连接已经建立并开放,客户端(Client)服务器(Server) 可以在连接上互相发送数据。

3、连接关闭阶段(Connection Closing)

在这个阶段,一个 WebSocket 连接即将被关闭。它可以被客户端(Client)服务器(Server) 发起,通过发送一个关闭帧来关闭连接。

4、连接关闭完成阶段(Connection Closed)

在这个阶段,WebSocket 连接已经完全关闭。客户端(Client)服务器(Server) 之间的任何交互都将无效。

需要注意的是,WebSocket 连接在任何时候都可能关闭,例如网络故障、 服务器(Server) 崩溃等情况都可能导致连接关闭。因此,需要及时处理 WebSocket 连接关闭的事件,以确保应用程序的可靠性和稳定性。

WebSocket 的消息格式

WebSocket 的消息格式与 HTTP 请求和响应的消息格式有所不同。WebSocket 的消息格式可以是 文本二进制数据,并且 WebSocket 消息的传输是在一个已经建立的连接上进行的,因此不需要再进行 HTTP 请求和响应的握手操作。

在这里插入图片描述

在这里插入图片描述

由图可知,WebSocket 的报文格式可以分为七大部分,分别是 1bitFIN 标志位3bitRSV 保留位4bitOpcode1bitMask 标志位7/7+16/7+64bitpayloadLen,可选字段 masking-key,可选字段 payload。具体解释下:

  • FIN 标志位:此标志位用于指示当前的帧是消息的最后一个分段。

    • WebSocket 支持将长消息切割成若干帧发送,切分后,前边的帧的 FIN 字段均为 0,最后一个帧的 FIN 为 1
    • 当消息没有分段时,这个帧便包含所有信息,FIN 标志位为1.【1bite】
  • RSV1~3 :这是三个保留位,一般情况下为全 0

    • 客户端(Client) 、服务端协商采用 WebSocket 扩展时,这三个标志位可以 非 0,且值的含义由扩展进行定义。
    • 如果出现 非0 值但并未采用 WebSocket 扩展,连接出错。
  • Opcode : 4bit 操作码,用于指示帧类型。

    • Opcode 决定了如何解析后续的数据载荷部分,如果操作码是不认识的,接收端应该断开连接。可选的操作码如下:

      %x0:表示一个延续帧。当 Opcode 为 0 时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。
      %x1:表示这是一个文本帧(frame)
      %x2:表示这是一个二进制帧(frame)
      %x3-7:保留的操作代码,用于后续定义的非控制帧。
      %x8:表示连接断开。
      %x9:表示这是一个 ping 操作。
      %xA:表示这是一个 pong 操作。
      %xB-F:保留的操作代码,用于后续定义的控制帧。
    • 其中注意 WebSocket 既可以传输文本数据,也可以传输二进制数据

  • Mask 标志位:指示帧的 payload 是否需要使用掩码覆盖。

    • RFC6455 规定,当且仅当由客户端(Client) 向服务端发送的帧需要覆盖。

    • 掩码覆盖的作用:解决 “缓冲区溢出”(忽略)

    • Mask1 ,但服务端接收的数据没有进行过掩码操作,服务端需要断开连接

      payload length:7/7+16位(64k)/7+64位(超级大),单位是字节
      模式区分:
      ①当7bite的payloadlength<126,此时为模式17bite的payloadlength=12616bite生效为模式27bite的payloadlength=12664bite生效为模式3
      
    • masking-keymask 值有关,当 mask0 时,没有 masking-key; 为 1 时,有 4bitmasking-key

  • payload-data :长度可变。包含扩展数据(x 字节)和应用数据(y 字节)

    • 如果通信双方约定使用了 WebSocket 扩展,则扩展数据也存放于此,并声明扩展长度。

    • 如果没有约定使用,则扩展数据为 0 字节

WebSocket 的报文格式中最重要的便是 Opcodepayload length(三种模式)payload data


四、基于 esp-idf 如何使用 webSocket

如何使用

  • 对于 esp-idf v5.0 以下版本, 有对应的 websocket 示例, 用户可以直接进行测试。

  • 对于 esp-idf v5.0 以上版本, 提供了 esp_websocket_client 组件, 直接在对应的示例下面添加组件即可。

    idf.py add-dependency "espressif/esp_websocket_client^1.2.3"
    

基于 ESP-IDF SDK 使用 Websocket 的案例:

使用 esp-idf v4.2.2 版本, 服务器(Server) 有时会异常断开, 模块会收到 服务器(Server) 发过来的 opcode=0x08 关闭帧, 这这种情况下设备要如何重新连接?这个机制是怎样的呢?

首先,op_code0x08 是一个断开帧, 表示对端主动断开的, 我们是不需要在 WEBSOCKET_EVENT_DATA 里去判断 op_code 等于 0x08 的情况。在 WEBSOCKET CLOSED 事件中,内部会进行断开的处理,如果直接在 WEBSOCKET_EVENT_CLOSED 调用 esp_websocket_client_start(client) 接口重新连接 服务器(Server) , 则实际测试下来会进入到 close 事件里面, 但是并没有重新连接, 日志如下:

2024-09-27 12:10:14.783]# RECV ASCII>
[0;32mI (27116) WEBSOCKET: ====================Received opcode=1==================[0m
[0;33mW (27116) WEBSOCKET: ------> [3, "410212051", {}] <------[2024-09-27 12:10:21.160]# RECV ASCII>
[0;32mI (33506) uart_events: netStatus:7
[2024-09-27 12:10:22.466]# RECV ASCII>
[0;32mI (34796) WEBSOCKET: ====================Received opcode=8==================[0m
[0;32mI (34796) WEBSOCKET: WEBSOCKET_EVENT_CLOSED[0m
[2024-09-27 12:10:26.185]# RECV ASCII>
[0;31mE (38486) TRANSPORT_WS: Error read response for Upgrade header GET /HBE-123456 HTTP/1.1Connection: UpgradeHost: 0c6eeb3d0d512aa2.octt.openchargealliance.org:21128User-Agent: ESP32 Websocket ClientUpgrade: websocketSec-WebSocket-Version: 13Sec-WebSocket-Key: VMy5tg1Rv1Gr/P7HhLCTVw==Sec-WebSocket-Protocol: ocpp1.6[0m
[0;31mE (38506) WEBSOCKET_CLIENT: Error transport connect[0m
[0;31mE (38516) WEBSOCKET_CLIENT: esp_websocket_client_abort_connection(160): Websocket already stop
[2024-09-27 12:10:29.403]# RECV ASCII>
[0;32mI (41746) uart_events: netStatus:6
[2024-09-27 12:10:37.633]# RECV ASCII>
[0;32mI (49976) uart_events: netStatus:6
[2024-09-27 12:10:45.848]# RECV ASCII>
[0;32mI (58196) uart_events: netStatus:6
[2024-09-27 12:10:51.190]# RECV ASCII>
[0;31mE (63456) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:[0m
[0;31mE (63456) task_wdt:  - IDLE (CPU 0)[0m
[0;31mE (63456) task_wdt: Tasks currently running:[0m
[0;31mE (63456) task_wdt: CPU 0: websocket_task[0m
[0;31mE (63456) task_wdt: CPU 1: IDLE[0m
[0;31mE (63456) task_wdt: Print CPU 0 (current core) backtrace
Backtrace: 0x4013BF6A:0x3FFBE920 0x40082A71:0x3FFBE940 0x4000BFED:0x3FFF1030 0x40093AAD:0x3FFF1040 0x40091698:0x3FFF1060 0x400917A8:0x3FFF10A0 0x400DD000:0x3FFF10C0 0x400938D9:0x3FFF10F0
[0;31mE (63456) task_wdt: Print CPU 1 backtrace[0m
Backtrace: 0x4008BFF1:0x3FFBEF2

再进一步调试发现是因为客户端(Client) 如果收到 0x08opcode , 它不仅仅只是断开之前的连接, 还会释放掉之前创建的 handle,所以用户需要在 WEBSOCKET_EVENT_CLOSED 事件里创建好 handle , 然后直接去做重新连接的操作, 参考如下代码:

case WEBSOCKET_EVENT_CLOSED:ESP_LOGI(TAG, "WEBSOCKET_EVENT_CLOSED");esp_websocket_client_config_t websocket_cfg = {};shutdown_signal_timer = xTimerCreate("Websocket shutdown timer", NO_DATA_TIMEOUT_SEC * 1000 / portTICK_PERIOD_MS,pdFALSE, NULL, shutdown_signaler);shutdown_sema = xSemaphoreCreateBinary();websocket_cfg.task_stack = 8192;websocket_cfg.uri = "ws://0c6eeb3d0d512aa2.octt.openchargealliance.org:21128/HBE-123456";websocket_cfg.subprotocol="ocpp1.6";ESP_LOGI(TAG, "Connecting to %s...", websocket_cfg.uri);esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, (void *)client);esp_websocket_client_start(client);break;}

修改代码之后, 在 服务器(Server) 收到 0x08opcode 之后, 可以重新连接成功。

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

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

相关文章

OpenSSL 自建CA 以及颁发证书(网站部署https双向认证)

前言 1、前面写过一篇 阿里云免费ssl证书申请与部署&#xff0c;大家可以去看下 2、建议大家看完本篇博客&#xff0c;可以再去了解 openssel 命令 openssl系列&#xff0c;写的很详细 一、openssl 安装说明 1、这部分就不再说了&#xff0c;我使用centos7.9&#xff0c;是自…

使用javaScript生成随机迷宫

效果预览 我制作了一个 CodePen&#xff0c;以动画形式展示随机迷宫的创建过程&#xff0c;以便更加直观的观察算法的工作原理。&#xff08;点击即可访问生成新迷宫&#xff09; 基本思路 使用javaScript生成随机迷宫的核心思想是使用一个“深度优先搜索”&#xff08;DFS&a…

【ArkTS】列表组件的“下拉刷新”和“上拉加载”

系列文章目录 【ArkTS】关于ForEach的第三个参数键值 【ArkTS】“一篇带你读懂ForEach和LazyForEach” 【小白拓展】 【ArkTS】“一篇带你掌握TaskPool与Worker两种多线程并发方案” 【ArkTS】 一篇带你掌握“语音转文字技术” --内附详细代码 【ArkTS】技能提高–“用户授权”…

Java项目实战II基于微信小程序的消防隐患在线举报系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着城市化进程的加快&…

每日十题八股-2024年12月7日

1.说说hashmap的负载因子 2.Hashmap和Hashtable有什么不一样的&#xff1f;Hashmap一般怎么用&#xff1f; 3.ConcurrentHashMap怎么实现的&#xff1f; 4.分段锁怎么加锁的&#xff1f; 5.分段锁是可重入的吗&#xff1f; 6.已经用了synchronized&#xff0c;为什么还要用CAS呢…

CTF学习24.11.19[音频隐写]

MISC07[音频隐写] 隐写术 隐写术是一门关于信息隐藏的技巧与科学&#xff0c;所谓信息隐藏指的是不让除预期的接收者之外的任何人知晓信息的传递事件或者信息的内容。隐写术的英文叫做Steganography&#xff0c;来源于特里特米乌斯的一本讲述密码学与隐写术的著作Steganograp…

掌握谈判技巧,达成双赢协议

在当今竞争激烈且合作频繁的社会环境中&#xff0c;谈判成为了我们解决分歧、谋求共同发展的重要手段。无论是商业合作、职场交流&#xff0c;还是国际事务协商&#xff0c;掌握谈判技巧以达成双赢协议都具有极其关键的意义。它不仅能够让各方在利益分配上找到平衡点&#xff0…

HTTPS的工作过程

1.HTTPS协议原理 1.1HTTPS协议的由来 HTTP在传输网络数据的时候是明文传输的&#xff0c;信息容易被窃听甚至篡改&#xff0c;因此他是一个不安全的协议&#xff08;但效率高&#xff09;。在如今的网络环境中&#xff0c;数据安全是很重要的&#xff08;比如支付密码又或者各…

鸿蒙UI开发——渐变色效果

1、概 述 ArkTs可以通过颜色渐变接口&#xff0c;设置组件的背景颜色渐变效果&#xff0c;实现在两个或多个指定的颜色之间进行平稳的过渡。 目前提供三种渐变类型&#xff1a;线性渐变、角度渐变、径向渐变。 我们在鸿蒙UI布局实战 —— 个人中心页面开发中&#xff0c;默认…

距离与AoA辅助的三维测距算法(适用于四个基站的情况的单点定位),MATLAB代码

本MATLAB 代码实现了一个基于LOS/NLOS混合环境的单点定位系统&#xff0c;主要用于估计目标物体的单点位 文章目录 代码运行结果源代码代码功能概述主要步骤分析初始化部分 绘图与输出 代码运行结果 定位结果如下&#xff1a; 命令行的坐标和误差输出&#xff1a; 部分代码…

Vue指令(一)--v-html、v-show、v-if、v-else、v-else-if、v-on、v-bind、v-for、v-model

目录 &#xff08;一&#xff09;初识指令和内容渲染指令v-html 1.v-html 案例&#xff1a; 官网的API文档 &#xff08;二&#xff09;条件渲染指令v-show和v-if 1. v-show 2. v-if &#xff08;三&#xff09;条件渲染指令v-else和v-else-if 案例 &#xff08;四…

记一次由docker容器使得服务器cpu占满密码和密钥无法访问bug

Bug场景&#xff1a; 前几天在服务器上部署了一个免费影视网站&#xff0c;这个应用需要四个容器&#xff0c;同时之前的建站软件workpress也是使用docker部署的&#xff0c;也使用了三个容器。在使用workpress之前&#xff0c;我将影视软件的容器全部停止。 再使用workpress…

【JavaEE 进阶(一)】SpringBoot(上)

博主主页: 33的博客 文章专栏分类:JavaEE ??我的代码仓库: 33的代码仓库?? ???关注我带你了解更多进阶知识 目录 1.前言2.Spring3.第一个SpringBoot程序4.Spring MVC 4.1建立连接 4.1.1RequestMapping使用 4.2请求 4.2.1传递单个参数4.2.2传递多个参数4.2.3传递一个对象…

(未更新完)day30-IO-阶段综合案例(带权重的随机每日一记)(笔记完全来源于黑马程序员)

目录 0 目录一、听黑马阿玮的视频记录的笔记1. 制造假数据1.1 如何制造假数据1.2 练习1-生成方式1&#xff1a;爬取姓氏、男生名字、女生名字1.3 练习2-生成方式1&#xff1a;在练习1的基础上&#xff0c;将数据写入本地文件1.4 练习3-生成方式2&#xff1a;利用糊涂包生成假数…

FPGA中所有tile介绍

FPGA中包含的tile类型&#xff0c;以xinlinx 7k为例&#xff0c;可以通过f4pga项目中的原语文件夹查看&#xff0c;主要包含以下这些&#xff1a; 以下是您提到的 Xilinx 7 系列 FPGA 中各种模块的含义及用途&#xff1a; 1. BRAM (Block RAM) BRAM 是 FPGA 中的块存储资源&…

如何解决压测过程中JMeter堆内存溢出问题

如何解决压测过程中JMeter堆内存溢出问题 背景一、为什么会堆内存溢出&#xff1f;二、解决堆内存溢出措施三、堆内存参数应该怎么调整&#xff1f;四、堆内存大小配置建议 背景 Windows环境下使用JMeter压测运行一段时间后&#xff0c;JMeter日志窗口报错“java.lang.OutOfMe…

嵌入式蓝桥杯学习4 lcd移植

cubemx配置 复制前面配置过的文件 打开cubemx&#xff0c;将PB8,PB9配置为GPIO-Output。 点击GENERATE CODE. 文件移植 1.打开比赛提供的文件包&#xff0c;点击Inc文件夹 2.点击Inc文件夹。复制fonts.h和lcd.h&#xff0c;粘贴到我们自己的工程文件夹的bsp中&#xff08…

学习记录,正则表达式, 隐式转换

正则表达式 \\&#xff1a;表示正则表达式 W: 表示一个非字&#xff08;不是一个字&#xff0c;例如&#xff1a;空格&#xff0c;逗号&#xff0c;句号&#xff09; W: 多个非字 基本组成部分 1.字符字面量&#xff1a; 普通字符&#xff1a;在正则表达式中&#xff0c;大…

标书里的“废标雷区”:你踩过几个?

在投标领域&#xff0c;标书的质量不仅决定了中标的可能性&#xff0c;更是体现企业专业度的关键。但即便是经验丰富的投标人&#xff0c;也难免会在标书编制过程中踩中“废标雷区”。这些雷区可能隐藏在技术方案的细节中&#xff0c;也可能是投标文件格式的规范问题。以下&…

k8s-编写CSI插件(3)

1、概述 在 Kubernetes 中&#xff0c;存储插件的开发主要有以下几种方式&#xff1a; CSI插件&#xff1a;Container Storage Interface (CSI) 是 Kubernetes 的标准插件接口&#xff0c;是全新的插件方案&#xff0c;插件和驱动调用通过grpc协议&#xff0c;功能丰富&#x…