【译】WebSocket协议第五章——数据帧(Data Framing)

概述

本文为WebSocket协议的第五章,本文翻译的主要内容为WebSocket传输的数据相关内容。html

数据帧(协议正文)

5.1 概览

在WebSocket协议中,数据是经过一系列数据帧来进行传输的。为了不因为网络中介(例如一些拦截代理)或者一些在第10.3节讨论的安全缘由,客户端必须在它发送到服务器的全部帧中添加掩码(Mask)(具体细节见5.3节)。(注意:不管WebSocket协议是否使用了TLS,帧都须要添加掩码)。服务端收到没有添加掩码的数据帧之后,必须当即关闭链接。在这种状况下,服务端能够发送一个在7.4.1节定义的状态码为1002(协议错误)的关闭帧。服务端禁止在发送数据帧给客户端时添加掩码。客户端若是收到了一个添加了掩码的帧,必须当即关闭链接。在这种状况下,它可使用第7.4.1节定义的1002(协议错误)状态码。(这些规则可能会在未来的规范中放开)。算法

基础的数据帧协议使用操做码、有效负载长度和在“有效负载数据”中定义的放置“扩展数据”与“引用数据”的指定位置来定义帧类型。特定的bit位和操做码为未来的协议扩展作了保留。数组

一个数据帧能够在开始握手完成以后和终端发送了一个关闭帧以前的任意一个时间经过客户端或者服务端进行传输(第5.5.1节)。缓存

5.2 基础帧协议

在这节中的这种数据传输部分的有线格式是经过ABNFRFC5234来进行详细说明的。(注意:不像这篇文档中的其余章节内容,在这节中的ABNF是对bit组进行操做。每个bit组的长度是在评论中展现的。在线上编码时,最高位的bit是在ABNF最左边的)。对于数据帧的高级的预览能够见下图。若是下图指定的内容和这一节中后面的ABNF指定的内容有冲突的话,如下图为准。安全

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 bit服务器

​ 表示这是消息的最后一个片断。第一个片断也有多是最后一个片断。网络

RSV1,RSV2,RSV3: 每一个1 bit闭包

​ 必须设置为0,除非扩展了非0值含义的扩展。若是收到了一个非0值可是没有扩展任何非0值的含义,接收终端必须断开WebSocket链接。app

Opcode: 4 bit编码

​ 定义“有效负载数据”的解释。若是收到一个未知的操做码,接收终端必须断开WebSocket链接。下面的值是被定义过的。

​ %x0 表示一个持续帧

​ %x1 表示一个文本帧

​ %x2 表示一个二进制帧

​ %x3-7 预留给之后的非控制帧

​ %x8 表示一个链接关闭包

​ %x9 表示一个ping包

​ %xA 表示一个pong包

​ %xB-F 预留给之后的控制帧

Mask: 1 bit

​ mask标志位,定义“有效负载数据”是否添加掩码。若是设置为1,那么掩码的键值存在于Masking-Key中,根据5.3节描述,这个通常用于解码“有效负载数据”。全部的从客户端发送到服务端的帧都须要设置这个bit位为1。

Payload length: 7 bits, 7+16 bits, or 7+64 bits

​ 以字节为单位的“有效负载数据”长度,若是值为0-125,那么就表示负载数据的长度。若是是126,那么接下来的2个bytes解释为16bit的无符号整形做为负载数据的长度。若是是127,那么接下来的8个bytes解释为一个64bit的无符号整形(最高位的bit必须为0)做为负载数据的长度。多字节长度量以网络字节顺序表示(译注:应该是指大端序和小端序)。在全部的示例中,长度值必须使用最小字节数来进行编码,例如:长度为124字节的字符串不可用使用序列126,0,124进行编码。有效负载长度是指“扩展数据”+“应用数据”的长度。“扩展数据”的长度可能为0,那么有效负载长度就是“应用数据”的长度。

Masking-Key: 0 or 4 bytes

