websocket协议解析

websocket 协议报文

websocket协议也是基于tcp协议,和http不同的是,tcp接受的数据包为二进制帧,而http为字符串数据包。并且websocket协议在连接阶段会触发一个http请求进行websocket协议校验。校验成功后才会接管tcp通讯流程不会断开该http连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
websocket 数据帧报文

0 1 2 3 4
0 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8
+-+-+-+-+-------+-+-------------+-------------------------------+
|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 ... |
+---------------------------------------------------------------+

WebSocket协议详解

WebSocket 协议解决了浏览器和服务器之间的全双工通信问题。在 WebSocket 出现之前,浏览器如果需要从服务器及时获得更新,则需要不停的对服务器主动发起请求,也就是 Web 中常用的poll技术。这样的操作非常低效,这是因为每发起一次新的 HTTP 请求,就需要单独开启一个新的 TCP 链接,同时 HTTP 协议本身也是一种开销非常大的协议。为了解决这些问题,所以出现了 WebSocket 协议。WebSocket 使得浏览器和服务器之间能通过一个持久的 TCP 链接就能完成数据的双向通信。关于 WebSocket 的 RFC 提案,可以参看RFC6455。

WebSocket 和 HTTP 协议一般情况下都工作在浏览器中,但 WebSocket 是一种完全不同于 HTTP 的协议。尽管,浏览器需要通过 HTTP 协议的GET请求,将 HTTP 协议升级为 WebSocket 协议。升级的过程被称为握手(handshake)。当浏览器和服务器成功握手后,则可以开始根据 WebSocket 定义的通信帧格式开始通信了。像其他各种协议一样,WebSocket 协议的通信帧也分为控制数据帧和普通数据帧,前者用于控制 WebSocket 链接状态,后者用于承载数据。下面我们将一一分析 WebSocket 协议的握手过程以及通信帧格式。

一、websocket握手

握手的过程也就是将HTTP协议升级为WebSocket协议的过程,握手开始首先由浏览器端发送一个GET请求,该请求的HTTP头部信息如下:

1
2
3
4
5
6
7
8
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protcol: chat, superchat
Sec-WebSocket-Version: 13

当服务器端,成功验证了以上信息后,则会返回一个形如以下的响应:

1
2
3
4
5
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

可以看到,浏览器发送端HTTP请求中,增加了一些新端字段,其作用如下所示:

  • Upgrade: 规定必需的字段,其值必需为 websocket, 如果不是则握手失败;
  • Connection: 规定必需的字段,值必需为 Upgrade, 如果不是则握手失败;
    Sec-WebSocket-Key: 必需字段,一个随机的字符串;
    Sec-WebSocket-Protocol: 可选字段,可以用于标识应用层的协议;
    Sec-WebSocket-Version: 必需字段,代表了 WebSocket 协议版本,值必需是 13, 否则握手失败;

    返回端响应中,如果握手成功会返回状态码101的HTTP响应,同时其他字段说明如下:

  • Upgrade: 规定必需的字段,其值必需为 websocket, 如果不是则握手失败;
  • Connection: 规定必需的字段,值必需为 Upgrade, 如果不是则握手失败;
  • Sec-WebSocket-Accept: 规定必需的字段,该字段的值是通过固定字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11加上请求中Sec-WebSocket-Key字段的值,然后再对其结果通过 SHA1 哈希算法求出的结果。
  • Sec-WebSocket-Protocol: 对应于请求中的 Sec-WebSocket-Protocol 字段;

    当浏览器和服务端成功握手后,就可以传递数据了,传送数据是按照WebSocket的数据格式生成的

    二、WebSocket协议数据帧

    数据帧的定义类似与TCP/IP的格式定义,具体看下图:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     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 ... |
    +---------------------------------------------------------------+

以上这张图,一行代表32bit(位),也就是4bytes(字节),总体上包含两份,帧头部和数据内容。每个从WebSocket链接中接受到的数据帧,都要按照以上格式进行解析,这样才能知道该数据帧是用于控制的还是用于传送数据的,关于以上数据帧的各个比特位的解释如下:

  • FIN:1bit,当该比特位值为%x0时,表示后面还有更多的数据帧,%x1时表示这是最后一个数据帧;
  • RSV1,RSV2,RSV3:各占1个比特位。一般情况下全为0,当客户端、服务端协商采用WebSocket扩展时,这三个标识位可以非0,且值当含义由扩展进行定义,如果出现非0当值,且没有采用WebSocket扩展,则链接出错
  • opcode:4 bit,用于表明数据帧当类型,一共可以表示16种帧类型,如下所示:
    • %x0:表示这是一个分片当帧,它属于前面帧当后续帧;
    • %x1:表示该数据帧携带的数据类型是文本类型,且编码utf-8
    • %x2 : 表示携带的是二进制数据;
    • %x3-7 : 保留未使用;
    • %x8 : 表示该帧用于关闭 WebSocket 链接;
    • %x9 : 表示该帧代表了 ping 操作;
    • %xA : 表示该帧代表了 pong 回应;
    • %xB-F : 保留未使用;
  • MASK:1 bit,%x0表示数据帧没有经过掩码计算,而%x1则表示数据帧已经经过掩码计算,得到真正当数据需要解码,一般情况下,只有浏览器发送给服务端当数据帧才需要进行掩码计算;
  • Payload len:7 bit,表示了数据帧携带当数据长度,7 bit 的值根据三种情况,帧的解析有所不同:
    • %x0 - 7D : 也就是从 0 到 125,表示数据长度, 数据总长度也就是 7 bit 代表的长度;
    • %x7E : 7 bit 的值是 126 时,则后续的 2 个字节(16 bit)表示的一个 16 位无符号数,这个数用来表示数据的长度;
    • %x7F : 7 bit 的值是 127 时,则后续的 8 个字节(64 bit)表示的一个 64 位无符号数,这个数用来表示数据的长度;
      • Masking-key: 32 bit, 表示了用于解码的 key,只有当 MASK 比特位的值为 %x1 是,才有该数据;
  • Payload Data: 余下的比特位用于存储具体的数据;

    通过以上分析可以看出,WebSocket 协议数据帧的最大头部为 2 + 8 + 4 = 14 bytes 也就是 14 个字节。同时我们要实现 WebSocket 协议,最主要的工作就是实现对数据帧的解析。