5. 数据帧
hyr editou esta páxina hai 9 meses
  1. 数据帧

5.1. 概述

在WebSocket协议中,数据通过一系列帧进行传输。为了避免混淆网络中介(如拦截代理)以及出于进一步在第10.3节讨论的安全原因,客户端必须对其发送给服务器的所有帧进行掩码处理(详见第5.3节了解更多细节)。(注意,无论WebSocket协议是否运行在TLS之上,都要进行掩码处理。)服务器在接收到未经掩码处理的帧时,必须关闭连接。在这种情况下,服务器可以发送一个带有1002(协议错误)状态码的关闭帧,如第7.4.1节所定义。服务器不得对其发送给客户端的任何帧进行掩码处理。如果客户端检测到一个被掩码处理的帧,它必须关闭连接。在这种情况下,它可以使用状态码1002(协议错误),如第7.4.1节所定义。(这些规则在未来的规范中可能会放宽。)

基本帧协议定义了一种帧类型,带有操作码、有效载荷长度和为“扩展数据”和“应用数据”指定的位置,这些共同定义了“有效载荷数据”。某些位和操作码保留用于协议的未来扩展。

数据帧可以在开放握手完成后和该端点发送关闭帧(第5.5.1节)之前的任何时间由客户端或服务器传输。

5.2. 基础帧协议

