WebSocket详解

WebSocket定义

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在这里插入图片描述

其特点

  1. 建立在 TCP 协议之上,服务器端的实现比较容易。
  2. 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种
    HTTP 代理服务器。
  3. 数据格式比较轻量,性能开销小,通信高效。
  4. 可以发送文本,也可以发送二进制数据。
  5. 没有同源限制,客户端可以与任意服务器通信。
  6. 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

ws://example.com:80/some/path

在这里插入图片描述

HTTP缺点:通过反复的轮训去查看资源是否有更新,对网络和资源有很大的消耗
websocket可以反向通知,双向通讯
在这里插入图片描述

如果过滤websocket请求:

  1. status:101 代表websocekt 请求
  2. 属性过滤:is:running
  3. 按类型筛选: WS

表格列:

Data:消息载体。如果消息为存文本,则显示在此处。对于二进制操作码,此列显示操作码的名称和代码。支持一下操作码:Continuation,Frame,Binary Frame,Connection Close Frame,Ping Frame和Pong Frame。
Length:消息负载的长度(以字节为单位)。
Time:收到或发送消息的时间。

消息颜色:

发送至服务器的文本消息为浅绿色。(发送的消息箭头朝上)
接收到的文本消息为白色。(接收的消息箭头朝下)
WebSocket操作码为浅黄色。
错误为浅红色。
在这里插入图片描述
支持双向通讯的WebSocket协议
websocket在rfc6455(2011.12)定义的 ,最大的优点实现了双向通讯,实现了实时推送消息
缺点:可伸缩性差,建立连接后需要扩容,增加连接数的时候方案比较复杂
使用ping pong心跳来做长链接
兼容HTTP协议,端口仍然是80,443
支持扩展 permessage_deflate扩展,通过扩展做压缩

WebScoket的成本:

  1. 实时性与可伸缩性:可以任意的去添加新的组件来提升服务能力
  2. 牺牲了简单性
  3. 网络效率与无状态:请求2基于请求1
  4. 牺牲了简单性和可见性
实时性与可伸缩性

**HTTP可伸缩性较好:**客户端发起一个http请求,向同一个域名下,域名下有个负载均衡(loadblance?),发起第一个请求,就把请求转到了第一个server上去,发起第二个请求可能就转到了第二个server上去;这样实现了很好的可伸缩性;
在这里插入图片描述

**websocket牺牲简单性可实现很好的伸缩性:**对于websocket比较麻烦,因为客户端和负载均衡之间只建立了一个请求连接,请求1,请求2都在一个连接上发出来的;需要一个复杂的架构设计,一般将websocket进行分成,第一层叫做接入层,第二层叫做实现层 ;第一层可能就做一些简单的事情,如消息转换,将消息转换成消息分发系统识别的协议,消息系统可能是kafa\MQ\redis等 ;消息系统收到这样的消息后中间做一个适配,通过消息分发系统分配到一个server上(订阅这个消息的服务端),请求2又分到另外一个server上;整体来讲牺牲了简单性后也可以达到很好的伸缩性

在这里插入图片描述

长连接的心跳保持

HTTP长连接只能基于简单的超时(常见为65秒)
WebSocket连接基于ping/Pong心跳机制维持

兼容HTTP协议

默认使用80/443端口
协议升级
代理服务器可以简单支持

设计哲学:在web约束下暴露TCP给上层

元数据去哪里了?

如http1.0中有header头里有很多元数据,如cookie/content-type
对比:HTTP协议头会存放元数据
websocket上传输的应用层存放元数据,没有定义元数据应该放在哪里,完全由应用层自己编码

基于帧不是基于流(HTTP、TCP)

http是基于流的,ascII字符流的,我们只要保证每个字符是有序的,tcp会保证接收方也是有序的;
每一帧有两种承载数据的方式,一种是承载的字符数据(ascII数据),一种是承载的二进制数据(Binary数据)

基于浏览器的同源策略模型(非浏览器无效)

可以使用Access-Control-Allow-Origin等头部;

基于URI、子协议支持同主机同端口上的多个服务;

同一台主机为多个ws应用进行服务
子协议:x-kaazing-handshake
下面是请求头里的子协议
在这里插入图片描述
下面是响应头,代表接收了这个子协议
在这里插入图片描述