​ 全部从客户端发往服务端的数据帧都已经与一个包含在这一帧中的32 bit的掩码进行过了运算。若是mask标志位(1 bit)为1,那么这个字段存在,若是标志位为0,那么这个字段不存在。在5.3节中会介绍更多关于客户端到服务端增长掩码的信息。

Payload data: (x+y) bytes

​ “有效负载数据”是指“扩展数据”和“应用数据”。

Extension data: x bytes

​ 除非协商过扩展,不然“扩展数据”长度为0 bytes。在握手协议中,任何扩展都必须指定“扩展数据”的长度,这个长度如何进行计算,以及这个扩展如何使用。若是存在扩展,那么这个“扩展数据”包含在总的有效负载长度中。

Application data: y bytes

​ 任意的“应用数据”,占用“扩展数据”后面的剩余全部字段。“应用数据”的长度等于有效负载长度减去“扩展应用”长度。

基础数据帧协议经过ABNF进行了正式的定义。须要重点知道的是,这些数据都是二进制的,而不是ASCII字符。例如,长度为1 bit的字段的值为%x0 / %x1表明的是一个值为0/1的单独的bit,而不是一整个字节(8 bit)来表明ASCII编码的字符“0”和“1”。一个长度为4 bit的范围是%x0-F的字段值表明的是4个bit,而不是字节(8 bit)对应的ASCII码的值。不要指定字符编码:“规则解析为一组最终的值,有时候是字符。在ABNF中,字符仅仅是一个非负的数字。在特定的上下文中,会根据特定的值的映射(编码)编码集(例如ASCII)”。在这里,指定的编码类型是将每一个字段编码为特定的bits数组的二进制编码的最终数据。

ws-frame =

  • frame-fin; 长度为1 bit
  • frame-rsv1; 长度为1 bit
  • frame-rsv2; 长度为1 bit
  • frame-rsv3; 长度为1 bit
  • frame-opcode; 长度为4 bit
  • frame-masked; 长度为1 bit
  • frame-payload-length; 长度为7或者7+16或者7+64 bit
  • [frame-masking-key]; 长度为32 bit
  • frame-payload-data; 长度为大于0的n*8 bit(其中n>0)

frame-fin =

  • %x0,除了如下为1的状况
  • %x1,最后一个消息帧
  • 长度为1 bit

frame-rsv1 =

  • %x0 / %x1,长度为1 bit,若是没有协商则必须为0

frame-rsv2 =

  • %x0 / %x1,长度为1 bit,若是没有协商则必须为0

frame-rsv3 =

  • %x0 / %x1,长度为1 bit,若是没有协商则必须为0

frame-opcode =

  • frame-opcode-non-control
  • frame-opcode-control
  • frame-opcode-cont

frame-opcode-non-control

  • %x1,文本帧
  • %x2,二进制帧
  • %x3-7,保留给未来的非控制帧
  • 长度为4 bit

frame-opcode-control

  • %x8,链接关闭
  • %x9,ping帧
  • %xA,pong帧
  • %xB-F,保留给未来的控制帧
  • 长度为4 bit

frame-masked

  • %x0,不添加掩码,没有frame-masking-key
  • %x1,添加掩码,存在frame-masking-key
  • 长度为1 bit

frame-payload-length

  • %x00-7D,长度为7 bit
  • %x7E frame-payload-length-16,长度为7+16 bit
  • %x7F frame-payload-length-63,长度为7+64 bit

frame-payload-length-16

  • %x0000-FFFF,长度为16 bit

frame-payload-length-63

  • %x0000000000000000-7FFFFFFFFFFFFFFF,长度为64 bit

frame-masking-key

  • 4(%x00-FF),当frame-mask为1时存在,长度为32 bit

frame-payload-data

  • frame-masked-extension-data frame-masked-application-data,当frame-masked为1时
  • frame-unmasked-extension-data frame-unmasked-application-data,当frame-masked为0时

frame-masked-extension-data

  • *(%x00-FF),保留给未来的扩展,长度为n*8,其中n>0

