文章

WebSocket

WebSocket的帧格式

WebSocket 使用了自定义的二进制分帧格式,将每个应用消息切分成一个或多个帧,对端等到接收到完整的消息后再进行组装与处理。

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 ...                |
 +---------------------------------------------------------------+

主要介绍两个关键的字段:

  • FIN。占1bit。表示后续是否还有帧。一个消息可能拆分成多个帧,接收方判断为最后一帧后将前面的帧拼接组成消息。TCP没有粘包,粘包是不合理的应用层协议设计导致的问题。
  • opcode。占4bit。
  • 8表示close(关闭连接)帧,主动关闭连接时需要发送这个控制指令。否则websocket会报1006错误,这个错误码可以用于区分连接是正常关闭的,还是其他异常情况。
  • 9表示ping帧,10表示pong帧。ping/pong机制是为了在长时间无消息通信时,检测连接是否断开。目前只能由服务器发ping给浏览器,浏览器返回pong消息。浏览器目前没有开放发送控制指令的接口。

  • FIN :1 bit 表示消息结束标志位。0表示还有后续帧, 1表示最后一帧。一个消息可能拆分成多个帧,接收方判断为最后一帧后将前面的帧拼接组成消息。

  • RSV1RSV2RSV3 :1 bit 保留字段,除非一个扩展经过协商赋予了非零值的某种含义,否则必须为0。

  • opcode :4 bit 解释 payload data 的类型。如果收到识别不了的opcode,会直接断开。0表示连续的帧; 1表示text(纯文本)帧; 2表示binary(二进制)帧 ; 8表示close(关闭连接)帧; 9表示ping帧 ;10表示pong帧;其余为非控制帧而预留。ping/pong类型帧是为了在长时间无消息通信时,检测连接是否断开,目前只能由服务器发ping给浏览器,浏览器返回pong消息。

    1
    2
    3
    4
    5
    6
    7
    8
    
    %x0:表示一个延续帧。当 Opcode  0 时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片;
    %x1:表示这是一个文本帧(frame);
    %x2:表示这是一个二进制帧(frame);
    %x3-7:保留的操作代码,用于后续定义的非控制帧;
    %x8:表示连接断开;
    %x9:表示这是一个 ping 操作;
    %xA:表示这是一个 pong 操作;
    %xB-F:保留的操作代码,用于后续定义的控制帧。
    
  • MASK :1 bit 标识 Payload data 是否经过掩码处理,如果是 1,Masking-key域的数据即为掩码密钥,用于解码Payload data。在标准规定,客户端发送数据必须使用掩码,而服务器发送则一定不使用掩码。所有客户端发送到服务端的数据帧,Mask都是1。

  • Payload len :7 bit | 7+16 bit | 7+64 bit 表示了 “有效负荷数据 Payload data”的长度:(1)如果是 0~125,那么就直接表示了 payload 长度 (2) 如果是 126,那么接下来的两个字节表示的 16位无符号整型数的值就是 payload 长度 (3)如果是 127,那么接下来的八个字节表示的 64位无符号整型数的值就是 payload 长度

    1
    2
    3
    4
    
    载荷数据的长度
    x  0~126:数据的长度为 x 字节;
    x  126:后续 2 个字节代表一个 16 位的无符号整数,该无符号整数的值为数据的长度;
    x  127:后续 8 个字节代表一个 64 位的无符号整数(最高位为 0),该无符号整数的值为数据的长度
    
  • Masking-key :0 | 4 bytes 掩码密钥。所有从客户端发送到服务端的帧都包含一个 32bits 的掩码(如果mask被设置成1),否则为0。一旦掩码被设置,所有接收到的 payload data 都必须与该值以一种算法做异或运算来获取真实值。

    • client选取的32为随机数

    • 掩码计算方法如下

      1
      2
      3
      4
      5
      
      original-octet-i:为原始数据的第i字节。
      transformed-octet-i:为转换后的数据的第i字节。
          
      j = i MOD 4
      transformed-octet-i = original-octet-i XOR masking-key-octet-j
      
  • Payload data :(x+y) bytes (载荷数据 = 扩展数据 + 应用数据) 它是 Extension data 和 Application data 数据的总和,但是一般扩展数据为空。

  • Extension data :x bytes 除非扩展被定义,否则就是0

  • Application data :y bytes 占据 Extension data 后面的所有空间

连接关闭

  • 关闭帧包含一个关闭的状态码和指定的原因; 如果关闭帧中没有状态码,状态码默认为1005,如果websocket已经关闭,并且终端没有收到任何关闭帧,那么websocket的关闭状态码为1006
  • 应用在发送了关闭帧之后,不能发送任何的数据帧
  • 如果一端收到了关闭帧,并且之前没有发送关闭帧,这一端必须回复一个关闭帧(尽快回包,也可能延迟发送回包,直到当前message发送完成)
  • 当一端发送和接收到了关闭报文,该端可以考虑关闭websocket连接,并且必须关闭底层的TCP连接; 如果是sever端必须立刻关闭TCP连接,client端等待server端关闭链接,但是也有可能关闭连接在收到关闭报文之后的任意时间(例如. 没有收到关闭的报文)
  • 两端同时发送关闭报文,两端都会发送和接收到close报文,应该考虑关闭websocket连接和底层的tcp连接

iOS websocket 抓包工具:Charles 、 Wireshark 、mitmproxy 、Socks5 代理(付费)

本文由作者按照 CC BY 4.0 进行授权