Websocket同时支持“字符串”及“二进制”数据的发送操作,因为在其发送的时候,都要进行二进制数据类型的转换。
这篇文章将会重点介绍数据包格式的重要性,以及Websocket在数据包定义上的特点。
良好的数据包结构,可以为数据解析提供良好协议基础。在实现上逻辑上提供更多的异常处理上的条件参考。
数据包的封装
在正常数据交换过程中,对发送的数据进行封装是必不可少的。对于上面进行的“连接文本”的发送相对来说只是一个特例。有空的话,找一个Websocket服务端的库进行源代码的分析。
我们常用的通讯方式,几乎都是基于TCP/UDP的方式进行数据发关的,其中TCP基于连接的发送方式比较常用。无论基于哪种方式,在数据接收端都是基于接收缓存的方式,接收一定数量的包后回调到应用层进行处理。这个时候,会产生以下几种情况:
-
数据包很大,接收缓存无法容纳完整的情况下回调到上层应用方法进行数据的处理。这个时候数据包不完整,要等下一次回调再进行合并后再进行包完整性的判断。当接一个包的数据完整后,进行数据分离,再对这个完整的数据包进行更上层的逻辑处理。
-
数据很小,发送的数据包比较频繁的情况下,接收缓存同时接收到多个完整的数据包(当然,最后一个包可能只是接收到一部分),在上层回调接口收到数据时,其实是多个包合在一起的数据。这个时候就要对数据进行分拆,分拆成一个个完整的数据包,把没接收完整的数据包数据先缓存起来,等待下一次的回调,然后再合并数据再进行分包操作。
基于上面的两种情况,在不定义数据包格式的情况下我们很难进行分包处理,所以如何定义数据包就显得尤为重要。数据格式的定义如下图:
0 1 2 3
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+---------+-+--------------+--------------+---------------+
|F|R|R|R| opcode |M| Payload len| Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+---------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+---------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+-----------------------------------------------------------------+
RSV1, RSV2, RSV3
各占1位,一般情况下全为0,当客户端、服务端协商采用WebSocket扩展时,这三个标志位可以非0。
Opcode:
占4位, Opcode是操作代码,用于标识这个数据包的操作指令,可选的操作代码如下:
- %x0:表示一个延续帧。当Opcode为0时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。
- %x1:表示这是一个文本帧(frame)
- %x2:表示这是一个二进制帧(frame)
- %x3-7:保留的操作代码,用于后续定义的非控制帧。
- %x8:表示连接断开。
- %x9:表示这是一个ping操作。
- %xA:表示这是一个pong操作。
- %xB-F:保留的操作代码,用于后续定义的控制帧。
Payload len
:数据载荷的长度,占用一个字节。但真实的包长度不一定只用这一个字节的值表示。
- 长度<=126:
Payload len
=包长度值
。 - 长度 > 126 && 长度 < UInt16.max:后面开启多两个字节的长度,用后面两个字节存放包长度,
Payload len
赋值126
。 - 长度 > UInt16.max:后面开启我多8个字节的长度,用于储存包的长度,最高位为0,
Payload len
赋值127
。
在这里,掩码操作就不作说明了,志在了解大概的过程。具体的代码如下:
/**
*Used to write things to the stream
*/
private func dequeueWrite(_ data: Data, code: OpCode, writeCompletion: (() -> ())? = nil) {