frame-masked-application-data

  • *(%x00-FF),长度为n*8,其中n>0

frame-unmasked-extension-data

  • *(%x00-FF),保留给未来的扩展,长度为n*8,其中n>0

frame-unmasked-application-data

  • *(%x00-FF),长度为n*8,其中n>0

5.3 客户端到服务端添加掩码

添加掩码的数据帧必须像5.2节定义的同样,设置frame-masked字段为1。

掩码值像第5.2节说到的彻底包含在帧中的frame-masking-key上。它是用于对定义在同一节中定义的帧负载数据Payload data字段中的包含Extension dataApplication data的数据进行添加掩码。

掩码字段是一个由客户端随机选择的32bit的值。当准备掩码帧时,客户端必须从容许的32bit值中须知你咋一个新的掩码值。掩码值必须是不可被预测的;所以,掩码必须来自强大的熵源(entropy),而且给定的掩码不能让服务器或者代理可以很容易的预测到后续帧。掩码的不可预测性对于预防恶意应用做者在网上暴露相关的字节数据相当重要。RFC 4086讨论了安全敏感的应用须要一个什么样的合适的强大的熵源。

掩码不影响Payload data的长度。进行掩码的数据转换为非掩码数据,或者反过来,根据下面的算法便可。这个一样的算法适用于任意操做方向的转换,例如:对数据进行掩码操做和对数据进行反掩码操做所涉及的步骤是相同的。

表示转换后数据的八位字节的i(transformed-octet-i )是表示的原始数据的i(original-octet-i)与索引i模4获得的掩码值(masking-key-octet-j)通过异或操做(XOR)获得的:

j = i MOD 4
transfromed-octed-i = original-octet-i XOR masking-key-octet-j

在规范中定义的位于frame-payload-length字段的有效负载的长度,不包括掩码值的长度。它只是Payload data的长度。如跟在掩码值后面的字节数组的数。

5.4 消息分片

消息分片的主要目的是容许发送一个未知长度且消息开始发送后不须要缓存的消息。若是消息不能被分片,那么一端必须在缓存整个消息,所以这个消息的长度必须在第一个字节发送前就须要计算出来。若是有消息分片,服务端或者代理能够选择一个合理的缓存长度,当缓存区满了之后,就想网络发送一个片断。

第二个消息分片使用的场景是不适合在一个逻辑通道内传输一个大的消息占满整个输出频道的多路复用场景。多路复用须要可以将消息进行自由的切割成更小的片断来共享输出频道。(注意:多路复用的扩展不在这个文档中讨论)。

除非在扩展中另有规定,不然帧没有语义的含义。若是客户端和服务的没有协商扩展字段,或者服务端和客户端协商了一些扩展字段,而且代理可以彻底识别全部的协商扩展字段,在这些扩展字段存在的状况下知道如何进行帧的合并和拆分,代理就可能会合并或者拆分帧。这个的一个含义是指在缺乏扩展字段的状况下,发送者和接收者都不能依赖特定的帧边界的存在。

