深入 WebSocket 协议

大致流程

1. 通过 HTTP 握手,完成协议升级 (datatracker.ietf.org/doc/html/rf…)

客户端发送如下 Headers:

GET /chat HTTP/1.1
Host: server.example.com
Origin: http://example.com

Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
复制代码

服务端响应如下 Headers:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
复制代码

其中,服务端响应的 Sec-WebSocket-Accept 字段的值生成算法如下:

sha1( ${Sec-WebSocket-Key} + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ),其中字符串 "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 是协议指定的唯一 GUID

当完成这次握手动作,客户端与服务端就建立了一个长连接,后续两端的消息收发等操作靠解析数据帧来完成。

2. 数据帧解析 (datatracker.ietf.org/doc/html/rf…)

 
  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 ...                |
  +---------------------------------------------------------------+
  
复制代码
  • FIN: 占 1 位,表示这是消息的最后一个片段,(如果为 1,表示当前已经结束了,如果为0,表示消息分片还没结束)

  • RSV1, RSV2, RSV3: 各占1位,表示扩展协议 (extensions)

  • opcode: 占4位,表示操作码,各操作码含义如下:

    • %x0 : 表示消息帧的延续(一般来说配合 FIN = 0 使用)
    • %x1 : 表示传输的内容是文本
    • %x2 : 表示传输的内容是二进制
    • %x3-7 : 给未来保留的非控制帧的操作吗
    • %x8 : 表示连接关闭
    • %x9 : 表示一个 ping
    • %xA : 表示一个 pong
    • %xB-F : 给未来保留的控制帧的操作码
  • MASK: 占1位,表示是否使用掩码

  • Payload len: 占7位:表示数据的字节数,该长度值的含义如下:

    • 如果值的范围是:0-125,则数据的真实长度就是该值
    • 如果值为:126,则以无符号整数的方式读取接下来的16位,读取结果为数据的真实长度
    • 如果值为:127,则以无符号整数的方式读取接下来的64位,读取结果为数据的真实长度

    Payload len 实际占有的位数可能有:7 (0-125)、7+16 (126)、7+64 (127)

  • Masking-key: 如果使用了掩码( MASK = 1), 则占32位,否则,不占空间

  • Payload Data:数据,包含2部分:

    • 扩展数据(握手时协商了扩展的时候才有,并要明确给出扩展数据的字节数)
    • 应用数据

3. 一些细节

3.1 关于掩码(datatracker.ietf.org/doc/html/rf…

掩码用于加密或解密载荷数据,加密和解码的计算方式都如下所示 (nodejs 代码):

/**
 * 处理控制帧
 * @param data 数据
 * @param maskingKey 掩码key(4个字节)
 */
function mask(data: Buffer, maskingKey: Buffer) {
	for (let i = 0; i < data.length; i++) {
    data[i] = data[i] ^ maskingKey[i % 4]
  }
}
复制代码

协议规定:客户端向服务端发送数据是,必须使用掩码;而服务端向客户端发送数据时,则不能使用掩码(datatracker.ietf.org/doc/html/rf…)

3. 2 关于 ping 和 pong 操作 (datatracker.ietf.org/doc/html/rf…)

ping 和 pong 通常用于检测两端是否处于连接正常的状态(心跳检测)

协议规定: 一端向向一端发送 ping 操作帧时,另一端必须发送一个 pong 操作帧作为响应

结语

本文只是简略地介绍了 WebSocket 协议的大致流程和部分细节, 关于协议里的大量实现细节还没有涉及到。需要更深入的了解协议内容或者实现一个完整的 WebSocket 协议还需通读协议原文?。

笔者也在学习的过程中,实现了一个带基本功能的 tiny-websocket (nodejs),有兴趣可以了解一下,欢迎一起探讨~

参考

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享