1.写在前面
前面的博客已经介绍完了HTTP1.0/1.1相关的协议了,这个时候我们继续来了解对应的WebSocket协议
2.Websocket解决什么问题?
如何及时获得更新?从轮询到通知
Chrome请求列表:分析WebSocket
- 过滤器
- 按类型:WS
- 属性过滤:is:running
- 表格列
- Data:消息负载。如果消息为纯文本,则在此处显示。对于二进制操作码,此列将显示操作码的名称和代码。支持以下操作码:Continuation Frame、Binary Frame、Connection Close Frame、Ping Frame 和Pong Frame
- Length:消息负载的长度(以字节为单位)
- Time:收到或发送消息的时间
- 消息颜色
- 发送至服务器的文本消息为浅绿色
- 接收到的文本消息为白色
- WebSocket操作码为浅黄色
- 错误为浅红色
支持双向通讯的WebSocket
-
Rfc6455(2011.12)
-
双向细通讯的优劣?
-
如何管理会话?
-
如何维持长连接?
-
兼容HTTP协议 端口复用
-
支持扩展 如permessage-deflate扩展
3.Websocket的约束
WebSocket的成本
-
实时性与可伸缩性
- 牺牲了简单性
-
网络效率与无状态:请求2基于请求1
- 牺牲了简单性与可见性
长连接的心跳保持
- HTTP长连接只能基于简单的超时(常见为65秒)
- WebSocket连接基于ping/pong心跳机制维持。
兼容HTTP协议
- 默认使用80或者443端口
- 协议升级
- 代理服务器可以简单支持
设计哲学:在Web约束下暴露TCP给上层
- 元数据去哪了?
- 对比:HTTP协议头部会存放元数据
- 由WebSocket上传输的应用层存放元数据
- 基于帧:不是基于流(HTTP、TCP)
- 每一帧要么承载字符数据,要么承载二进制数据
- 基于浏览器的同源策略模型(非浏览器无效)
- 可以使用Access-Control-Allow-Origin等头部
- 基于URI、子协议支持同主机同端口上的多个服务
4.WebSocket协议格式
帧格式示意图
-
红色是2字节必然存在的帧首部
数据帧格式:RSV保留值
-
RSV1/RSV2/RSV3:默认为0,仅当使用extension扩展时,由扩展决定其值
数据帧格式:帧类型
-
持续帧
- 0:继续前一帧
-
非控制帧
- 1:文本帧(UTF8)
- 2:二进制帧
- 3-7:为非控制帧保留
-
控制帧
- 8:关闭帧
- 9:心跳帧ping
- A:心跳帧pong
- B-F:为控制帧保留
ABNF描述的帧格式
5.如何从HTTP升级到WebSocket
URI格式
- Ws-URI = “ws:” “//” host [“:” port] path [“?” query]
- 默认的port端口80
- Wss-URI = “wss:” “//” host [“:” port] path [“?” query]
- 默认port端口443
- 客户端提供信息
- host与port:主机名和端口
- Shema:是否基于SSL
- 访问资源:URI
- 握手随机数:Sec-WebSocket-Key
- 选择子协议:Sec-WebSocket-Protocol
- 扩展协议:Sec-WebSocket-Extensions
- CORS跨域:Origin
建立握手
如何证明握手被服务器接受?预防意外
- 请求中的Sec-WebSocket-Key随机数
- 例如Sec-WebSocket-Key: A1EEou7Nnq6+BBzoAzqWlg==
- 响应中的Sec-WebSocket-Accept证明值
- GUID(RFC4122):258EAFA5-E914-47DA-95CA-C5AB0DC85B11
- 值构造规则:BASE64(SHA1(Sec-WebSocket-KeyGUID))
- 拼接值:A1EEou7Nnq6+BBZoAZqWlg==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
- SHA1 值:713f15ece2218612fcadb1598281a35380d1790f
- BASE 64 值:cT8V7OIhhhL8rbFZgoGjU4DReQ8=
- 最终头部:Sec-WebSocket-Accept: cT8V7OIhhhL8rbFZgoGjU4DReQ8=
6.传递消息时的编码格式
消息与数据帧
- Message消息
- 1条消息由1个或者多个帧组成,这些数据帧属于同一类型
- 代理服务器可能合并、拆分消息的数据帧
- Frame数据帧
- 持续帧
- 文本帧、二进制帧
非控制帧的消息分片:有序
数据帧格式:消息内容的长度
-
消息内容长度组成
- 应用消息长度
- 扩展数据长度
-
<=125字节
- 仅使用Payload len
-
126至2^16-1
- Payload len 值为126
- Extended payload length16为表示长度
-
216至264-1
- Payload len值为127
- Extended payload length共8字节64位表示长度
发送消息
- 确保WebSocket会话处于OPEN状态
- 以帧来承载消息,一条消息可以拆分多个数据帧
- 客户端发送的帧必须基于掩码编码
- 一旦发送或者接收到关闭帧,连接处于CLOSING状态
- 一旦发送了关闭帧,且接收到关闭帧,连接处于CLOSED状态
- TCP连接关闭后,WebSocket连接才完全被关闭
7.掩码及其所针对的代理污染攻击
针对代理服务器的缓存污染攻击
frame-masking-key掩码
-
客户端消息:MASK为1(包括控制帧)传递32位无法预测的、随机的Masking-key
-
服务器端消息:MASK为0
掩码如何防止缓存污染攻击?
- 目的:防止恶意页面上的代码,可以经由浏览器构造出合法的GET请求,使得代理服务器可以识别出请求并缓存响应。
- 强制浏览器执行以下方法:
- 生成随机的32位frame-masking-key,不能让JS代码猜出(否则可以反向构造)
- 对传输的包体按照frame-masking-key执行可对称解密的XOR异或操作,使代理服务器不识别
- 消息编码算法:
- j = i MOD 4
- Transformed-octet-i = original-octet-i XOR masking-key-octet-j
8.如何保持会话心跳
心跳帧
- 心跳帧可以插在数据帧中传输
- ping帧
- opcode=9
- 可以含有数据
- pong帧
- opcode=A
- 必须与ping帧数据相同
- ping帧
9.如何关闭WebSocket会话
关闭会话的方式
-
控制帧中关闭帧:在TCP连接之上的双向关闭
- 发送关闭帧后,不能再发送任何数据
- 接收到关闭帧后,不再接收任何到达的数据
-
TCP连接意外中断
关闭帧格式
-
opcode=8
-
可以含有数据,但仅用于解释关闭会话的原因
-
前2字节为无符号整型
-
遵循mask掩码规则
-
关闭帧的错误码
错误码 | 含义 |
---|---|
1000 | 正常关闭 |
1001 | 表示浏览器页面跳转或者服务器将要关机 |
1002 | 发现协议错误 |
1003 | 接收到不能处理的数据帧(例如某端不能处理二进制消息) |
1004 | 预留 |
1005 | 预留(不能用在关闭帧里)期望但没有接收到错误码 |
1006 | 预留(不能用在关闭帧里)期望给出非正常关闭的错误码 |
1007 | 消息格式不符合opcode(例如文本帧里消息没有用UTF8编码) |
1008 | 接收到的消息不遵守某些策略(比1003、1009更一般的错误) |
1009 | 消息超出能处理的最大的长度 |
1010 | 客户端明确需要使用扩展,但服务器没有给出扩展的协商信息 |
1011 | 服务器遇到未知条件不能完成请求 |
1015 | 预留(不能用在关闭帧里),表示TLS握手失败 |
10.写在最后
这篇博客对WebSocket协议进行一个简单的介绍,后面我们会继续介绍对应的HTTP2的协议