消息分片相关的规则以下:

  • 一个未分片的消息包含一个设置了FIN字段(标记为1)的单独的帧和一个除0之外的操做码。
  • 一个分片的消息包含一个未设置的FIN字段(标记为0)的单独的帧和一个除0之外的操做码,而后跟着0个或者多个未设置FIN字段的帧和操做码为0的帧,而后以一个设置了FIN字段以及操做码为0的帧结束。一个分片的消息内容按帧顺序组合后的payload字段,是等价于一个单独的更大的消息payload字段中包含的值;然而,若是扩展字段存在,由于扩展字段定义了Extension data的解析方式,所以前面的结论可能不成立。例如:Extension data可能只出如今第一个片断的开头,并适用于接下来的片断,或者可能每个片断都有Extension data,可是只适用于特定的片断。在Extension data不存在时,下面的示例演示了消息分片是如何运做的。
    示例:一个文本须要分红三个片断进行发送,第一个片断包含的操做码为0x1而且未设置FIN字段,第二个片断的操做码为0x0而且未设置FIN字段,第三个片断的操做码为0x0而且设置了FIN字段。
  • 控制帧(见5.5节)可能被插入到分片消息的中间。控制帧不能被分片。
  • 消息片断必须在发送端按照顺序发送给接收端。
  • 除非在扩展中定义了这种嵌套的逻辑,不然一条消息分的片不能与另外一条消息分的片嵌套传输。
  • 终端必须有能力来处理在分片的消息中的控制帧。
  • 发送端可能会建立任意大小的非控制消息片断。
  • 客户端和服务端必须同时支持分片和不分片消息。
  • 控制帧不能被分片,而且代理不容许改变控制帧的片断。
  • 若是有保留字段被使用而且代理不能理解这些字段的值时,那么代理不能改变消息的片断。
  • 在扩展字段已经被协商过,可是代理不知道协商扩展字段的具体语义时,代理不能改变任意消息的片断。一样的,扩展不能看到WebSocket握手(而且得不到通知内容)致使WebSocket的链接禁止改变链接过程当中任意的消息片断。
  • 做为这些规则的结论,全部的消息片断都是同类型的,而且设置了第一个片断的操做码(opccode)字段。控制帧不能被分片,全部的消息分片类型必须是文本或者二进制,或者是保留的任意一个操做码。

注:若是控制帧没有被打断,心跳(ping)的等待时间可能会变很长,例如在一个很大的消息以后。所以,在分片的消息传输中插入控制帧是有必要的。

实践说明:若是扩展字段不存在,接收者不须要使用缓存来存储下整个消息片断来进行处理。例如:若是使用一个流式API,再收到部分帧的时候就能够将数据交给上层应用。然而,这个假设对之后全部的WebSocket扩展可能不必定成立。

5.5 控制帧

控制帧是经过操做码最高位的值为1来进行区分的。当前已经定义的控制帧操做码包括0x8(关闭),0x9(心跳Ping)和0xA(心跳Pong)。操做码0xB-0xF没有被定义,当前被保留下来作为之后的控制帧。

控制帧是用于WebSocket的通讯状态的。控制帧能够被插入到消息片断中进行传输。

全部的控制帧必须有一个126字节或者更小的负载长度,而且不能被分片。

5.5.1 关闭(Close)

控制帧的操做码值是0x8。

关闭帧可能包含内容(body)(帧的“应用数据”部分)来代表链接关闭的缘由,例如终端的断开,或者是终端收到了一个太大的帧,或者是终端收到了一个不符合预期的格式的内容。若是这个内容存在,内容的前两个字节必须是一个无符号整型(按照网络字节序)来表明在7.4节中定义的状态码。跟在这两个整型字节以后的能够是UTF-8编码的的数据值(缘由),数据值的定义不在此文档中。数据值不必定是要人能够读懂的,可是必须对于调试有帮助,或者能传递有关于当前打开的这条链接有关联的信息。数据值不保证人必定能够读懂,因此不能把这些展现给终端用户。

从客户端发送给服务端的控制帧必须添加掩码,具体见5.3节。

应用禁止在发送了关闭的控制帧后再发送任何的数据帧。

若是终端收到了一个关闭的控制帧而且没有在之前发送一个关闭帧,那么终端必须发送一个关闭帧做为回应。(当发送一个关闭帧做为回应时,终端一般会输出它收到的状态码)响应的关闭帧应该尽快发送。终端可能会推迟发送关闭帧直到当前的消息都已经发送完成(例如:若是大多数分片的消息已经发送了,终端可能会在发送关闭帧以前将剩余的消息片断发送出去)。然而,已经发送关闭帧的终端不能保证会继续处理收到的消息。

在已经发送和收到了关闭帧后,终端认为WebSocket链接以及关闭了,而且必须关闭底层的TCP链接。服务端必须立刻关闭底层的TCP链接,客户端应该等待服务端关闭链接,可是也能够在收到关闭帧之后任意时间关闭链接。例如:若是在合理的时间段内没有收到TCP关闭指令。

