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

news2024/12/26 22:09:59

一、 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.coloradmin.cn/o/2252935.html

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

相关文章

linux(centos) 环境部署,安装JDK,docker(mysql, redis,nginx,minio,nacos)

目录 1.安装JDK (非docker)1.1 将文件放在目录下&#xff1a; /usr/local/jdk1.2 解压至当前目录1.3 配置环境变量 2.安装docker2.1 验证centos内核2.2 安装软件工具包2.3 设置yum源2.4 查看仓库中所有docker版本&#xff0c;按需选择安装2.5 安装docker2.6 启动docker 并 开机…

CODESYS可视化秒表分批计时详细制作案例(一)

#制作一个在可视化界面可用于秒表计时的详细案例# 前言: 在电脑和手机的时钟上,都有一个秒表计时的功能。除此之外,在赛事上,也有更为专业的秒表计时器设备。举一反三,那么对于工控设备,为了衡量生产效率和节拍,引入了"Cycle Time(CT)"的概念,我们可以通…

openGauss开源数据库实战十八

文章目录 任务十八 openGauss逻辑结构:构:用户和权眼管理任务目标实施步骤一、准备工作二、用户和角色管理1.使用CREATE USER语句创建用户2.使用CREATE ROLE语句创建用户3.删除用户和角色 三、权限管理1.系统权限清理工作 任务十八 openGauss逻辑结构:构:用户和权眼管理 任务目…

Scratch游戏推荐 | 我的世界:平台冒险——像素世界的全新挑战! ⛏️

&#x1f3ae; Scratch游戏推荐 | 我的世界&#xff1a;平台冒险——像素世界的全新挑战&#xff01; ⛏️&#x1f30d; 今天给大家推荐一款精彩绝伦的Scratch平台冒险游戏——《我的世界&#xff1a;平台冒险 – 第二章》&#xff01;由atomicmagicnumber制作&#xff0c;这…

【java-数据结构篇】揭秘 Java LinkedList:链表数据结构的 Java 实现原理与核心概念

我的个人主页 我的专栏&#xff1a;Java-数据结构&#xff0c;希望能帮助到大家&#xff01;&#xff01;&#xff01;点赞❤ 收藏❤ 目录 1. Java LinkedList 基础 1.1 LinkedList 简介 1.2 LinkedList 的实现原理 1.3 LinkedList 与 ArrayList 的区别 2. 链表基础 2.1 链…

北斗道路运输车辆管理应用:违规驾驶行为监测、车辆编队管理、安全跟踪(车辆历史轨迹查询)、车辆动态位置数据的实时查看和管理

文章目录 场景概述解决方案应用案例合作构想场景概述 面向旅游大巴车、危险品运输车及重型载货运输车等车辆,利用北斗定位导航服务,结合互联网通信技术,实现车辆安全驾驶管理与调度,有效降低道路事故发生风险,提升道路运输管理水平及车辆调度能力。 解决方案 在车辆上安…

【ABAP——DAILOG_2】

文章目录 使用Tabstrip控件实现分页签效果标签页的修改使用Table Control控件实现表单输出表格控件使用向导创建Table ControlTable Control列的修改 用户通过界面输入数据&#xff0c;数据通过屏幕控件传递到ABAP/4程序中的变量&#xff0c;程序在PBO中准备数据并显示界面&…

资料文件夹转移工具5.2.3 |快速转移到D盘,释放C盘空间

这是一款支持将C盘的常用文件夹转移到其他磁盘分区的工具&#xff0c;提供仅变目录、复制资料和转移资料三种转移方式。该工具完全免费&#xff0c;单文件免安装&#xff0c;大小仅为546KB&#xff0c;非常适合需要释放C盘空间的用户。 大小&#xff1a;546KB 下载地址&#…

使用STM32CubeMX配置串口各种功能

使用STM32CubeMX配置串口各种功能 STM32CubeMX软件的安装接收空闲中断STM32CubeMX配置1.新建工程2. 选择芯片3. 选择时钟和下载方式4. 配置串口5.设置工程消息6.生成代码7.修改生成的代码 空闲中断DMA转运STM32CubeMX配置4.配置串口5.设置工程消息6.生成代码7.修改生成的代码 S…

