This will delete the page "1. 引言"
. Please be certain.
1.1. 背景
本节为非规范性内容。
历史上,创建需要客户端与服务器之间双向通信的Web应用程序(例如,即时通讯和游戏应用程序)需要滥用HTTP来轮询服务器以获取更新,同时将上行通知作为独立的HTTP调用发送[RFC6202]。
这导致了各种问题:
o 服务器被迫为每个客户端使用多个不同的底层TCP连接:一个用于向客户端发送信息,每个传入消息一个新的连接。
o 有线协议开销很大,每个客户端到服务器的消息都有一个HTTP头部。
o 客户端脚本被迫维护从出站连接到入站连接的映射,以跟踪回复。
一个更简单的解决方案是使用单个TCP连接来处理双向流量。这就是WebSocket协议提供的。结合WebSocket API [WSAPI],它为从网页到远程服务器的双向通信提供了一种替代HTTP轮询的方法。
同样的技术可以用于各种Web应用程序:游戏、股票行情、支持同时编辑的多用户应用程序、实时暴露服务器端服务的用户界面等。
WebSocket协议旨在取代使用HTTP作为传输层的现有双向通信技术,以便从现有基础设施(代理、过滤、认证)中受益。这些技术是在效率和可靠性之间进行权衡的实现,因为HTTP最初并不是为双向通信设计的(有关进一步讨论,请参见[RFC6202])。WebSocket协议试图在现有HTTP基础设施的背景下解决现有双向HTTP技术的目标;因此,它被设计为在HTTP端口80和443上工作,以及支持HTTP代理和中介,即使这意味着当前环境特有的一些复杂性。然而,设计并不限制WebSocket使用HTTP,未来的实现可以在专用端口上使用更简单的握手,而无需重新发明整个协议。这最后一点很重要,因为交互式消息的流量模式与标准HTTP流量不完全匹配,可能会对某些组件产生不寻常的负载。
1.2. 协议概述
本节为非规范性内容。
该协议有两个部分:握手和数据传输。
客户端的握手如下所示:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: 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
客户端的起始行遵循Request-Line格式。服务器的起始行遵循Status-Line格式。Request-Line和Status-Line的生成规则在[RFC2616]中定义。
在两种情况下,起始行之后是一组无序的头字段。这些头字段的含义在本文档的第4节中指定。也可能存在额外的头字段,如cookies [RFC6265]。头的格式和解析如[RFC2616]中所定义。
一旦客户端和服务器都发送了他们的握手,并且如果握手成功,那么数据传输部分开始。这是一个双向通信通道,每一方都可以独立于另一方随意发送数据。
在成功握手之后,客户端和服务器以本规范中称为“消息”的概念单位来回传输数据。在传输层上,一个消息由一个或多个帧组成。WebSocket消息不一定对应于特定的网络层帧,因为一个分片的消息可能被中介合并或分割。
一个帧有一个相关联的类型。属于同一消息的每个帧包含相同类型的数据。广义上讲,有文本数据类型(解释为UTF-8 [RFC3629]文本)、二进制数据类型(其解释留给应用程序)和控制帧类型(不旨在为应用程序携带数据,而是用于协议级别的信号,如信号连接应该关闭)。本协议版本定义了六种帧类型,并为未来使用保留了十种。
1.3. 开始握手
本节为非规范性内容。
开始握手旨在与基于HTTP的服务器端软件和中介兼容,以便单个端口既可以被访问该服务器的HTTP客户端使用,也可以被访问该服务器的WebSocket客户端使用。为此,WebSocket客户端的握手是一个HTTP升级请求:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
按照[RFC2616]的规定,握手中的头字段可以由客户端以任何顺序发送,因此接收不同头字段的顺序并不重要。
GET方法的“Request-URI”[RFC2616]用于识别WebSocket连接的端点,既允许多个域名从一个IP地址提供服务,也允许单个服务器提供多个WebSocket端点服务。
客户端在其握手的|Host|头字段中包含主机名,按照[RFC2616],这样客户端和服务器都可以验证它们对正在使用的主机的一致性。
额外的头字段用于在WebSocket协议中选择选项。此版本中可用的典型选项包括子协议选择器(|Sec-WebSocket-Protocol|)、客户端支持的扩展列表(|Sec-WebSocket-Extensions|)、|Origin|头字段等。|Sec-WebSocket-Protocol|请求头字段可用于指示客户端接受哪些子协议(在WebSocket协议之上分层的应用级协议)。服务器选择一个或没有可接受的协议,并在其握手中回显该值,以表明它已选择了该协议。
Sec-WebSocket-Protocol: chat
|Origin|头字段[RFC6454]用于防止未经授权的跨源使用WebSocket服务器,通过在Web浏览器中使用WebSocket API的脚本。服务器被告知生成WebSocket连接请求的脚本来源。如果服务器不希望接受来自此来源的连接,它可以选择通过发送适当的HTTP错误代码来拒绝连接。这个头字段由浏览器客户端发送;对于非浏览器客户端,如果在这些客户端的上下文中有意义,也可以发送这个头字段。
最后,服务器必须向客户端证明它已收到客户端的WebSocket握手,以便服务器不接受非WebSocket连接。这可以防止攻击者通过使用XMLHttpRequest [XMLHttpRequest]或表单提交发送精心制作的数据包来欺骗WebSocket服务器。
为了证明已收到握手,服务器必须取两条信息并将它们组合以形成响应。第一条信息来自客户端握手中的|Sec-WebSocket-Key|头字段:
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
对于这个头字段,服务器必须取该值(如头字段中所示,例如,base64编码[RFC4648]版本,减去任何前导和尾随空白)并与字符串形式的全局唯一标识符(GUID, [RFC4122])"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"连接,这个GUID不太可能被不理解WebSocket协议的网络端点使用。然后,服务器的握手中返回这个连接的SHA-1哈希(160位)[FIPS.180-3],base64编码(见[RFC4648]第4节)。
具体来说,如果像上面的例子中,|Sec-WebSocket-Key|头字段的值为"dGhlIHNhbXBsZSBub25jZQ==",服务器将连接字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"形成字符串"dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11"。然后服务器将对此进行SHA-1哈希处理,得到值0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea。然后将此值进行base64编码(见[RFC4648]第4节),得到值"s3pPLMBiTxaQ9kYGzzhZRbK+xOo="。然后这个值将在|Sec-WebSocket-Accept|头字段中回显。
服务器的握手比客户端的握手简单得多。第一行是一个HTTP状态行,状态码为101:
HTTP/1.1 101 Switching Protocols
任何非101的状态码都表示WebSocket握手未完成,HTTP的语义仍然适用。头字段跟随状态码。
|Connection|和|Upgrade|头字段完成了HTTP升级。|Sec-WebSocket-Accept|头字段表明服务器是否愿意接受连接。如果存在,这个头字段必须包含客户端在|Sec-WebSocket-Key|中发送的随机数的哈希值以及预定义的GUID。任何其他值都不应被服务器解释为接受连接。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
WebSocket客户端会检查这些字段以用于脚本页面。如果|Sec-WebSocket-Accept|值不匹配预期值,如果头字段缺失,或者如果HTTP状态码不是101,连接将不会建立,WebSocket帧也不会被发送。
也可以包括选项字段。在这个协议版本中,主要的选项字段是|Sec-WebSocket-Protocol|,它指示服务器选择的子协议。WebSocket客户端验证服务器是否包含了WebSocket客户端握手中指定的一个值。会说多种子协议的服务器必须确保它根据客户端的握手选择一个,并在其握手中指定它。
Sec-WebSocket-Protocol: chat
服务器还可以设置与cookie相关的选项字段来设置cookie,如[RFC6265]中所述。
1.4. 关闭握手
本节为非规范性内容。
关闭握手比开始握手简单得多。
任一对等方都可以发送一个包含指定控制序列的控制帧来开始关闭握手(详见第5.5.1节)。收到这样的帧后,另一对等方响应发送一个关闭帧,如果它还没有发送的话。收到_那个_控制帧后,第一个对等方随后关闭连接,确信不会有更多数据传入。
发送指示应关闭连接的控制帧后,一个对等方不会发送任何进一步的数据;收到指示应关闭连接的控制帧后,一个对等方将丢弃收到的任何进一步数据。
两个对等方同时启动这个握手是安全的。
关闭握手旨在补充TCP关闭握手(FIN/ACK),因为TCP关闭握手在存在拦截代理和其他中介的情况下,尤其是不总是可靠的端到端。
通过发送关闭帧并等待响应的关闭帧,避免了可能导致数据不必要丢失的某些情况。例如,在某些平台上,如果一个套接字在接收队列中有数据时被关闭,将发送一个RST数据包,这将导致接收到RST的一方的recv()失败,即使有数据等待被读取。
1.5. 设计哲学
本节为非规范性内容。
WebSocket协议的设计原则是应该有最小的帧结构(唯一存在的帧结构是使协议基于帧而不是基于流,并支持Unicode文本和二进制帧之间的区分)。预期元数据将由应用层在WebSocket之上分层,就像元数据通过应用层在TCP之上分层一样(例如,HTTP)。
从概念上讲,WebSocket实际上只是在TCP之上增加了以下内容的一层:
o 为浏览器添加了基于Web源的安全模型
o 添加了一个地址和协议命名机制,以支持一个端口上的多个服务和一个IP地址上的多个主机名
o 在TCP之上分层了一个帧结构,回到TCP建立在上的IP数据包机制,但没有长度限制
o 包括了一个额外的在带内的关闭握手,旨在在存在代理和其他中介的情况下工作
除此之外,WebSocket没有添加任何东西。基本上,它旨在尽可能接近在Web的约束条件下直接向脚本暴露原始TCP。它还以这样一种方式设计,即其服务器可以与HTTP服务器共享端口,通过使其握手成为有效的HTTP升级请求。从概念上讲,人们可以使用其他协议来建立客户端-服务器消息传递,但WebSocket的意图是提供一个相对简单的协议,它可以与HTTP和部署的HTTP基础设施(如代理)共存,并且鉴于安全考虑,它尽可能接近TCP,同时针对简化使用和保持简单事物简单(如添加消息语义)进行了有针对性的添加。
该协议旨在可扩展;未来版本可能会引入额外的概念,如多路复用。
1.6. 安全模型
本节为非规范性内容。
WebSocket协议使用Web浏览器采用的源模型来限制哪些网页可以在通过网页使用WebSocket协议时联系WebSocket服务器。自然地,当WebSocket协议被专用客户端直接使用时(即,不通过Web浏览器从网页中),源模型并不适用,因为客户端可以提供任意的源字符串。
该协议旨在无法与SMTP [RFC5321]和HTTP等预先存在的协议服务器建立连接,同时允许HTTP服务器选择支持此协议(如果需要)。这是通过进行严格和详尽的握手以及限制在握手完成之前可以插入到连接中的数据量(从而限制服务器可以受到的影响)来实现的。
同样,当向WebSocket服务器发送其他协议的数据时,特别是HTTP,也旨在无法建立连接,例如,如果将HTML“表单”提交给WebSocket服务器时可能发生的情况。这主要是通过要求服务器证明它已读取握手来实现的,服务器只有在握手包含适当部分时才能做到这一点,而这些部分只能由WebSocket客户端发送。特别是,在编写本规范时,以|Sec-|开头的字段不能仅通过使用HTML和JavaScript API(如XMLHttpRequest [XMLHttpRequest])的Web浏览器的攻击者设置。
1.7. 与TCP和HTTP的关系
本节为非规范性内容。
WebSocket协议是一个独立的基于TCP的协议。它与HTTP的唯一关系是其握手被HTTP服务器解释为升级请求。
默认情况下,WebSocket协议使用端口80进行常规WebSocket连接,以及端口443通过传输层安全性(TLS)[RFC2818]隧道化的WebSocket连接。
1.8. 建立连接
本节为非规范性内容。
当要与HTTP服务器共享端口的情况下建立连接时(这种情况很可能发生在端口80和443的流量中),连接对HTTP服务器来说将出现为带有升级提议的常规GET请求。在相对简单的设置中,只有一个IP地址和单个服务器处理单个主机名的所有流量,这可能为基于WebSocket协议的系统部署提供了一种实用的方式。在更复杂的设置中(例如,带有负载均衡器和多个服务器),为WebSocket连接单独设置一组主机,与HTTP服务器分开,可能更容易管理。在编写本规范时,应注意到端口80和443上的连接成功率有显著不同,端口443上的连接更有可能成功,尽管这种情况可能随时间而变化。
1.9. 使用WebSocket协议的子协议
本节为非规范性内容。
客户端可以通过在其握手中包含|Sec-WebSocket-Protocol|字段来请求服务器使用特定的子协议。如果指定了该字段,服务器需要在其响应中包含相同的字段和所选子协议值之一,以建立连接。
这些子协议名称应按照第11.5节注册。为避免潜在的冲突,建议使用包含子协议发起者域名的ASCII版本的名称。例如,如果示例公司创建了一个由Web上许多服务器实现的聊天子协议,他们可以将其命名为"chat.example.com"。如果示例组织将其竞争对手的子协议命名为"chat.example.org",那么这两个子协议可以同时由服务器实现,服务器可以根据客户端发送的值动态选择使用哪个子协议。
子协议可以通过更改子协议名称以向后不兼容的方式进行版本控制,例如,从"bookings.example.net"更改为"v2.bookings.example.net"。这些子协议将被WebSocket客户端视为完全不同的协议。向后兼容的版本控制可以通过重用相同的子协议字符串来实现,但需要仔细设计实际的子协议以支持这种可扩展性。
This will delete the page "1. 引言"
. Please be certain.