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

概述

本文为WebSocket协定的第五章,本文翻译的重要内容为WebSocket传输的数据相干内容。

有兴致相识该文档之前几张内容的同砚可以见:

数据帧(协定正文)

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衔接。

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包的相应没有划定。

数据帧

数据帧(比方非掌握帧)的定义是操纵码的最高位值为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\]

扩大性

这个协定的设想初志是许可扩大的,可以在基本协定上增添才能。终端的衔接必需在握手的过程当中协商运用的一切扩大。在范例中供应了从0x3-0x7和0xB-0xF的操纵码,在数据帧Header中的“扩大数据”字段、frame-rsv1、frame-rsv2、frame-rsv3字段都可以用于扩大。扩大的协商议论将在今后的9.1节中细致议论。下面是一些相符预期的扩大用法。下面的列表不完全,也不是范例中内容。

  • “扩大数据”可以安排在“负载数据“中的运用数据”之前的位置。
  • 保存的字段可以在每一帧须要时被运用。
  • 保存的操纵码的值可以被定义。
  • 假如须要更多的操纵码,那末保存的操纵码字段可以被定义。
  • 保存的字段或许“扩大”操纵码可以在“负载数据”当中的分派分外的位置来定义,如许可以定义更大的操纵码或许更多的每一帧的字段。
    原文作者:hjava
    原文地址: https://segmentfault.com/a/1190000017792977
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