Espressif 玩转 WebSocket

本文仅针对 ESP32

ESP32 使用 WebSocket 进行通信的场景比较少,大部分使用的应用层协议是 MQTTHTTP。不过对于 WebSocket,基本的了解还是要的。最近正好有时间,就撸一下 WebSocket

本文只是简单的对 WebSocket 进行分析,如果想深入了解的话,建议还是参考 RFC6455

WebSocket 是什么?

WebSocket 是一种网络通信协议。跟 MQTT、HTTP 协议一样,它也属于 TCP/IP 四层模型中的应用层协议。

WebSocket 协议在 2008 年诞生,2011 年成为国际标准。HTML5 开始提供的一种浏览器与服务器进行全双工通讯的网络技术。它基于 TCP 传输,并复用 HTTP 的握手。

WebSocket 与 HTTP 的关系?

对于 HTTP,我们都知道它具有以下两个特性:

  1. 无连接:每次 HTTP 连接仅仅处理一个请求。服务器返回客户端的请求后,这次的 HTTP 连接也就意味着断开了。该特性可以大大提高服务器的执行效率。
  2. 无状态:HTTP 协议对于请求/应答过程不保存状态信息。意味着后续的处理如果需要前面的信息,就只能通过重传来实现。该特性可以减轻服务器的记忆负担,提高服务器的响应速度。

这两个特性在理论上大大提高了服务器的执行效率和响应速度。但也掩盖不了 HTTP 协议的一个弊端,那就是通信只能由客户端发起。这在以前倒是没什么问题,可现在的应用对实时性的要求越来越高,典型的应用场景就是直播,网页游戏,股票交易等。虽然可以通过轮询的方式加以解决,但轮询的效率非常低,不停的轮询在无形之中又加重了服务器的负担,这与 HTTP 的设计初衷又背道而驰。

看到这里,肯定有读者想到了 HTTP 的长连接。没错,当一个 HTTP 的请求被服务器响应后,HTTP 基于的 TCP 连接不会被关闭。客户端如果想再次发送 HTTP 的请求,可以直接在该 TCP 连接上发送。相比较轮询的方式,长连接算是一种比较优雅的方式。HTTP 的长连接是在响应头中加入头部字段 Connection:keep-alive 来实现。但长连接不会永久保持连接,也有一个保持的时间。HTTP 的长连接本质上就是 TCP 的长连接

  • 相同点

    1. 均属于 TCP/IP 四层模型中的应用层协议。
    2. 均是基于 TCP
    3. WebSocketHTTP 有良好的兼容性,默认端口也是 80443
  • 不同点
    HTTP 是单向的,只能由客户端发起 Request 请求;WebSocket 是双向的。

  • 联系
    WebSocket 仅仅只是借 HTTP 来完成握手。握手成功之后,WebSocket 有其自有的帧进行通信。

WebSocket 与 Socket 的关系?

对于 Socket,我们都知道它仅仅是一个抽象概念,它将 TCP/IP 层操作抽象为几个简单的接口供应用程序调用。对于用户来说,仅仅需要调用几个 socket 接口就可以完成主机之间的通信。

Socket 也可以用来表示网络中一个连接的两端WebSocket 仅仅是借用了这个概念来表示网络中一个连接的两端,而这两端可以等视作浏览器和服务器

WebSocket 简单示例

Espressif 已经提供了 WebSocket 的 demo。不过在该 demo 中使用的服务器已经不在提供服务,所以可以使用公共的服务器或者在本地自己搭建一个服务器。本文选择后者。

echo.websocket.org

搭建 WebSocket 本地服务器

本文在 Windows 环境下搭建基于 nodejs 平台实现的 WebSocket 测试工具 wscat

参考官方说明:wscat

安装步骤:

  1. 安装 nodejs
  2. 安装完成后在命令行下输入以下命令全局安装 wscat
npm install -g wscat

启动 WebSocket 服务端

安装完成之后,输入以下命令在本地 8080 端口启动 WebSocket 服务。

wscat -l 8080

wscat listen

启动 WebSocket 客户端

WebSocket 目录下执行以下命令进行配置(设置 WebSocket 的 URL):

idf.py menuconfig

WebSocket URL config
将固件下载到 ESP32 上并运行,就可以看到在 WebSocket 服务端不断接收到从 ESP32 上发送来的文本数据。
WebSocket receive

WebSocket 协议

结合 Wireshark 抓包,可以简单的对 WebSocket 协议进行分析。
Wireshark packets

握手

  • 建立 TCP 连接

    这个不用过多的介绍,TCP 建立连接需要 3 次握手。包 23545 (SYN),包 23547 (SYN, ACK) 和包 24983 (ACK) 分别表示 TCP 的 3 次握手包。

  • 握手

    WebSocket 借助 HTTP 来完成握手。客户端首先需要发送握手包,握手包以 HTTP GET Request 的结构发送给服务端,服务端解析了之后以 HTTP Response 的结构发送给客户端,其中 Response 中的状态码为 101 表示握手成功。

WebSocket 协议文档中对客户端发送的握手请求包有如下要求:
1. 握手请求包必须为一个有效的 HTTP Request
2. Request Method 必须为 Get,Request Version 必须至少为 1.1 版本
3. 请求头部中必须包含一个 Host 键值对,如果未在请求头部中使用 port 键值对的话,那在 Host 所对应的值中必须指明端口号
4. 请求头部中必须包含一个 Upgrade 键值对,值必须为 websocket
5. 请求头部中必须包含一个 Connection 键值对,值必须为 Upgrade
6. 请求头部中必须包含一个 Sec-WebSocket-Key 键值对,值必须是随机产生的 16 字节,以 base64 编码
7. 如果客户端是浏览器,则请求头部中必须包含一个 Origin 键值对
8. 请求头部中必须包含一个 Sec-WebSocket-Version 键值对,值必须为 13
9. 请求头部中可以包含一个 Sec-WebSocket-Protocol 键值对
10. 请求头部中可以包含一个 Sec-WebSocket-Extensions 键值对