websocket协议格式

在这里插入图片描述
示意图是由RFC文档中提出的
最上边的0,1,2,3是表示4个字节
红色的2字节是必然存在的帧的头部
使用wirshark抓包可以看到帧的头部,如果使用谷歌浏览器开发者模式抓包无法看到

RSV (1,2,3)是保留值

默认是0,仅当使用extension扩展的时候才可以使用,由扩展决定其值

opcode中定义了帧的类型:

持续帧
0:继续前一帧;表示和前一个帧的类型完全一致的
非控制帧:主要用来传输数据的
1:文本帧(UTF8)
2: 二进制帧
3-7: 为非控制保留帧
控制帧
8:关闭帧:当关闭ws链接的时候就会有关闭帧
9:心跳帧ping
A:心跳帧pong
B-F:为控制保留帧
opcode是4位,所以是16进制

下图传输的是文本,所以opcode是1
在这里插入图片描述
下图传输的是binary帧,所以opcode是2
在这里插入图片描述

ABNF(语法格式描述规范)对帧的描述

在这里插入图片描述

Websocket是HTTP1.1格式升级而来的

URI 格式

ws-URI=“ws:” “//” host[":" port ] path ["?" query]
默认端口是80

wss-URI=“wss:” “//” host[":" port ] path ["?" query]
默认端口是443

客户端提供信息:

host和port:主机名和端口
shema:是否基于SSL
访问资源:URI
握手随机数:Sec-WebSocket-Key;握手的随机数(需要非常随机,如时间戳)
选择子协议:Sec-WebSocket-Protocol(复杂的时候可能会用到自协议)----可选项
扩展协议:Sec-WebSocket-Extensions(如:使用压缩的时候会进行两边协商,使用自协议)----可选项
CORS跨域:Origin–可选项

WebSocket如何建立链接

建立握手的流程:

客户端发起http消息,服务器回http的响应,这个消息和响应完成后,就认为ws的握手建立成功了;这次握手到底传递什么消息呢?如下图
在这里插入图片描述

分为四类:

红色消息是必选消息,
请求
请求是get方法,
基于http1.1,1.0是不可以的,
头部必须是host头部,
Sec-WebSocket-Version:13 目前这个版本都应该传13
Connection这里必须要传 Upgrade,代表升级
Upgrade:传websocket ,代表升级要升级到websocket协议

响应
服务器返回的状态码必须是101
Connection这里必须要传 Upgrade
Upgrade:必须传websocket

绿色消息也是必选项:
建立握手的时候传了一段随机数
服务器端必须根据这个随机书,生成一段新的base64编码

蓝色消息:
Origin:跨域信息

黑色消息:
Sec-WebSocket-Extensions扩展,这个图没有协商成功,所以服务器没有返回,如果协商成功服务器有对应的返回
在这里插入图片描述

如何证明握手被服务端所接受呢?

请求中Sec-WebSocket-Key必须是随机数
随机数做base64编码
响应中生成Sec-WebSocket-Accept证明值

  1. 拼接值:用Sec-WebSocket-Key+GUID
  2. SHA1编码:通过编码得到一串16进制的数字
  3. BASE 64值:将2中的数字做BASE 64编码
  4. 编码完成后,放到Sec-WebSocket-Accept中
    PS:
    GUID:是RFC4122中定义的一串写死的字符串

    在这里插入图片描述

消息是如何传递的

Message消息

消息是逻辑概念,一条http request/response就是一条消息,消息是应用端想发出的一条逻辑上有效的、完整的信息,一条消息可能很大,而每一个数据帧所能承载的最大的长度是有限的,服务器/客户端内存是有限的,能够处理的帧的数量和长度更有限,所以一个消息很可能被拆分成多个帧来组成;

  1. 当一个消息由1个或则多个帧组成,这些数据帧属于同一类型
  2. 代理服务器可能合并、拆分消息的数据帧
Frame数据帧
  1. 持续帧
  2. 文本帧、二进制帧
非控制帧的消息分片:有序

