websocket协议详解(2):websocket协议规范
握手
首先由客户端发起连接请求,服务端验证客户端握手数据并返回服务器握手数据,客户端验证服务端握手数据,双方完成握手。
客户端:请求协议升级
首先,客户端发起协议升级请求。(采用标准 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数据,都是按照上图所示的规则进行排列的。在本文中,我们把一条数据叫做消息。下面我们结合上图,详细的讲解各帧的含义,如果有不清楚的地方,可参考协议规范。
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
定义了“负载数据”的解释。如果收到一个未知的操作码,接收端点必须断开连接。
值 | 含义 |
0 | 继续帧,表示消息分片模式 |
1 | 文本帧,表示文本格式传输 |
2 | 二进制帧,表示二进制格式传输 |
3-7 | 保留(目前还未定义的意思),用于定义未来的非控制帧 |
8 | 关闭帧,表示关闭连接 |
9 | Ping帧,收到一般要回复对方一个pong |
A | Pong帧,收到一般不理他就完事了。 |
B-F | 保留,用于定义未来的控制帧 |
负载数据,可以理解为我们要传送的文字、视频、图片等数据。比如上面说的“这是一条消息”就是负载数据,对于分片模式,负载数据就是一个分片,比如“这”。
关闭帧:websocket规定,只要收到关闭帧,都必须立即关闭连接。
客户端和服务端建立连接之后,如果双方长时间没有数据往来,可能被消息中间件断开连接。此时需要ping,pong主要用来实现心跳机制
- 发送方 ->接收方:ping
- 接收方 ->发送方:pong
着重说一下0,继续帧。结合FIN帧,我们可以知道当前消息是否分片,是否结束。
一条消息发送:FIN=1且Opcode<>0
分片消息发送:
- 第一个片段:FIN=0,Opcode=1或Opcode=2
- 中间的片段:FIN=0,Opcode=0
- 最后一个片段: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 | 可以由应用使用. |
上一篇: 运算符重载
推荐阅读
-
HTML5实现WebSocket协议原理浅析
-
C#实现WebSocket协议客户端和服务器websocket sharp组件实例解析
-
Websocket协议详解及简单实例代码
-
Python实现同时兼容老版和新版Socket协议的一个简单WebSocket服务器
-
PHP 实现 WebSocket 协议原理与应用详解
-
Spring Boot实现STOMP协议的WebSocket的方法步骤
-
C#实现WebSocket协议客户端和服务器websocket sharp组件实例解析
-
Yii2结合Workerman的websocket示例详解
-
五分钟学会HTML5的WebSocket协议
-
基于websocket协议的即时通讯webapp(摘自本人毕业论文)