研究了一下 WebSocket,在 HTTP/1.1 之上,提供了一个双工长连接,通过 header 的 Upgrade 来完成协议升级及参数交换,之后就都是 websocket 的 frame 了。其最基础的协议描述在 RFC6455,压缩部分的扩展在 RFC7962。
之前研究了一下 C 写的 Websocket,验证了一下是可以正常工作的,但它 frame 的解析部分不好单独摘出来。
client 端的 html + js websocket data send/recv 其实很好写,browser 本来就有 WebSocket 的接口给调用,拉起本地的一个 html 就行了,但是 browser 无法指定 http header 的参数(包括 ws extension),发送 text、binary 的 frame 时,是通过 js 的 string、uint8 array 来指定的。
想着找一个可以直接提供解析 websocket frame 的库,虽然找到了 C 写的 wslay,但是实际使用过程中,感觉使用实在是繁琐。
比如 websocket frame 的结构如下:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|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 ... |
+---------------------------------------------------------------+
回到之前说的 wslay,霓虹国写的东西太复杂,居然 header 没检测全都能返回好几种错误,我感觉这部分其实可以简单点,比如 header 都解析不了的时候其实可以提示数据不足(毕竟 header 数据都不足),缺点是放弃了一部分错误的检查。
在 HTTP/1.1 的协议升级到 websocket 之前,header 需要交换一些数据,比如 client 会发送如下的字段
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: fSQMpewdgSIz1IhuhL3ERQ==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
说到 permessage-deflate,server 在 deflate payload 后构建 frame 时,需要设置 rsv1 bit 为 1,要不 client 会忽略 extensions 的描述。
还有 server 在 inflate frame payload 时,实际上认为不断喂进来的 payload 是连续的 data stream,因此不需要做 inflateEnd。
这个感觉也跟 HTTP/1.1 的实际处理类似,实际每个 http requet / response 都是一个单独的 inflate / deflate stream,websocket 升级了协议后,可以认为不断发送过来(出去)的 payload 是没有结束的 http body 数据,因此不需要结束这个 inflate / delfate stream。
其他比如 ping、pong、close frame 类型没怎么研究,不过感觉 close 没啥作用,也许是用于实际关闭 tcp 链接前的一个处理吧,也许给调用层处理,在 websocket frame 这一层感觉没什么必要,毕竟无论如何总是要处理没有发送 close 的资源注销的,那又何必多此一举要这个 close 呢。