此数据传输部分的线路格式由本节详细给出的ABNF [RFC5234] 描述。(注意,与本文档的其他部分不同,本节中的ABNF操作的是位组。每组位的长度在注释中指示。当在线路上编码时,最高有效位是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位

表示这是消息中的最后一个片段。第一个片段也可能是最后一个片段。

RSV1, RSV2, RSV3: 每个1位

除非协商了定义非零值含义的扩展,否则必须为0。如果接收到非零值,并且没有协商的扩展定义了这样的非零值的含义,接收端点必须_失败WebSocket连接_。

Opcode: 4位

定义了“有效载荷数据”的解释。如果接收到未知的操作码,接收端点必须_失败WebSocket连接_。定义了以下值。

  • %x0 表示继续帧
  • %x1 表示文本帧
  • %x2 表示二进制帧
  • %x3-7 保留用于进一步的非控制帧
  • %x8 表示连接关闭
  • %x9 表示ping
  • %xA 表示pong
  • %xB-F 保留用于进一步的控制帧

Mask: 1位

定义“有效载荷数据”是否被掩码处理。如果设置为1,则掩码键存在于掩码键中,并且用于根据第5.3节解除“有效载荷数据”的掩码。所有从客户端发送到服务器的帧都将此位设置为1。

有效载荷长度:7位,7+16位,或7+64位

“有效载荷数据”的长度,以字节为单位:如果是0-125,那就是有效载荷长度。如果是126,接下来的2字节解释为一个16位无符号整数是有效载荷长度。如果是127,接下来的8字节解释为一个64位无符号整数(最高有效位必须为0)是有效载荷长度。多字节长度量以网络字节顺序表示。注意,在所有情况下,必须使用最少的字节数来编码长度,例如,不能将124字节长的字符串的长度编码为序列126, 0, 124。有效载荷长度是“扩展数据”长度+“应用数据”长度。“扩展数据”的长度可能为零,在这种情况下,有效载荷长度就是“应用数据”的长度。

掩码键:0或4字节

所有从客户端发送到服务器的帧都通过包含在帧内的32位值进行掩码处理。如果掩码位设置为1,则此字段存在;如果掩码位设置为0,则此字段不存在。有关客户端到服务器掩码的更多信息,请参见第5.3节。

有效载荷数据:(x+y)字节

“有效载荷数据”定义为“扩展数据”与“应用数据”连接在一起。

扩展数据:x字节

除非协商了扩展,“扩展数据”为0字节。任何扩展都必须指定“扩展数据”的长度,或者如何计算该长度,以及如何在开放握手期间协商扩展的使用。如果存在,“扩展数据”包含在总有效载荷长度中。

应用数据:y字节

任意的“应用数据”,占据帧中任何“扩展数据”之后的剩余部分。“应用数据”的长度等于有效载荷长度减去“扩展数据”的长度。

基础帧协议由以下ABNF [RFC5234] 正式定义。重要的是要注意,这些数据的表示形式是二进制的,而不是ASCII字符。因此,长度为1位且取值为%x0 / %x1的字段表示为单个位的值为0或1,而不是代表ASCII编码中的字符“0”或“1”的完整字节(八位字节)。长度为4位且值在%x0-F之间的字段再次由4位表示,而不是由ASCII字符或具有这些值的完整字节(八位字节)表示。[RFC5234] 没有指定字符编码:“规则解析为一串终端值,有时称为字符。在ABNF中,字符仅仅是一个非负整数。在某些上下文中,将指定值到字符集(如ASCII)的特定映射(编码)。”这里,指定的编码是二进制编码,其中每个终端值以指定的位数编码,这对每个字段都是不同的。

5.3. 客户端到服务器的掩码处理

一个被掩码处理的帧必须将字段 frame-masked 设置为1,如第5.2节所定义。

掩码键完全包含在帧内,如第5.2节中定义的 frame-masking-key。它用于掩码处理同一节中定义的“有效载荷数据”(frame-payload-data),包括“扩展数据”和“应用数据”。

掩码键是客户端随机选择的32位值。当准备一个被掩码处理的帧时,客户端必须从允许的32位值集合中选择一个新的掩码键。掩码键需要是不可预测的;因此,掩码键必须来自一个强熵源,并且给定帧的掩码键不得使服务器/代理能够简单地预测后续帧的掩码键。掩码键的不可预测性对于防止恶意应用的作者选择出现在线路上的字节至关重要。RFC 4086 [RFC4086] 讨论了对于安全敏感应用来说什么构成了合适的熵源。

掩码处理不影响“有效载荷数据”的长度。要将被掩码处理的数据转换为未掩码处理的数据,或反之亦然,应用以下算法。无论转换的方向如何,都应用相同的算法,例如,掩码处理数据的步骤与解除掩码处理数据的步骤相同。

变换后数据的第i个字节("transformed-octet-i")是原始数据的第i个字节("original-octet-i")与掩码键中索引为i模4的字节("masking-key-octet-j")的异或(XOR)结果:

j                   = i MOD 4
transformed-octet-i = original-octet-i XOR masking-key-octet-j

帧中指示的有效载荷长度(frame-payload-length)不包括掩码键的长度。它是“有效载荷数据”的长度,例如,掩码键后面的字节数。

5.4. 分片

分片的主要目的是允许在开始消息时不知道消息的大小而不必缓冲该消息就能发送消息。如果不能对消息进行分片,那么端点将不得不缓冲整个消息,以便在发送第一个字节之前计算其长度。通过分片,服务器或中介可以选择一个合理大小的缓冲区,并且当缓冲区满时,将一个片段写入网络。

分片的次要用例是多路复用,其中不希望一个逻辑通道上的大消息垄断输出通道,因此多路复用需要能够将消息分割成更小的片段以更好地共享输出通道。(注意,多路复用扩展在本文档中没有描述。)

除非扩展另有规定,否则帧没有语义含义。如果客户端和服务器没有协商扩展,或者协商了一些扩展,但中介理解所有协商的扩展并知道如何在这些扩展存在的情况下合并和/或分割帧,那么中介可能会合并和/或分割帧。这意味着在没有扩展的情况下,发送者和接收者不得依赖于特定帧边界的存在。

分片遵循以下规则:

  • 未分片的消息由一个FIN位设置(第5.2节)且操作码不为0的单个帧组成。
  • 分片的消息由一个FIN位清除且操作码不为0的单个帧开始,后跟零个或多个FIN位清除且操作码设置为0的帧,并以一个FIN位设置且操作码为0的单个帧结束。从概念上讲,分片的消息等同于一个更大的消息,其有效载荷等于按顺序连接片段的有效载荷;然而,在扩展存在的情况下,这可能不成立,因为扩展定义了存在的“扩展数据”的解释。例如,“扩展数据”可能只出现在第一个片段的开头并适用于后续片段,或者每个片段中可能存在仅适用于该特定片段的“扩展数据”。在没有“扩展数据”的情况下,以下示例演示了分片是如何工作的。

示例:对于作为三个片段发送的文本消息,第一个片段将有一个操作码0x1和一个清除的FIN位,第二个片段将有一个操作码0x0和一个清除的FIN位,第三个片段将有一个操作码0x0和一个设置的FIN位。

  • 控制帧(见第5.5节)可以在分片消息的中间插入。控制帧本身不能被分片。
  • 必须按发送者发送的顺序将消息片段交付给接收者。
  • 一个消息的片段不能在另一个消息的片段之间交错,除非协商了可以解释交错的扩展。
  • 端点必须能够在分片消息中间处理控制帧。
  • 发送者可以为非控制消息创建任何大小的片段。
  • 客户端和服务器必须支持接收分片和未分片的消息。
  • 由于控制帧不能被分片,中介不得试图改变控制帧的分片。
  • 如果使用了任何保留位值且中介不知道这些值的含义,中介不得改变消息的分片。
  • 在协商了扩展且中介不了解协商扩展的语义的连接上下文中,中介不得改变任何消息的分片。同样,如果中介没有看到WebSocket握手(并且没有被通知其内容)导致WebSocket连接,中介不得改变此类连接的任何消息的分片。
  • 根据这些规则,所有消息的片段都是由第一个片段的操作码设置的相同类型。由于控制帧不能被分片,所有片段中的类型必须是文本、二进制或其中一个保留操作码。

注意:如果不能插入控制帧,例如,如果在一个大消息后面,ping的延迟会非常长。因此,要求在分片消息中间处理控制帧。

实现注意:在没有任何扩展的情况下,接收者不必缓冲整个帧就能处理它。例如,如果使用流API,可以将帧的一部分交付给应用程序。然而,请注意,这个假设可能不适用于所有未来的WebSocket扩展。

5.5. 控制帧

控制帧通过操作码来识别,其中操作码的最高有效位为1。目前为控制帧定义的操作码包括0x8(关闭)、0x9(Ping)和0xA(Pong)。操作码0xB-0xF保留用于将来定义的进一步控制帧。

控制帧用于通信有关WebSocket的状态。控制帧可以在分片消息的中间插入。

所有控制帧必须有125字节或更少的有效载荷长度,并且不能被分片。

5.5.1. 关闭

关闭帧包含操作码0x8。

关闭帧可能包含一个体(帧的“应用数据”部分),该体指示关闭的原因,例如端点关闭、端点收到过大的帧,或端点收到的帧不符合端点期望的格式。如果有体,体的前两个字节必须是一个2字节无符号整数(网络字节顺序),表示在第7.4节中定义的状态码/code/。在2字节整数之后,体可能包含UTF-8编码的数据/value/reason/,其解释不由本规范定义。这些数据不一定是人类可读的,但可能对调试或传递与打开连接的脚本相关的信息有用。由于数据不保证是人类可读的,客户端不得向最终用户显示它。

从客户端发送到服务器的关闭帧必须按照第5.3节进行掩码处理。

应用在发送关闭帧后不得再发送任何数据帧。

如果端点收到关闭帧而之前没有发送关闭帧,端点必须发送一个关闭帧作为响应。(发送响应关闭帧时,端点通常回显它收到的状态码。)它应该尽快这样做。端点可能会延迟发送关闭帧,直到其当前消息被发送(例如,如果分片消息的大部分已经发送,端点可能会在发送关闭帧之前发送剩余的片段)。然而,不能保证已经发送关闭帧的端点将继续处理数据。

在发送和接收关闭消息后,端点认为WebSocket连接已关闭,并且必须关闭底层的TCP连接。服务器必须立即关闭底层的TCP连接;客户端应该等待服务器关闭连接,但在发送和接收关闭消息后,可以在任何时候关闭连接,例如,如果在合理的时间内没有从服务器收到TCP关闭。

如果客户端和服务器同时发送关闭消息,两个端点都将发送和接收关闭消息,并应该认为WebSocket连接已关闭,并关闭底层的TCP连接。

5.5.2. Ping

Ping帧包含操作码0x9。

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

在收到Ping帧后,端点必须发送一个Pong帧作为响应,除非它已经收到了关闭帧。它应该尽快以Pong帧作为响应。Pong帧在第5.5.3节中讨论。

在连接建立后和关闭之前的任何时间,端点都可以发送Ping帧。

注意:Ping帧可以用作保活信号或用来验证远程端点是否仍然响应。

5.5.3. Pong

Pong帧包含操作码0xA。

第5.5.2节详细说明了适用于Ping和Pong帧的要求。

作为对Ping帧的响应发送的Pong帧必须具有与被回复的Ping帧的消息体中找到的“应用数据”完全相同。

如果端点收到Ping帧,并且尚未对之前的Ping帧发送Pong帧作为响应,端点可以选择仅对最近处理的Ping帧发送Pong帧。

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响应

    • 0x89 0x05 0x48 0x65 0x6c 0x6c 0x6f (包含一个"Hello"的体,但体的内容是任意的)
    • 0x8a 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58 (包含一个与ping的体匹配的"Hello"的体)
  • 单帧未掩码256字节二进制消息

    • 0x82 0x7E 0x0100 [256字节的二进制数据]
  • 单帧未掩码64KiB二进制消息

    • 0x82 0x7F 0x0000000000010000 [65536字节的二进制数据]

5.8. 可扩展性

该协议旨在允许扩展,这将为基本协议增加功能。连接的端点必须在开放握手期间协商任何扩展的使用。本规范为扩展提供了操作码0x3至0x7和0xB至0xF、“扩展数据”字段,以及帧头的frame-rsv1、frame-rsv2和frame-rsv3位。扩展的协商在第9.1节中进一步详细讨论。以下是一些预期的扩展用途。这个列表既不完整也不规定性。

  • “扩展数据”可以放在“应用数据”之前的“有效载荷数据”中。
  • 可以为每帧需求分配保留位。
  • 可以定义保留的操作码值。
  • 如果需要更多的操作码值,可以将保留位分配给操作码字段。
  • 可以定义一个保留位或一个“扩展”操作码,该操作码从“有效载荷数据”中分配额外的位,以定义更大的操作码或更多的每帧位。