本文主要是介绍Espressif 玩转 WebSocket,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
本文仅针对 ESP32。
ESP32 使用 WebSocket 进行通信的场景比较少,大部分使用的应用层协议是 MQTT 和 HTTP。不过对于 WebSocket,基本的了解还是要的。最近正好有时间,就撸一下 WebSocket。
本文只是简单的对 WebSocket 进行分析,如果想深入了解的话,建议还是参考 RFC6455。
文章目录
- WebSocket 是什么?
- WebSocket 与 HTTP 的关系?
- WebSocket 与 Socket 的关系?
- WebSocket 简单示例
- 搭建 WebSocket 本地服务器
- 启动 WebSocket 服务端
- 启动 WebSocket 客户端
- WebSocket 协议
- 握手
- 数据交互
WebSocket 是什么?
WebSocket 是一种网络通信协议。跟 MQTT、HTTP 协议一样,它也属于 TCP/IP 四层模型中的应用层协议。
WebSocket 协议在 2008 年诞生,2011 年成为国际标准。HTML5 开始提供的一种浏览器与服务器进行全双工通讯的网络技术。它基于 TCP 传输,并复用 HTTP 的握手。
WebSocket 与 HTTP 的关系?
对于 HTTP,我们都知道它具有以下两个特性:
- 无连接:每次 HTTP 连接仅仅处理一个请求。服务器返回客户端的请求后,这次的 HTTP 连接也就意味着断开了。该特性可以大大提高服务器的执行效率。
- 无状态:HTTP 协议对于请求/应答过程不保存状态信息。意味着后续的处理如果需要前面的信息,就只能通过重传来实现。该特性可以减轻服务器的记忆负担,提高服务器的响应速度。
这两个特性在理论上大大提高了服务器的执行效率和响应速度。但也掩盖不了 HTTP 协议的一个弊端,那就是通信只能由客户端发起。这在以前倒是没什么问题,可现在的应用对实时性的要求越来越高,典型的应用场景就是直播,网页游戏,股票交易等。虽然可以通过轮询的方式加以解决,但轮询的效率非常低,不停的轮询在无形之中又加重了服务器的负担,这与 HTTP 的设计初衷又背道而驰。
看到这里,肯定有读者想到了 HTTP 的长连接。没错,当一个 HTTP 的请求被服务器响应后,HTTP 基于的 TCP 连接不会被关闭。客户端如果想再次发送 HTTP 的请求,可以直接在该 TCP 连接上发送。相比较轮询的方式,长连接算是一种比较优雅的方式。HTTP 的长连接是在响应头中加入头部字段 Connection:keep-alive
来实现。但长连接不会永久保持连接,也有一个保持的时间。HTTP 的长连接本质上就是 TCP 的长连接。
-
相同点:
- 均属于 TCP/IP 四层模型中的应用层协议。
- 均是基于 TCP。
- WebSocket 与 HTTP 有良好的兼容性,默认端口也是
80
和443
。
-
不同点:
HTTP 是单向的,只能由客户端发起 Request 请求;WebSocket 是双向的。 -
联系:
WebSocket 仅仅只是借 HTTP 来完成握手。握手成功之后,WebSocket 有其自有的帧进行通信。
WebSocket 与 Socket 的关系?
对于 Socket,我们都知道它仅仅是一个抽象概念,它将 TCP/IP 层操作抽象为几个简单的接口供应用程序调用。对于用户来说,仅仅需要调用几个 socket 接口就可以完成主机之间的通信。
Socket 也可以用来表示网络中一个连接的两端。WebSocket 仅仅是借用了这个概念来表示网络中一个连接的两端,而这两端可以等视作浏览器和服务器。
WebSocket 简单示例
Espressif 已经提供了 WebSocket 的 demo。不过在该 demo 中使用的服务器已经不在提供服务,所以可以使用公共的服务器或者在本地自己搭建一个服务器。本文选择后者。
搭建 WebSocket 本地服务器
本文在 Windows 环境下搭建基于 nodejs 平台实现的 WebSocket 测试工具 wscat。
参考官方说明:wscat
安装步骤:
- 安装 nodejs
- 安装完成后在命令行下输入以下命令全局安装 wscat
npm install -g wscat
启动 WebSocket 服务端
安装完成之后,输入以下命令在本地 8080
端口启动 WebSocket 服务。
wscat -l 8080
启动 WebSocket 客户端
在 WebSocket 目录下执行以下命令进行配置(设置 WebSocket 的 URL):
idf.py menuconfig
将固件下载到 ESP32 上并运行,就可以看到在 WebSocket 服务端不断接收到从 ESP32 上发送来的文本数据。
WebSocket 协议
结合 Wireshark 抓包,可以简单的对 WebSocket 协议进行分析。
握手
-
建立 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 包,基本满足协议中对握手请求包的要求。
如果服务端选择接收连接,它必须回复一个有效的 HTTP Response 包。
在 WebSocket 协议文档中对服务端回复的 HTTP Response 有如下要求:
- 状态码必须为
101
,原因短语可以为Switching Protocols
- 首部行中必须有
Upgrade
键,值为websocket
- 首部行中必须有
Connection
键,值为Upgrade
- 首部行中必须有
Sec-WebSocket-Accept
键,值是根据客户端 Get Request 中的Sec-WebSocket-Key
键值生成,流程是先经过 SHA-1 加密成 20 字节的数据,在将这些数据以 base64 编码。
我们来看一下 wscat 回复的 HTTP Response 包,基本满足协议中的要求。
数据交互
客户端和服务端要进行数据通信,首先要了解的就是 WebSocket 中的数据格式。WebSocket 中的数据帧比较简单,格式如下:
-
FIN
: 1 bit用来指示该帧是否是整个要发送的消息的最后一帧。如果整个消息能通过一帧发送,则也要置位。
-
RSV1
,RSV2
,RSV3
: 1 bit保留。必须为 0。
-
opcode
: 4 bits操作码。是对
Payload Data
域的解释。 如果接收端接收到未定义的操作码,则必须结束 WebSocket 连接。值 定义 %x0 表明这一帧数据是上一帧数据的延续,这一帧是一个连续帧 %x1 表明这一帧数据是一个文本帧 %x2 表明这一帧数据是一个二进制数据帧 %x3 - 7 保留,适用于未来的非控制帧 %x8 表明这一帧是个断开连接帧 %x9 ping 帧 %xA pong 帧 %xB - F 保留,适用于未来的控制帧 -
MASK
: 1 bit掩码位。指明
Payload Data
域中的数据是否经过XOR
(异或) 运算。如果值为1
,则Masking-key
域中的值用来还原经过XOR
(异或) 过的数据。所有从客户端发给服务端的帧必须置1
。 -
Payload len
: 7 bits, 7 + 16 bits, 7 + 64 bits负载长度位,必须结合
Extended payload length
域一起指明Payload Data
域中数据的长度,以字节为单位 。注意Payload len
域和Extended payload length
域中均采用网络字节序(大端模式)。- 当
Payload len
域中的值在 [0, 125] 中时,该域中的值就代表实际的数据长度,此时Extended payload length
域消失。Payload len
域后为Masking-key
域。 - 当
Payload len
域中的值在 126 时,该域中的值就表明后续的Extended payload length
域为 16 bits。Payload Data
域中数据的长度由Extended payload length
域指明。 - 当
Payload len
域中的值在 127 时,该域中的值就表明后续的Extended payload length
域为 64 bits。Payload Data
域中数据的长度由Extended payload length
域指明。
- 当
-
Masking-key
: 0 bit 或 32 bits掩码钥匙位。
MASK
域为1
时有效。所有从客户端发给服务端的帧MASK
域必须置1
。换句话说Masking-key
域就是专门用来服务服务端的。服务端需要该域中的 key 用来解码Payload len
域中的数据。所有服务端发送给客户端的帧设置MASK
域为0
,此时帧中的Masking-key
域省略。
未完待续…
这篇关于Espressif 玩转 WebSocket的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!