欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

websocket协议详解(2):websocket协议规范

程序员文章站 2022-05-18 16:34:20
...

握手

首先由客户端发起连接请求,服务端验证客户端握手数据并返回服务器握手数据,客户端验证服务端握手数据,双方完成握手。

客户端:请求协议升级

首先,客户端发起协议升级请求。(采用标准 HTTP 报文格式,且只支持GET方法。)

GET / HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: [LocalHost:80]
Origin: [LocalIP]
Pragma: no -cache
cache -Control: no -cache
Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw==
Sec-WebSocket-Version: 13
Sec -WebSocket - Extensions: x -webkit - deflate - Frame;permessage-deflate; client_max_window_bits
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.2171.99 Safari/537.36

各参数含义:

  • Connection: Upgrade:表示要升级协议
  • Upgrade: websocket:表示要升级到 websocket 协议。
  • Sec-WebSocket-Version: 13:表示 websocket 的版本。
  • Sec-WebSocket-Key:阻止无效websocket协议连接。

需要指出Sec-WebSocket-Key的生成算法,伪代码:base64编码(16位随机字符)

其它部分没有什么好介绍的,跟http头信息一样,可以查看http相关资料。

服务端:响应协议升级

HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU=

返回状态码101表示协议升级成功(从http升级到websocket)。此后,所有的数据都按照websocket协议进行。如果返回其他状态码代表请求失败,因此双方要关闭连接。具体状态码含义可以查看http相关资料。

Sec-WebSocket-Accept用于客户端验证服务器的合法性,如果验证数据不一致,客户端就要关闭连接。伪代码:base64编码(sha1加密( Sec-WebSocket-Key +"258EAFA5-E914-47DA-95CA-C5AB0DC85B11")  ),其中258EAFA5-E914-47DA-95CA-C5AB0DC85B11是固定的,代表websocket全局唯一标识符。 

至此,客户端与服务端已经完成协议升级。

数据帧

websocket用帧(Frame)来表示数据。一个数据帧可以是一个bit大小,也可以N个bit大小,也就是说数据帧大小不是固定的,不同数据帧类型其大小也可能是不一样的。但是每个数据帧类型都有固定的大小。

每个数据帧代表的含义是不同的,它们按照各自规定的大小储存数据,并按照固定的顺序排列。

如下图,FIN就是一个数据帧,它的大小只有1bit,并且它的位置排在所有数据帧的第一位。而opcode也是一个数据帧,它的大小是4个bit,它排在第5位。它们的大小和顺序是websocket规定好的,不能随意改变。

下面这幅图很好的说明了WebSocket 数据帧的格式,第一行代表数据大小,每一小格代表1bit。数据帧按照图中从左到右,从上到下的顺序排列。

websocket协议详解(2):websocket协议规范

每一条websocket数据,都是按照上图所示的规则进行排列的。在本文中,我们把一条数据叫做消息。下面我们结合上图,详细的讲解各帧的含义,如果有不清楚的地方,可参考协议规范。

FIN:1 bit

如果是1,表示这是消息的最后一个分片(fragment),如果是0,表示不是消息的最后一个分片。分片我们可以理解为一条消息的一个部分,只要你愿意,可以随意将一条消息分为N个分片。比如“这是一条消息”是一条消息,我们可以把它分为“这”、“是”、“一”、“条”、“消”、“息”,共6个分片。也可以划分为“这是一条”、“消息”两个分片。但是注意,websocket协议中没有分片顺序标识符,为了保证接收端能接收到正确的消息,分片要按严格按照顺序发送。

FIN用来判断消息是否接收完,如果是0,表示是一条消息的接收完毕。

RSV1, RSV2, RSV3:每个 1 bit

必须是0,除非一个扩展协商为非0值定义含义。如果收到一个非0值且没有协商的扩展定义这个非0值的含义,接收端就必须断开连接。

一般情况下,这三个帧全部定义为0就完事了。因为非0的情况下,对方会告诉你这些非0的含义,比如RSV1=1,代表对面很蛋疼的含义。当然如果你也要蛋疼自定义一些非0值,当我啥也没说。

Opcode: 4 bits

定义了“负载数据”的解释。如果收到一个未知的操作码,接收端点必须断开连接。

 

 

Opcode值含义(16进制)
含义
0 继续帧,表示消息分片模式
1 文本帧,表示文本格式传输
2 二进制帧,表示二进制格式传输
3-7 保留(目前还未定义的意思),用于定义未来的非控制帧
8 关闭帧,表示关闭连接
9 Ping帧,收到一般要回复对方一个pong
A Pong帧,收到一般不理他就完事了。
B-F 保留,用于定义未来的控制帧


负载数据,可以理解为我们要传送的文字、视频、图片等数据。比如上面说的“这是一条消息”就是负载数据,对于分片模式,负载数据就是一个分片,比如“这”。

关闭帧:websocket规定,只要收到关闭帧,都必须立即关闭连接。

客户端和服务端建立连接之后,如果双方长时间没有数据往来,可能被消息中间件断开连接。此时需要ping,pong主要用来实现心跳机制

  • 发送方 ->接收方:ping
  • 接收方 ->发送方:pong

着重说一下0,继续帧。结合FIN帧,我们可以知道当前消息是否分片,是否结束。

一条消息发送:FIN=1且Opcode<>0

