WebSocket协议如何详细解析?

2026-05-19 15:431阅读0评论SEO资源
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计3513个文字,预计阅读时间需要15分钟。

WebSocket协议如何详细解析?

WebSocket + 协议背景 + 早期,网站通过轮询或Comet技术向用户推送消息,轮询即浏览器每隔几秒向服务器发送HTTP请求,服务器返回消息后立即关闭连接。

WebSocket协议如何详细解析?

一、WebSocket 协议背景

早期,在网站上推送消息给用户,只能通过轮询的方式或 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 是一种有状态的协议,通信就可以省略部分状态信息。
  • 实时性更强,因为是双向通信协议,所以服务端可以随时向客户端发送数据。
三、HTTP 升级到 WebSocket 过程

WebSocket 协议建立复用了 HTTP 的握手请求过程。

客户端通过 HTTP 请求与 WebSocket 服务端协商升级协议。协议完成后,后续的数据交互则遵循 WebSocket 的协议。

  1. 客户端发起协议升级请求

  • 服务端响应协议升级
  • 扩展数据:如果没有协商使用扩展的话,扩展数据数据为 0 字节。所有的扩展都必须声明扩展数据的长度,或者可以如何计算出扩展数据的长度。此外,扩展如何使用必须在握手阶段就协商好。如果扩展数据存在,那么载荷数据长度必须将扩展数据的长度包含在内。
    应用数据:任意的应用数据,在扩展数据之后(如果存在扩展数据),占据了数据帧剩余的位置。载荷数据长度 减去 扩展数据长度,就得到应用数据的长度。 (x+y) 字节

    表中 opcode 操作码:

    • %x0:表示一个延续帧(continuation frame)。当 Opcode 为 0 时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。
    • %x1:表示这是一个文本帧(frame),text frame
    • %x2:表示这是一个二进制帧(frame),binary frame
    • %x3-7:保留的操作代码,用于后续定义的非控制帧。
    • %x8:表示连接断开。connection close
    • %x9:表示这是一个 ping 操作。a ping
    • %xA:表示这是一个 pong 操作。a pong
    • %xB-F:保留的操作代码,用于后续定义的控制帧。
    数据帧另外一种表达方式

    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 客户端到服务端的掩码算法

    www.rfc-editor.org/rfc/rfc6455.html#section-5.3 Client-to-Server Masking

    掩码键(Masking-key)是由客户端挑选出来的 32 位的随机数。掩码操作不会影响数据载荷的长度。掩码、反掩码操作都采用如下算法:

    举例说明:

    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

    • original-octet-i:为原始数据的第 i 字节。
    • transformed-octet-i:为转换后的数据的第 i 字节。
    • j:为i mod 4的结果。
    • masking-key-octet-j:为 mask key 第 j 字节。

    算法描述为: original-octet-i 与 masking-key-octet-j 异或后,得到 transformed-octet-i。

    j = i MOD 4 transformed-octet-i = original-octet-i XOR masking-key-octet-j 数据分片

    分片的目的:

    • 有了消息分片,发送一个消息的时候,就可以发送未知大小的信息。如果消息不能被分片,那么就不得不缓冲整个消息,以便计算长度。而有了分片就可以选择合适大小缓冲区来缓冲分片。
    • 第二个目的是可以使用多路复用。

    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:

    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!

    (具体例子见:developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers)

    五:怎么保持连接

    在第二小结中我们介绍了 websocket 的特点,其中有一个是保持连接状态。

    websocket 是建立在 tcp 之上,那也就是客户端与服务端的 tcp 通道要保持连接不断开。

    怎么保持呢?可以用心跳来实现。

    其实 websocket 协议早就想到了,它的帧数据格式中有一个字段 opcode,定义了 2 种类型操作, ping 和 pong,opcode 分别是 0x9、0xA

    说明:对于长时间没有数据往来的连接,如果依旧长时间保持连接的状态,那么就会浪费连接资源。

    [完]

    六、参考
    • www.rfc-editor.org/rfc/rfc6455.html WebScoket RFC6455
    • www.rfc-editor.org/rfc/rfc5234 ABNF 格式
    • www.ruanyifeng.com/blog/2017/05/websocket.html websocket 教程,阮一峰
    • developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers
    == just do it ==

    本文共计3513个文字,预计阅读时间需要15分钟。

    WebSocket协议如何详细解析?

    WebSocket + 协议背景 + 早期,网站通过轮询或Comet技术向用户推送消息,轮询即浏览器每隔几秒向服务器发送HTTP请求,服务器返回消息后立即关闭连接。

    WebSocket协议如何详细解析?

    一、WebSocket 协议背景

    早期,在网站上推送消息给用户,只能通过轮询的方式或 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 是一种有状态的协议,通信就可以省略部分状态信息。
    • 实时性更强,因为是双向通信协议,所以服务端可以随时向客户端发送数据。
    三、HTTP 升级到 WebSocket 过程

    WebSocket 协议建立复用了 HTTP 的握手请求过程。

    客户端通过 HTTP 请求与 WebSocket 服务端协商升级协议。协议完成后,后续的数据交互则遵循 WebSocket 的协议。

    1. 客户端发起协议升级请求

  • 服务端响应协议升级
  • 扩展数据:如果没有协商使用扩展的话,扩展数据数据为 0 字节。所有的扩展都必须声明扩展数据的长度,或者可以如何计算出扩展数据的长度。此外,扩展如何使用必须在握手阶段就协商好。如果扩展数据存在,那么载荷数据长度必须将扩展数据的长度包含在内。
    应用数据:任意的应用数据,在扩展数据之后(如果存在扩展数据),占据了数据帧剩余的位置。载荷数据长度 减去 扩展数据长度,就得到应用数据的长度。 (x+y) 字节

    表中 opcode 操作码:

    • %x0:表示一个延续帧(continuation frame)。当 Opcode 为 0 时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。
    • %x1:表示这是一个文本帧(frame),text frame
    • %x2:表示这是一个二进制帧(frame),binary frame
    • %x3-7:保留的操作代码,用于后续定义的非控制帧。
    • %x8:表示连接断开。connection close
    • %x9:表示这是一个 ping 操作。a ping
    • %xA:表示这是一个 pong 操作。a pong
    • %xB-F:保留的操作代码,用于后续定义的控制帧。
    数据帧另外一种表达方式

    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 客户端到服务端的掩码算法

    www.rfc-editor.org/rfc/rfc6455.html#section-5.3 Client-to-Server Masking

    掩码键(Masking-key)是由客户端挑选出来的 32 位的随机数。掩码操作不会影响数据载荷的长度。掩码、反掩码操作都采用如下算法:

    举例说明:

    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

    • original-octet-i:为原始数据的第 i 字节。
    • transformed-octet-i:为转换后的数据的第 i 字节。
    • j:为i mod 4的结果。
    • masking-key-octet-j:为 mask key 第 j 字节。

    算法描述为: original-octet-i 与 masking-key-octet-j 异或后,得到 transformed-octet-i。

    j = i MOD 4 transformed-octet-i = original-octet-i XOR masking-key-octet-j 数据分片

    分片的目的:

    • 有了消息分片,发送一个消息的时候,就可以发送未知大小的信息。如果消息不能被分片,那么就不得不缓冲整个消息,以便计算长度。而有了分片就可以选择合适大小缓冲区来缓冲分片。
    • 第二个目的是可以使用多路复用。

    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:

    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!

    (具体例子见:developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers)

    五:怎么保持连接

    在第二小结中我们介绍了 websocket 的特点,其中有一个是保持连接状态。

    websocket 是建立在 tcp 之上,那也就是客户端与服务端的 tcp 通道要保持连接不断开。

    怎么保持呢?可以用心跳来实现。

    其实 websocket 协议早就想到了,它的帧数据格式中有一个字段 opcode,定义了 2 种类型操作, ping 和 pong,opcode 分别是 0x9、0xA

    说明:对于长时间没有数据往来的连接,如果依旧长时间保持连接的状态,那么就会浪费连接资源。

    [完]

    六、参考
    • www.rfc-editor.org/rfc/rfc6455.html WebScoket RFC6455
    • www.rfc-editor.org/rfc/rfc5234 ABNF 格式
    • www.ruanyifeng.com/blog/2017/05/websocket.html websocket 教程,阮一峰
    • developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers
    == just do it ==