WebSocket协议如何详细解析?
- 内容介绍
- 文章标签
- 相关推荐
本文共计3513个文字,预计阅读时间需要15分钟。
WebSocket + 协议背景 + 早期,网站通过轮询或Comet技术向用户推送消息,轮询即浏览器每隔几秒向服务器发送HTTP请求,服务器返回消息后立即关闭连接。
早期,在网站上推送消息给用户,只能通过轮询的方式或 Comet 技术。轮询就是浏览器每隔几秒钟向服务端发送 HTTP 请求,然后服务端返回消息给客户端。
轮询技术一般在浏览器上就是使用 setInerval 或 setTimeout
这种方式的缺点:
需要不断的向服务端发送 HTTP 请求,这种就比较浪费带宽资源。而且发送 HTTP 请求只能由客户端发起,这也是早期 HTTP1.0/1.1 协议的一个缺点。它做不到由服务端向客户端发起请求。
为了能实现客户端和服务端的双向通信,经过多年发展于是 WebSocket 协议在 2008 年就诞生了。
它最初是在 HTML5 中引入的。经过多年发展后,该协议慢慢被多个浏览器支持,RFC 在 2011 年就把该协议作为一个国际标准,叫 rfc6455。
二、协议简介WebSocket 是一种支持双向通信的网络协议。
- 双向通信:客户端(比如浏览器)可以向服务端发送消息,服务端也可以主动向客户端发送消息。
这样就实现了客户端和服务端的双向通信,那么上面所说的消息推送就比较容易实现了。
原先的 HTTP1.0/1.1 只能是客户端向服务端发送消息。
协议特点:
- 建立在 TCP 协议之上。
- WebSocket 协议是从 HTTP 协议升级而来。
- 与 HTTP 协议良好兼容新。默认端口是 80 和 443,握手阶段采用 HTTP 协议。
- 数据格式比较轻量,通信效率高,性能开销小。
- 可以发送文本,也可以发送二进制数据。
- 没有同源限制,客户端可以与任意服务端通信。
- 协议标识符是 ws(如果加密,则为 wss),服务器网址就是 URL。
- 可以支持扩展,定了扩展协议。
- 保持连接状态,websocket 是一种有状态的协议,通信就可以省略部分状态信息。
- 实时性更强,因为是双向通信协议,所以服务端可以随时向客户端发送数据。
WebSocket 协议建立复用了 HTTP 的握手请求过程。
客户端通过 HTTP 请求与 WebSocket 服务端协商升级协议。协议完成后,后续的数据交互则遵循 WebSocket 的协议。
- 客户端发起协议升级请求
表中 opcode 操作码: www.rfc-editor.org/rfc/rfc6455.html#section-5.3 Client-to-Server Masking 掩码键(Masking-key)是由客户端挑选出来的 32 位的随机数。掩码操作不会影响数据载荷的长度。掩码、反掩码操作都采用如下算法: 举例说明: 算法描述为: original-octet-i 与 masking-key-octet-j 异或后,得到 transformed-octet-i。 分片的目的: WebSocket 的每条消息(message)可能被切分为多个数据帧。 当 WebSocket 的接收方接收到一个数据帧时,会根据 FIN 值来判断是否收到消息的最后一个数据帧。 从上图可以看出,FIN = 1 时,表示为消息的最后一个数据帧;FIN = 0 时,则不是消息的最后一个数据帧,接收方还要继续监听接收剩余数据帧。 opcode 表示数据传输的类型,0x01 表示文本类型的数据;0x02 表示二进制类型的数据;0x00 比较特殊,表示延续帧(continuation frame),意思就是完整数据对应的数据帧还没有接收完。 更多分片内容请看这里:www.rfc-editor.org/rfc/rfc6455.html#section-5.4 消息分片example: (具体例子见:developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers) 在第二小结中我们介绍了 websocket 的特点,其中有一个是保持连接状态。 websocket 是建立在 tcp 之上,那也就是客户端与服务端的 tcp 通道要保持连接不断开。 怎么保持呢?可以用心跳来实现。 其实 websocket 协议早就想到了,它的帧数据格式中有一个字段 opcode,定义了 2 种类型操作, ping 和 pong,opcode 分别是 说明:对于长时间没有数据往来的连接,如果依旧长时间保持连接的状态,那么就会浪费连接资源。 [完]
扩展数据:如果没有协商使用扩展的话,扩展数据数据为 0 字节。所有的扩展都必须声明扩展数据的长度,或者可以如何计算出扩展数据的长度。此外,扩展如何使用必须在握手阶段就协商好。如果扩展数据存在,那么载荷数据长度必须将扩展数据的长度包含在内。
应用数据:任意的应用数据,在扩展数据之后(如果存在扩展数据),占据了数据帧剩余的位置。载荷数据长度 减去 扩展数据长度,就得到应用数据的长度。
(x+y) 字节
数据帧另外一种表达方式
ws-frame = frame-fin ; 1 bit in length
frame-rsv1 ; 1 bit in length
frame-rsv2 ; 1 bit in length
frame-rsv3 ; 1 bit in length
frame-opcode ; 4 bits in length
frame-masked ; 1 bit in length
frame-payload-length ; either 7, 7+16,
; or 7+64 bits in
; length
[ frame-masking-key ] ; 32 bits in length
frame-payload-data ; n*8 bits in
; length, where
; n >= 0
frame-fin = %x0 ; more frames of this message follow
/ %x1 ; final frame of this message
; 1 bit in length
frame-rsv1 = %x0 / %x1
; 1 bit in length, MUST be 0 unless
; negotiated otherwise
frame-rsv2 = %x0 / %x1
; 1 bit in length, MUST be 0 unless
; negotiated otherwise
frame-rsv3 = %x0 / %x1
; 1 bit in length, MUST be 0 unless
; negotiated otherwise
frame-opcode = frame-opcode-non-control /
frame-opcode-control /
frame-opcode-cont
frame-opcode-cont = %x0 ; frame continuation
frame-opcode-non-control= %x1 ; text frame
/ %x2 ; binary frame
/ %x3-7
; 4 bits in length,
; reserved for further non-control frames
frame-opcode-control = %x8 ; connection close
/ %x9 ; ping
/ %xA ; pong
/ %xB-F ; reserved for further control
; frames
; 4 bits in length
frame-masked = %x0
; frame is not masked, no frame-masking-key
/ %x1
; frame is masked, frame-masking-key present
; 1 bit in length
frame-payload-length = ( %x00-7D )
/ ( %x7E frame-payload-length-16 )
/ ( %x7F frame-payload-length-63 )
; 7, 7+16, or 7+64 bits in length,
; respectively
frame-payload-length-16 = %x0000-FFFF ; 16 bits in length
frame-payload-length-63 = %x0000000000000000-7FFFFFFFFFFFFFFF
; 64 bits in length
frame-masking-key = 4( %x00-FF )
; present only if frame-masked is 1
; 32 bits in length
frame-payload-data = (frame-masked-extension-data
frame-masked-application-data)
; when frame-masked is 1
/ (frame-unmasked-extension-data
frame-unmasked-application-data)
; when frame-masked is 0
frame-masked-extension-data = *( %x00-FF )
; reserved for future extensibility
; n*8 bits in length, where n >= 0
frame-masked-application-data = *( %x00-FF )
; n*8 bits in length, where n >= 0
frame-unmasked-extension-data = *( %x00-FF )
; reserved for future extensibility
; n*8 bits in length, where n >= 0
frame-unmasked-application-data = *( %x00-FF )
; n*8 bits in length, where n >= 0
客户端到服务端的掩码算法
Octet i of the transformed data ("transformed-octet-i") is the XOR of
octet i of the original data ("original-octet-i") with octet at index
i modulo 4 of the masking key ("masking-key-octet-j"):
j = i MOD 4
transformed-octet-i = original-octet-i XOR masking-key-octet-j
j = i MOD 4
transformed-octet-i = original-octet-i XOR masking-key-octet-j
数据分片
Client: FIN=1, opcode=0x1, msg="hello"
Server: (process complete message immediately) Hi.
Client: FIN=0, opcode=0x1, msg="and a"
Server: (listening, new message containing text started)
Client: FIN=0, opcode=0x0, msg="happy new"
Server: (listening, payload concatenated to previous message)
Client: FIN=1, opcode=0x0, msg="year!"
Server: (process complete message) Happy new year to you too!
0x9、0xA。
== just do it ==
相关推荐
本文共计3513个文字,预计阅读时间需要15分钟。
WebSocket + 协议背景 + 早期,网站通过轮询或Comet技术向用户推送消息,轮询即浏览器每隔几秒向服务器发送HTTP请求,服务器返回消息后立即关闭连接。
早期,在网站上推送消息给用户,只能通过轮询的方式或 Comet 技术。轮询就是浏览器每隔几秒钟向服务端发送 HTTP 请求,然后服务端返回消息给客户端。
轮询技术一般在浏览器上就是使用 setInerval 或 setTimeout
这种方式的缺点:
需要不断的向服务端发送 HTTP 请求,这种就比较浪费带宽资源。而且发送 HTTP 请求只能由客户端发起,这也是早期 HTTP1.0/1.1 协议的一个缺点。它做不到由服务端向客户端发起请求。
为了能实现客户端和服务端的双向通信,经过多年发展于是 WebSocket 协议在 2008 年就诞生了。
它最初是在 HTML5 中引入的。经过多年发展后,该协议慢慢被多个浏览器支持,RFC 在 2011 年就把该协议作为一个国际标准,叫 rfc6455。
二、协议简介WebSocket 是一种支持双向通信的网络协议。
- 双向通信:客户端(比如浏览器)可以向服务端发送消息,服务端也可以主动向客户端发送消息。
这样就实现了客户端和服务端的双向通信,那么上面所说的消息推送就比较容易实现了。
原先的 HTTP1.0/1.1 只能是客户端向服务端发送消息。
协议特点:
- 建立在 TCP 协议之上。
- WebSocket 协议是从 HTTP 协议升级而来。
- 与 HTTP 协议良好兼容新。默认端口是 80 和 443,握手阶段采用 HTTP 协议。
- 数据格式比较轻量,通信效率高,性能开销小。
- 可以发送文本,也可以发送二进制数据。
- 没有同源限制,客户端可以与任意服务端通信。
- 协议标识符是 ws(如果加密,则为 wss),服务器网址就是 URL。
- 可以支持扩展,定了扩展协议。
- 保持连接状态,websocket 是一种有状态的协议,通信就可以省略部分状态信息。
- 实时性更强,因为是双向通信协议,所以服务端可以随时向客户端发送数据。
WebSocket 协议建立复用了 HTTP 的握手请求过程。
客户端通过 HTTP 请求与 WebSocket 服务端协商升级协议。协议完成后,后续的数据交互则遵循 WebSocket 的协议。
- 客户端发起协议升级请求
表中 opcode 操作码: www.rfc-editor.org/rfc/rfc6455.html#section-5.3 Client-to-Server Masking 掩码键(Masking-key)是由客户端挑选出来的 32 位的随机数。掩码操作不会影响数据载荷的长度。掩码、反掩码操作都采用如下算法: 举例说明: 算法描述为: original-octet-i 与 masking-key-octet-j 异或后,得到 transformed-octet-i。 分片的目的: WebSocket 的每条消息(message)可能被切分为多个数据帧。 当 WebSocket 的接收方接收到一个数据帧时,会根据 FIN 值来判断是否收到消息的最后一个数据帧。 从上图可以看出,FIN = 1 时,表示为消息的最后一个数据帧;FIN = 0 时,则不是消息的最后一个数据帧,接收方还要继续监听接收剩余数据帧。 opcode 表示数据传输的类型,0x01 表示文本类型的数据;0x02 表示二进制类型的数据;0x00 比较特殊,表示延续帧(continuation frame),意思就是完整数据对应的数据帧还没有接收完。 更多分片内容请看这里:www.rfc-editor.org/rfc/rfc6455.html#section-5.4 消息分片example: (具体例子见:developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers) 在第二小结中我们介绍了 websocket 的特点,其中有一个是保持连接状态。 websocket 是建立在 tcp 之上,那也就是客户端与服务端的 tcp 通道要保持连接不断开。 怎么保持呢?可以用心跳来实现。 其实 websocket 协议早就想到了,它的帧数据格式中有一个字段 opcode,定义了 2 种类型操作, ping 和 pong,opcode 分别是 说明:对于长时间没有数据往来的连接,如果依旧长时间保持连接的状态,那么就会浪费连接资源。 [完]
扩展数据:如果没有协商使用扩展的话,扩展数据数据为 0 字节。所有的扩展都必须声明扩展数据的长度,或者可以如何计算出扩展数据的长度。此外,扩展如何使用必须在握手阶段就协商好。如果扩展数据存在,那么载荷数据长度必须将扩展数据的长度包含在内。
应用数据:任意的应用数据,在扩展数据之后(如果存在扩展数据),占据了数据帧剩余的位置。载荷数据长度 减去 扩展数据长度,就得到应用数据的长度。
(x+y) 字节
数据帧另外一种表达方式
ws-frame = frame-fin ; 1 bit in length
frame-rsv1 ; 1 bit in length
frame-rsv2 ; 1 bit in length
frame-rsv3 ; 1 bit in length
frame-opcode ; 4 bits in length
frame-masked ; 1 bit in length
frame-payload-length ; either 7, 7+16,
; or 7+64 bits in
; length
[ frame-masking-key ] ; 32 bits in length
frame-payload-data ; n*8 bits in
; length, where
; n >= 0
frame-fin = %x0 ; more frames of this message follow
/ %x1 ; final frame of this message
; 1 bit in length
frame-rsv1 = %x0 / %x1
; 1 bit in length, MUST be 0 unless
; negotiated otherwise
frame-rsv2 = %x0 / %x1
; 1 bit in length, MUST be 0 unless
; negotiated otherwise
frame-rsv3 = %x0 / %x1
; 1 bit in length, MUST be 0 unless
; negotiated otherwise
frame-opcode = frame-opcode-non-control /
frame-opcode-control /
frame-opcode-cont
frame-opcode-cont = %x0 ; frame continuation
frame-opcode-non-control= %x1 ; text frame
/ %x2 ; binary frame
/ %x3-7
; 4 bits in length,
; reserved for further non-control frames
frame-opcode-control = %x8 ; connection close
/ %x9 ; ping
/ %xA ; pong
/ %xB-F ; reserved for further control
; frames
; 4 bits in length
frame-masked = %x0
; frame is not masked, no frame-masking-key
/ %x1
; frame is masked, frame-masking-key present
; 1 bit in length
frame-payload-length = ( %x00-7D )
/ ( %x7E frame-payload-length-16 )
/ ( %x7F frame-payload-length-63 )
; 7, 7+16, or 7+64 bits in length,
; respectively
frame-payload-length-16 = %x0000-FFFF ; 16 bits in length
frame-payload-length-63 = %x0000000000000000-7FFFFFFFFFFFFFFF
; 64 bits in length
frame-masking-key = 4( %x00-FF )
; present only if frame-masked is 1
; 32 bits in length
frame-payload-data = (frame-masked-extension-data
frame-masked-application-data)
; when frame-masked is 1
/ (frame-unmasked-extension-data
frame-unmasked-application-data)
; when frame-masked is 0
frame-masked-extension-data = *( %x00-FF )
; reserved for future extensibility
; n*8 bits in length, where n >= 0
frame-masked-application-data = *( %x00-FF )
; n*8 bits in length, where n >= 0
frame-unmasked-extension-data = *( %x00-FF )
; reserved for future extensibility
; n*8 bits in length, where n >= 0
frame-unmasked-application-data = *( %x00-FF )
; n*8 bits in length, where n >= 0
客户端到服务端的掩码算法
Octet i of the transformed data ("transformed-octet-i") is the XOR of
octet i of the original data ("original-octet-i") with octet at index
i modulo 4 of the masking key ("masking-key-octet-j"):
j = i MOD 4
transformed-octet-i = original-octet-i XOR masking-key-octet-j
j = i MOD 4
transformed-octet-i = original-octet-i XOR masking-key-octet-j
数据分片
Client: FIN=1, opcode=0x1, msg="hello"
Server: (process complete message immediately) Hi.
Client: FIN=0, opcode=0x1, msg="and a"
Server: (listening, new message containing text started)
Client: FIN=0, opcode=0x0, msg="happy new"
Server: (listening, payload concatenated to previous message)
Client: FIN=1, opcode=0x0, msg="year!"
Server: (process complete message) Happy new year to you too!
0x9、0xA。
== just do it ==