分片消息发送:

  1. 第一个片段:FIN=0,Opcode=1或Opcode=2
  2. 中间的片段:FIN=0,Opcode=0
  3. 最后一个片段:FIN=1,Opcode=0

 

Mask: 1 bit

如果0,不需掩码处理。(不掩码处理也就无需掩码键,所以消息中不包含masking-key帧)

如果1,需要掩码处理。(掩码处理必须要有掩码键,所以masking-key帧不能为0)

根据websocket定义:

客户端发送数据需要进行掩码处理,接收数据无需反掩码操作

服务端发送数据无需进行掩码处理,接收数据需要反掩码操作

掩码与反掩码采用一样的算法,伪代码为:

j =i mod 4  

返回数据(i)=原始数据 (i) xor masking-key(j)

i分别代表返回数据和原始数据的第i个字节(byte),j代表masking-key(掩码键)的第j个字节。

如何得到掩码键,我们在后面masking-key介绍里说。

 

Payload length: 7 bits, 7 + 16 bits或者 7 + 64 bits

“负载数据”的长度,单位是字节(byte).

如果 0-125,这是负载长度。

如果 126,之后的2个字节(16 位)表示负载数据长度。

如果 127,之后的8个字节(64位)表示负载数据长度。而且最高有效位必须是0。

要确定负载数据长度,首先先判断第一个字节的值,如果>0且<125,那么这个值就是负载数据的长度。如果=126,那么就取这个字节后面的两个字节作为负载数据的长度。如果=127,就取后面8个字节表示数据的长度。

需要注意多字节长度数量以网络字节顺序来表示,也就是大端模式。以第一个字节=126为例,根据上面介绍,我们知道要取后面两个字节作为负载数据长度,假设两个字节的值分别为A0,01(16进制)。那么负载数据就是A001。

 

Masking-key: 0 or 32 bits

指掩码键,客户端发送到服务器的所有帧必须通过一个包含在帧中的 32 位值来掩码。

如果 mask 位设置为 1,则该字段存在。

如果 mask 位设置为 0,则该字段缺失。

详细信息见上面介绍的Mask帧介绍

mask的生成算法也很简单,就是随机4个字节的任意字符组成。

Payload data: (x+y) bytes

指负载数据

“负载数据”=“扩展数据”+“应用数据”。

以上是负载数据的权威解释,前面说负载数据可以理解为文字、图片、视频的解释不确切,这样解释只是为了让读者更好的进入状态,通过这里我们知道,它只是代表无扩展数据时候的负载数据。

 

Extension data: x bytes

指“扩展数据”

通常是0字节,除非已经协商了一个扩展。任何扩展必须指定“扩展数据” 的长度,或长度是如何计算的,以及扩展如何使用,必须在打开阶段握手期间协商。

如果存在,“扩展数据”包含在总负载数据长度中。

其实就是自定义一个协议,如果有扩展数据,扩展数据就加在应用数据前面,并且要协商好扩展数据长度如何计算。

 

Application data: y bytes

指“应用数据”,“扩展数据”之后帧的剩余部分。

“应用数据”的长度 =负载长度 - “扩展数据”长度。

应用数据长度也可以为0字节。

状态码

当收到一个关闭帧的时候,可能附带关闭的状态码,含义如下表

状态码 名称 描述
0–999   保留段, 未使用.
1000 CLOSE_NORMAL 正常关闭; 无论为何目的而创建, 该链接都已成功完成任务.
1001 CLOSE_GOING_AWAY 终端离开, 可能因为服务端错误, 也可能因为浏览器正从打开连接的页面跳转离开.
1002 CLOSE_PROTOCOL_ERROR 由于协议错误而中断连接.
1003 CLOSE_UNSUPPORTED 由于接收到不允许的数据类型而断开连接 (如仅接收文本数据的终端接收到了二进制数据).
1004   保留. 其意义可能会在未来定义.
1005 CLOSE_NO_STATUS 保留.  表示没有收到预期的状态码.
1006 CLOSE_ABNORMAL 保留. 用于期望收到状态码时连接非正常关闭 (也就是说, 没有发送关闭帧).
1007 Unsupported Data 由于收到了格式不符的数据而断开连接 (如文本消息中包含了非 UTF-8 数据).
1008 Policy Violation 由于收到不符合约定的数据而断开连接. 这是一个通用状态码, 用于不适合使用 1003 和 1009 状态码的场景.
1009 CLOSE_TOO_LARGE 由于收到过大的数据帧而断开连接.
1010 Missing Extension 客户端期望服务器商定一个或多个拓展, 但服务器没有处理, 因此客户端断开连接.
1011 Internal Error 客户端由于遇到没有预料的情况阻止其完成请求, 因此服务端断开连接.
1012 Service Restart 服务器由于重启而断开连接.
1013 Try Again Later 服务器由于临时原因断开连接, 如服务器过载因此断开一部分客户端连接.
1014   由 WebSocket 标准保留以便未来使用.
1015 TLS Handshake 保留. 表示连接由于无法完成 TLS 握手而关闭 (例如无法验证服务器证书).
1016–1999   由 WebSocket 标准保留以便未来使用.
2000–2999   由 WebSocket 拓展保留使用.
3000–3999   可以由库或框架使用.不应由应用使用. 可以在 IANA 注册, 先到先得.
4000–4999   可以由应用使用.