Javascript中DOM操作和事件监听综合练习 (具备三种功能的轮播图案例)

#如何去实现图片轮播效果图&#xff0c;通过创建一个基本的 HTML 页面结构&#xff0c;包含用于展示轮播图片的区域、左右切换箭头以及放置轮播图片的容器。# 整体架构 一、CSS 样式 接下来创建一个 styles.css 文件来设置页面的样式&#xff0c;让轮播效果看起来更美观。定义…

无人设备遥控器之防水性能篇

无人设备遥控器的防水性能是评估其耐用性和适应不同环境能力的重要指标。随着无人设备技术的不断发展&#xff0c;越来越多的遥控器在设计时融入了防水元素&#xff0c;以满足用户在不同天气条件下的使用需求。 一、防水等级与标准 无人设备遥控器的防水性能通常通过防水等级来…

【JAVA】Java入门 - 循环结构进阶

第1关 for循环的进阶使用-嵌套循环&#xff08;1&#xff09; 第2关 for循环的进阶使用-嵌套循环&#xff08;2&#xff09; 第3关 99乘法表 第4关 综合练习之ATM取款机 第5关 选择题 D、BC、B

【AI系统】AI 编译器后端优化

AI 编译器后端优化 AI 编译器分为多层架构&#xff0c;最顶层由各种 AI 训练框架编写的神经网络模型架构&#xff0c;一般由 Python 编写&#xff0c;常见的 AI 训练框架有 PyTorch、MindSpore、PaddlePaddle 等。在导入 AI 编译器时需要用对应框架的 converter 功能转换为 AI…

力扣 三角dp

动态规划基础题&#xff0c;当前所在元素来自上一行的两列的值。 题目 从图可以看出&#xff0c;每一行的第一个数与最后一个数都是1&#xff0c;然后中间的数是来自它左上方和右上方的数的和。当然并不是要打印这个三角形的形状&#xff0c;因此可以想到正常的打印方式应该是…

Oracle检查加强版本

支持更丰富了&#xff0c;代码也更乱了 #!/bin/bash## 实例个数 告警日志 实例状态 会话 活动会话 锁 集群状态 服务状态 磁盘空间 侦听日志 ## 单机、RAC Linux、AIX 11g、19c、23ai ## 依赖adrci配置正常&#xff0c;也可以改为 getAlert() ## ver 1.2case uname inAIX)ps…

RoBERTa- 稳健优化的 BERT 预训练模型详解

一、引言 自 BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;问世&#xff0c;预训练语言模型在自然语言处理&#xff08;NLP&#xff09;领域掀起革命浪潮&#xff0c;凭卓越表现大幅刷新诸多任务成绩。RoBERTa 承继 BERT 架构&#x…

【流程图】各元素形状和含义

判定、文档、数据、数据库、流程处理节点 矩形 - 动词 平行四边形 - 图像 下波浪 - 数据 图片来源http://baike.cu12.com/bkss/62449.shtml

「Mac畅玩鸿蒙与硬件41」UI互动应用篇18 - 多滑块联动控制器

本篇将带你实现一个多滑块联动的控制器应用。用户可以通过拖动多个滑块&#xff0c;动态控制不同参数&#xff08;如红绿蓝三色值&#xff09;&#xff0c;并实时显示最终结果。我们将以动态颜色调节为例&#xff0c;展示如何结合状态管理和交互逻辑&#xff0c;打造一个高级的…

PyQt6 开发基础

<?xml version"1.0" encoding"UTF-8"?> <ui version"4.0"><class>Form</class><widget class"QWidget" name"Form"><property name"geometry"><rect><x>0<…

Kafka如何保证消息可靠?

大家好&#xff0c;我是锋哥。今天分享关于【Kafka如何保证消息可靠&#xff1f;】面试题。希望对大家有帮助&#xff1b; Kafka如何保证消息可靠&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Kafka通过多种机制来确保消息的可靠性&#xff0c;主要包…