我们来看一下 ESP32 发送的用于握手的 GET Request 包,基本满足协议中对握手请求包的要求。
Handshark Get Request
如果服务端选择接收连接,它必须回复一个有效的 HTTP Response 包。

WebSocket 协议文档中对服务端回复的 HTTP Response 有如下要求:

  1. 状态码必须为 101,原因短语可以为 Switching Protocols
  2. 首部行中必须有 Upgrade 键,值为 websocket
  3. 首部行中必须有 Connection 键,值为 Upgrade
  4. 首部行中必须有 Sec-WebSocket-Accept 键,值是根据客户端 Get Request 中的Sec-WebSocket-Key 键值生成,流程是先经过 SHA-1 加密成 20 字节的数据,在将这些数据以 base64 编码。

我们来看一下 wscat 回复的 HTTP Response 包,基本满足协议中的要求。
Handshark HTTP Response

数据交互

客户端和服务端要进行数据通信,首先要了解的就是 WebSocket 中的数据格式。WebSocket 中的数据帧比较简单,格式如下:
Data Framing

  1. FIN: 1 bit

    用来指示该帧是否是整个要发送的消息的最后一帧。如果整个消息能通过一帧发送,则也要置位。

  2. RSV1, RSV2, RSV3: 1 bit

    保留。必须为 0。

  3. opcode: 4 bits

    操作码。是对 Payload Data 域的解释。 如果接收端接收到未定义的操作码,则必须结束 WebSocket 连接。

    定义
    %x0表明这一帧数据是上一帧数据的延续,这一帧是一个连续帧
    %x1表明这一帧数据是一个文本帧
    %x2表明这一帧数据是一个二进制数据帧
    %x3 - 7保留,适用于未来的非控制帧
    %x8表明这一帧是个断开连接帧
    %x9ping 帧
    %xApong 帧
    %xB - F保留,适用于未来的控制帧
  4. MASK: 1 bit

    掩码位。指明 Payload Data 域中的数据是否经过 XOR(异或) 运算。如果值为 1,则 Masking-key 域中的值用来还原经过 XOR(异或) 过的数据。所有从客户端发给服务端的帧必须置 1

  5. Payload len: 7 bits, 7 + 16 bits, 7 + 64 bits

    负载长度位,必须结合 Extended payload length 域一起指明 Payload Data 域中数据的长度,以字节为单位 。注意 Payload len 域和 Extended payload length 域中均采用网络字节序(大端模式)

    1. Payload len 域中的值在 [0, 125] 中时,该域中的值就代表实际的数据长度,此时 Extended payload length 域消失。Payload len 域后为 Masking-key 域。
    2. Payload len 域中的值在 126 时,该域中的值就表明后续的 Extended payload length 域为 16 bits。Payload Data 域中数据的长度由 Extended payload length 域指明。
    3. Payload len 域中的值在 127 时,该域中的值就表明后续的 Extended payload length 域为 64 bits。Payload Data 域中数据的长度由 Extended payload length 域指明。
  6. Masking-key: 0 bit 或 32 bits

    掩码钥匙位。 MASK 域为 1 时有效。所有从客户端发给服务端的帧 MASK 域必须置 1。换句话说 Masking-key 域就是专门用来服务服务端的。服务端需要该域中的 key 用来解码 Payload len 域中的数据。所有服务端发送给客户端的帧设置 MASK 域为 0,此时帧中的 Masking-key 域省略。

未完待续…

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
2022 / 01/ 30: 新版esptool 刷micropython固件指令不是 esptool.py cmd... 而是 esptool cmd... 即可;另外rshell 在 >= python 3.10 的时候出错解决方法可以查看:  已于2022年发布的: 第二章:修复rshell在python3.10出错 免费内容: https://edu.csdn.net/course/detail/29666 micropython语法和python3一样,编写起来非常方便。如果你快速入门单片机玩物联网而且像轻松实现各种功能,那绝力推荐使用micropython。方便易懂易学。 同时如果你懂C语音,也可以用C写好函数并编译进micropython固件里然后进入micropython调用(非必须)。 能通过WIFI联网(2.1章),也能通过sim卡使用2G/3G/4G/5G联网(4.5章)。 为实现语音控制,本教程会教大家使用tensorflow利用神经网络训练自己的语音模型并应用。为实现通过网页控制,本教程会教大家linux(debian10 nginx->uwsgi->python3->postgresql)网站前后台入门。为记录单片机传输过来的数据, 本教程会教大家入门数据库。  本教程会通过通俗易懂的比喻来讲解各种原理与思路,并手把手编写程序来实现各项功能。 本教程micropython版本是 2019年6月发布的1.11; 更多内容请看视频列表。  学习这门课程之前你需要至少掌握: 1: python3基础(变量, 循环, 函数, 常用库, 常用方法)。 本视频使用到的零件与淘宝上大致价格:     1: 超声波传感器(3)     2: MAX9814麦克风放大模块(8)     3: DHT22(15)     4: LED(0.1)     5: 8路5V低电平触发继电器(12)     6: HX1838红外接收模块(2)     7:红外发射管(0.1),HX1838红外接收板(1)     other: 电表, 排线, 面包板(2)*2,ESP32(28)  

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式工程狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值