1-websocket协议分析

golang 2019-04-06

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头部信息如下:

        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

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

        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的格式定义,具体看下图:

      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 协议,最主要的工作就是实现对数据帧的解析。


本文由 小东@xiaodo 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。