FIN:表示一个消息的结尾;
FIN=1表示这条消息已经结束了
消息拆分:
FIN=0:非结束消息
op=1/2 opcode=1或则2,是文本消息或则二进制消息

FIN=0:
op=0 代表持续帧,它的类型和上一个帧一致

FIN=1
op=A:插入了一条控制帧,插入控制帧是可以的,但是不能插入其他的数据帧

FIN=1
op=0:数据帧,是持续帧,结尾了
将三个蓝色的帧拼装成同一个消息

消息未拆分:
FIN=1
op>0 :表示1条消息由1个数据帧构成
在这里插入图片描述

数据帧的长度

数据帧的长度指的是ws整体的一个消息内容的长度
消息内容长度组成:
应用消息长度:
扩展数据长度:
在这里插入图片描述

<=125字节
仅使用payload len
payload len的数据长度只有7位,共由2的7次方,如果要表达较大的数据使用下方的编码格式
正好发送125个字节
在这里插入图片描述

126至2^16 - 1
使用蓝色区域
Extended payload length(16bit)16位 表示长度
下图是发送126个字节
在这里插入图片描述

2^16 至 2 ^64 -1
Extended payload length共8字节 64位表示长度
整个绿色区域表示

发送消息

  • 确保websocket会话处于OPEN状态
  • 以帧来承载消息,一条消息可以拆分多个数据帧
  • 客户端发送的帧必须基于掩码编码
  • 一旦发送或则接收到关闭帧,链接处于CLOSING状态
  • 一旦发送来关闭帧,且接收到关闭帧,连接处于CLOSED状态
  • TCP连接关闭后,WebSocket连接才完全被关闭

掩码:结尾有MASKED,客户端向服务端发送的;下一条返回的就没有掩码;
在这里插入图片描述

为什么要做掩码处理呢?

掩码处理是为了减少缓存污染攻击风险的;
有些实现不当的代理服务器,它不认识websocket协议,只能透传HTTP协议
下方图讲解缓存污染攻击
在这里插入图片描述

怎样做掩码处理呢

frame-masking-key 掩码
客户端消息:MASK为1(包括控制帧),传递32位无法预测的、随机的Masking-key
服务端消息:MASK为0
在这里插入图片描述
强制浏览器执行以下方法:

  • 生成随机的32位frame-masking-key,不能让js代码猜出(否则可以反向构造)
  • 对传输的包体按frame-masking-key执行可对称解密的XOR异或操作,使代理服务器不识别
  • 消息编码算法:
    j = i MOD 4
    transformed-octet-i=priginal-octet-i XOR masking-key-octet-j
    下图传了个masking-key,对其进行编码后的值是蓝色区域的值
    在这里插入图片描述
    在这里插入图片描述

如何保持心跳

http长连接,就是设置定时器,如果到时间后,就关闭连接,而ws是定时器到时间后,发送心跳ping\pong

心跳帧

心跳帧格式
在这里插入图片描述
心跳帧可以穿插在数据帧中传输
ping帧:
opcode = 9
可以含有数据
下图,ping是服务端发的在这里插入图片描述

pong帧:
opcode = A
必须与ping帧数据相同
如果收到了ping帧,就应该回一个pong帧
pong是客户端发的,所以有MASKED掩码
在这里插入图片描述

如何关闭websocket会话?

ws是双向传输协议,所以在关闭ws会话的时候,需要进行双向关闭;ws协议承载在tcp协议之上,所以通常应该在tcp协议之前,先关闭ws会话

TCP连接意外中断:
那么ws连接也会中断

**正常关闭:**控制帧中的关闭帧:在tcp连接之前先做双向关闭

  1. 任何一方发送关闭帧后,都不能在发送任何数据了,但是可以接收数据
  2. 接收到关闭帧后,不再接收任何到达的数据

关闭帧的格式
在这里插入图片描述
opcode=8
可以包含数据,但仅用于解释关闭会话的原因

  1. 前2字节位无符号整形
  2. 遵循mask掩码规则
    在这里插入图片描述
    在这里插入图片描述
    ws关闭后,就是tcp的四次握手关闭连接
    在这里插入图片描述
    前2个字节必须是无符号整形,也就是下方的错误码
    在这里插入图片描述
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值