若是客户端和服务端咋同一个时间发送了关闭帧,两个终端都会发送和接收到一条关闭的消息,而且应该认为WebSocket链接已经关闭,同时关闭底层的TCP链接。

5.5.2 心跳Ping

心跳Ping帧包含的操做码是0x9。

关闭帧可能包含“应用数据”。

若是收到了一个心跳Ping帧,那么终端必须发送一个心跳Pong 帧做为回应,除非已经收到了一个关闭帧。终端应该尽快恢复Pong帧。Pong帧将会在5.5.3节讨论。

终端可能会在创建链接后与链接关闭前中间的任意时间发送Ping帧。

注意:Ping帧多是用于保活或者用来验证远端是否仍然有应答。

5.5.3 心跳Pong

心跳Ping帧包含的操做码是0xA。

5.5.2节详细说明了Ping帧和Pong帧的要求。

做为回应发送的Pong帧必须完整携带Ping帧中传递过来的“应用数据”字段。

若是终端收到一个Ping帧可是没有发送Pong帧来回应以前的pong帧,那么终端可能选择用Pong帧来回复最近处理的那个Ping帧。

Pong帧能够被主动发送。这会做为一个单项的心跳。预期外的Pong包的响应没有规定。

5.6 数据帧

数据帧(例如非控制帧)的定义是操做码的最高位值为0。当前定义的数据帧操做吗包含0x1(文本)、0x2(二进制)。操做码0x3-0x7是被保留做为非控制帧的操做码。

数据帧会携带应用层/扩展层数据。操做码决定了携带的数据解析方式:

文本

“负载字段”是用UTF-8编码的文本数据。注意特殊的文本帧可能包含部分UTF-8序列;然而,整个消息必须是有效的UTF-8编码数据。从新组合消息后无效的UTF-8编码数据处理见8.1节。

二进制

“负载字段”是任意的二进制数据,二进制数据的解析仅仅依靠应用层。

5.7 示例

  • 一个单帧未添加掩码的文本消息
    0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f (内容为"Hello")
  • 一个单帧添加掩码的文本消息
    0x81 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58 (内容为Hello")
  • 一个分片的未添加掩码的文本消息
    0x01 0x03 0x48 0x65 0x6c (内容为"Hel")
    0x80 0x02 0x6c 0x6f (内容为”lo")
  • 未添加掩码的Ping请求和添加掩码的Ping响应(译者注:即Pong)
    0x89 0x05 0x48 0x65 0x6c 0x6c 0x6f (包含内容为”Hello", 可是文本内容是任意的)
    0x8a 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58 (包含内容为”Hello", 匹配ping的内容)
  • 256字节的二进制数据放入一个未添加掩码数据帧
    0x82 0x7E 0x0100 [256 bytes of binary data\]
  • 64KB二进制数据在一个非掩码帧中
    0x82 0x7F 0x0000000000010000 [65536 bytes of binary data\]

5.8 扩展性

这个协议的设计初衷是容许扩展的,能够在基础协议上增长能力。终端的链接必须在握手的过程当中协商使用的全部扩展。在规范中提供了从0x3-0x7和0xB-0xF的操做码,在数据帧Header中的“扩展数据”字段、frame-rsv一、frame-rsv二、frame-rsv3字段均可以用于扩展。扩展的协商讨论将在之后的9.1节中详细讨论。下面是一些符合预期的扩展用法。下面的列表不完整,也不是规范中内容。

  • “扩展数据”能够放置在“负载数据“中的应用数据”以前的位置。
  • 保留的字段能够在每一帧须要时被使用。
  • 保留的操做码的值能够被定义。
  • 若是须要更多的操做码,那么保留的操做码字段能够被定义。
  • 保留的字段或者“扩展”操做码能够在“负载数据”之中的分配额外的位置来定义,这样能够定义更大的操做码或者更多的每一帧的字段。
相关文章
相关标签/搜索