Skip to content

WebSocket协议

WebSocket 是一种全双工通信协议,允许客户端和服务器之间建立持久化的双向通信连接。WebSocket 协议设计的初衷是解决 HTTP 协议在实时交互上的局限性,例如长轮询、Ajax 等方法的高延迟问题。WebSocket 可以在单个 TCP 连接上实现客户端与服务器之间的实时、低延迟的数据传输。

HTTP的局限性

HTTP(超文本传输协议)是一种用于传输数据的协议,但它也有一些局限性,主要包括以下几点:

  1. 无状态性:HTTP 是一种无状态协议,每个请求都是独立的,不会保留客户端的上下文信息。这使得实现用户会话和状态管理变得复杂,需要额外的机制(如 cookies 或 sessions)。
  2. 性能问题
    • 延迟:每次请求都需要重新建立连接,尤其是在使用 HTTP/1.1 时,存在多个请求时会造成额外的延迟。
    • 带宽浪费:每次请求都需要发送请求头,增加了网络带宽的消耗。
  3. 安全性(加密不足):传统的 HTTP 传输是明文的,数据在传输过程中容易被窃听和篡改。虽然可以通过 HTTPS(HTTP Secure)来增强安全性,但 HTTP 本身并不提供加密。
  4. 不支持双向通信:HTTP 是请求-响应模型,客户端发起请求后,服务器才能响应,不支持实时双向通信。这在需要即时数据传输的应用中(如聊天应用)造成了限制。
  5. 请求重定向和缓存管理的复杂性:在复杂的应用中,HTTP 请求的重定向和缓存管理可能导致性能下降和数据一致性问题,增加了开发和维护的复杂性。
  6. 适应性差:HTTP 的设计初衷是用于静态网页的传输,对于现代动态内容和实时应用(如流媒体、在线游戏等)支持不够灵活。

虽然 HTTP 是互联网通信的基础,但其局限性促使开发者寻求更高效、更安全的替代方案,如 WebSocket、HTTP/2 和 HTTP/3 等。理解这些局限性有助于在设计系统时做出更明智的选择。

WebSocket协议标准

WebSocket 是基于 TCP 的协议,定义在 IETF 的 RFC 6455 标准中后由 RFC 7936 补充规范。。WebSocket 的连接从一个标准的 HTTP 请求开始,经过一次协议升级后,建立起一个全双工的 WebSocket 连接。

WebSocket 默认使用以下两个端口:

  • 80 端口:非加密的 WebSocket 协议,使用 ws:// 开头的 URL。
  • 443 端口:加密的 WebSocket 协议(通过 TLS/SSL 加密),使用 wss:// 开头的 URL。

WebSocket 的 URL 格式与 HTTP 类似,但有一些特定的细节。以下是 WebSocket URL 的基本结构:

http
ws://[host]:[port]/[path]

可以看到 WebSocket 的 URL 一共由四部分构成,下面是关于它们的具体说明:

  1. 协议:ws://:表示 WebSocket 协议;wss://:表示 WebSocket Secure(加密的 WebSocket),类似于 HTTPS。
  2. 主机(host):服务器的域名或 IP 地址,例如 example.com 或 192.168.1.100。
  3. 端口(port)(可选):WebSocket 默认端口是 80(ws)和 443(wss),可以省略。如果使用其他端口,则需要显式指定,例如 :8080。
  4. 路径(path):服务器上用于 WebSocket 连接的具体路径,例如 /socket。

需要注意的是,WebSocket 的 URL 必须是有效的 HTTP URL,并且服务器必须支持 WebSocket 连接。此外,WebSocket 连接是基于 HTTP 协议的,因此也需要遵循 HTTP 的同源策略,即只能连接到与当前页面相同的域名或 IP 地址。

下面是几个关于 WebSocket URL 格式的几个示例:

http
// 非加密的 WebSocket 连接
ws://example.com/socket

// 加密的 WebSocket 连接
wss://example.com/socket

// 指定端口的连接
ws://example.com:8080/socket

// 带路径的连接
wss://example.com/api/v1/chat

WebSocket URL 的格式与 HTTP URL 类似,主要区别在于使用的协议前缀(ws 或 wss)。使用时要确保服务器能够处理 WebSocket 连接,并在前端正确配置 URL。

工作原理

握手阶段

WebSocket 连接从 HTTP 请求开始,客户端通过 HTTP 升级机制请求升级协议:

  1. 客户端发起 HTTP 请求,并在请求头中包含特定的 WebSocket 字段以表示希望建立 WebSocket 连接。Http请求消息示例(部分):

    http
    GET /chat HTTP/1.1
    Host: example.com
    Upgrade: websocket	// 表示希望将协议升级为 WebSocket。
    Connection: Upgrade	// 告知服务器此次连接希望协议升级。
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==		// 客户端生成的随机密钥,用于服务器生成握手应答的 Sec-WebSocket-Accept。
    Sec-WebSocket-Version: 13		// 指定 WebSocket 的版本,当前标准版本是 13。
  2. 服务器收到请求后,返回 101 状态码,并附带确认的握手信息。http响应消息示例(部分):

    http
    HTTP/1.1 101 Switching Protocols	// 状态行
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=	// 由 Sec-WebSocket-Key 和特定算法生成的哈希值,用于确认握手的安全性。
  3. 状态行由三个部分组成:

    • 协议版本:HTTP/1.1 表示使用的 HTTP 版本。
    • 状态码:101 是状态码,表示服务器已接受客户端的请求并正在切换协议。
    • 状态描述:Switching Protocols 是对状态码的描述,说明了服务器的响应内容。

数据帧传输

WebSocket 的数据通过帧 (frame) 进行传输("帧"是指在客户端和服务器之间传输的数据单元)。每一帧都有固定的格式,包含数据以及控制信息。WebSocket 支持以下几种帧类型:

  • 文本帧(Text Frame):用于传输文本数据,通常是 UTF-8 编码的字符串。
  • 二进制帧(Binary Frame):用于传输二进制数据,如图片、音视频数据等。
  • 关闭帧(Close Frame):用于关闭连接。
  • Ping 和 Pong 帧:用于检测连接的活跃性,Ping 由客户端或服务器发送,Pong 由对方响应。 每个 WebSocket 数据帧的格式如下:
cpp
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+
  • FIN: (1bit),表示是否为最后一帧。如果为1,表示这是最后一帧;如果为0,表示后面还有帧。
  • RSV1, RSV2, RSV3:(各1bit)保留位,用于扩展协议。通常在初始实现中,这些位应设置为0。
  • Opcode: (4bit),用于表示帧的类型。
    • 0x0:继续帧
    • 0x1:文本帧
    • 0x2:二进制帧
    • 0x3~0x7:预留给以后的非控制帧
    • 0x8:连接关闭帧
    • 0x9:Ping 帧
    • 0xA:Pong 帧
    • 0xB~0xF:预留给以后的控制帧
  • Mask: (1bit),表示是否对数据进行掩码处理。
    • 客户端到服务器的消息必须使用掩码
    • 服务器到客户端的消息不需要使用掩码
  • Payload length: (7bit/7+16bit/7+64bit),表示数据的长度。
    • 当它的值在 0 到 125 之间时,表示有效负载的字节数。
    • 当它的值是 126(0x7E)时,表示后面将有 2 字节(16 位)来表示有效负载的真实长度(有效负载长度在 0 到 65535 之间)。
    • 当它的值是 127(0x7F)时,则表示后面会有 8 字节(64 位)来表示有效负载的真实长度。
  • Masking Key:(0或4bytes)
    • 所有从客户端发往服务端的数据帧都已经与一个包含在这一帧中的32 bit的掩码进行了运算。
    • 如果mask标志位(1 bit)为1,那么这个字段存在,如果标志位为0,那么这个字段不存在。
  • Payload data:实际传输的数据,根据Payload length字段的值来确定长度。

保持连接与心跳

WebSocket 在建立连接后会保持连接状态,直到一方显式关闭连接。为了防止连接由于网络问题或闲置超时而断开,WebSocket 支持通过 Ping-Pong 机制 来检测连接的活跃性。客户端或服务器可以发送一个 Ping 帧,接收方必须在合理时间内响应一个 Pong 帧,确保连接仍然正常。

Ping-Pong 机制的示例流程:

  1. 客户端发送 Ping 帧:
    • 客户端发送一个 Ping 帧,Payload Data 可能包含当前时间戳。
    • 帧格式:| FIN | RSV | Opcode: 0x9 | MASK=1 | Payload Length | Masking key=6 | Payload Data=时间戳 |
  2. 服务器接收并响应 Pong 帧:
    • 服务器接收到 Ping 帧后,立即发送一个 Pong 帧,Payload Data 与接收到的 Ping 帧的 Payload Data 相同。
    • 帧格式:| FIN | RSV | Opcode: 0xA | MASK=0 | Payload Length | Payload Data=时间戳 |
  3. 客户端接收 Pong 帧:
    • 客户端接收到 Pong 帧后,可以确认连接仍然活跃。
    • 客户端可以根据时间戳计算往返时间(RTT),以评估网络延迟。

在使用 WebSocket 协议的 Ping-Pong 机制的时候,有以下事项需要再次强调:

  1. 双向检测:客户端和服务器都可以发送 Ping 帧,并期望对方发送 Pong 帧。这使得双方都能检测到连接的活跃性。
  2. 超时处理:如果发送 Ping 帧的一方在一定时间内没有收到 Pong 帧,可以认为连接已经断开或不活跃,并采取相应的措施(如关闭连接或重新连接)。
  3. Payload Data:Ping 和 Pong 帧的 Payload Data 是可选的,但通常会包含一些简单的数据(如时间戳),以便双方能够进行更详细的检测和分析。

关闭连接

WebSocket 连接关闭时,双方需要发送一个关闭帧(Close Frame)。关闭帧包含一个关闭码和关闭原因,可以用于表明连接关闭的原因。具体来说,关闭帧的格式遵循 WebSocket 协议规范,其中状态码的位置和结构如下:

  • FIN 位: 1 bit
  • RSV 位: 3 bits (保留位,一般用于扩展)
  • Opcode: 4 bits (对于关闭帧,值为 0x8)
  • MASK: 1 bit (指示消息是否被掩码,客户端消息必须掩码)
  • Payload length: 7 bits, 7+16 bits, 或 7+64 bits (指示随后的有效负载的长度)
  • Masking key: 0或4 bytes (如果 MASK 位为 1,则使用,包含用于掩码的密钥)
  • Payload data: (长度通过 Payload length 指定)
    • 关闭状态码: 在关闭帧的有效负载部分的最开始,紧接在 Payload length 字段后面。这两个字节 (未经掩码处理)表示状态码(Close Code)。
    • 关闭原因: 状态码之后可以跟随一个可选的 UTF-8 字符串,描述关闭的原因。

常见的标准关闭码有:

  • 1000:正常关闭。
  • 1001:服务器或客户端因特殊原因需要关闭(如服务器关闭)。
  • 1002:协议错误。
  • 1003:接收到不支持的数据类型。
  • 1006:异常关闭,没有收到关闭帧。
  • 1007:收到包含不一致数据的帧,导致连接关闭。例如,格式不正确的内容。
  • 1009:发送的消息超过了可以接收的大小。
  • 1011:服务器遇到错误,无法完成请求。
  • 1015:当 WebSocket 连接在 TLS/SSL 握手期间失败时,使用此状态码。

除了上述标准状态码外,使用者也可以定义自定义的关闭状态码,通常建议使用在 1,000 到 1,999 之间的代码(一定要注意避免与标准代码冲突)。

关闭帧字节流示例:

http
| FIN | RSV | Opcode | MASK | Payload length | Close Code (2 bytes) | Close Reason (n bytes) |

关闭流程非常简单就是发送和接收数据,所以对于应用层的 WebSocket 而言就只有两步:

  • 发送: 当一方要关闭连接时,它会构造关闭帧并在 Payload 中写入状态码和可能的关闭原因,然后将其发送给另一方。
  • 接收: 接收方在接收到关闭帧后,会读取 Payload,首先获取状态码,然后读取可选的关闭原因,从而知道连接关闭的具体情况。

如果是基于 TCP 的四次挥手进行描述就是以下四步:

  1. 发起关闭请求:
    • 一方调用关闭操作:连接的一方(可以是客户端或服务器)决定关闭连接时,调用相应的 close() 方法。
    • 发送关闭帧:主动断开连接的一方会创建并发送一个关闭帧(Close Frame),该帧包含关闭状态码和可选的关闭原因。
  2. 接收关闭帧:
    • 接收帧:被动断开连接的一方(接收方)收到关闭帧时,会首先解析该帧的状态码和可选原因。
    • 处理关闭帧:接收方可以根据关闭状态码进行相应的处理(例如,记录日志,更新连接状态等)。
  3. 发送响应的关闭帧 (Pong):
    • 回应关闭请求:接收方在确认关闭请求后(可能进行清理操作)会发送自己的关闭帧,这通常是一个回应帧。
    • 格式:该响应关闭帧也会包含一个状态码,通常是 1000(正常关闭),并可能附带关闭原因。
  4. 最终关闭连接:
    • 双方完成关闭:一旦双方都发送并接收了关闭帧,WebSocket 连接将被正式关闭。
    • 清理资源:关闭连接时,双方可以释放相关资源,如定时器、事件监听器等。

安全性

WebSocket 使用 wss(WebSocket Secure)协议进行安全通信的方式与使用 ws(WebSocket)协议类似,但需要额外的安全配置。

  • WSS: 是通过 TLS/SSL 加密的 WebSocket 协议,类似于 HTTPS 相对于 HTTP 的形式。使用 WSS 可以确保数据在传输过程中得到加密,避免被窃听和篡改。
  • 端口: WSS 通常使用 443 端口,而 WS(不安全)使用 80 端口。

在使用 WSS 时,必须提供有效的 SSL/TLS 证书。可以使用:

  • 自签名证书: 用于开发和测试,但浏览器可能会显示安全警告。
  • 公认的证书: 从受信任的证书颁发机构获取的证书,用于生产环境,以确保通信的安全性。

webSocket和Http对比

相同点

  1. 应用层协议:WebSocket 和 HTTP 都属于应用层协议,主要用于数据的传输。
  2. 基于 TCP:两者都依赖于 TCP 协议进行底层的数据传输,确保数据的可靠性和顺序。
  3. 跨平台兼容性:WebSocket 和 HTTP 都可以在不同的平台和设备上使用,支持跨浏览器和跨设备的通信。
  4. 使用标准的 URL:WebSocket 和 HTTP 都使用类似的 URL 结构,例如 http:// 和 ws://。

不同点

特性HTTPWebSocket
连接模式短连接长连接
通信方式单向(请求-响应)双向(全双工通信)
连接建立每次请求建立新连接,之后关闭连接初始通过 HTTP 握手后建立持久连接
延迟每次请求都需要重新建立连接,延迟较高连接一旦建立后,延迟较低,可实时传输数据
数据格式基于文本的请求/响应,通常是 JSON 或 HTML数据帧,支持文本和二进制数据
状态保持无状态(每个请求都是独立的)有状态(连接保持,允许持续交互)
头信息每个请求都带有完整的 HTTP 头信息头信息仅在握手时使用,后续数据传输不需要
适用场景静态网页加载、API 请求等实时聊天、在线游戏、数据流等
安全性依赖于 HTTPS 加密通过 WSS 进行加密,确保数据传输安全
心跳机制无法主动检测连接是否存活支持 Ping/Pong 心跳机制,确保连接活跃性

连接模式:

  • HTTP:每次请求都是独立的,需要重新建立连接,适合于一次性请求的场景。
  • WebSocket:连接建立后可以保持很长时间,适合需要频繁数据交换的场景。

通信方式:

  • HTTP:客户端发起请求,服务器响应,适用于请求-响应模式。
  • WebSocket:客户端和服务器可以随时发送消息,适合双向通信。

延迟:

  • HTTP:每次请求都需要经过建立和关闭连接的过程,导致较高的延迟。
  • WebSocket:连接建立后,数据传输几乎是即时的,延迟较低。

数据格式:

  • HTTP:通常以文本格式传输,如 JSON、HTML 等。
  • WebSocket:支持更灵活的数据格式,包括二进制数据,适合多种应用场景。

状态保持:

  • HTTP:每个请求都是独立的,无状态设计。
  • WebSocket:连接是持久的,可以保持状态,适合需要长时间交互的应用。

心跳机制:

  • HTTP:没有内置机制检测连接的存活状态。
  • WebSocket:通过 Ping/Pong 帧来保持连接活跃性,检测连接状态。

WebSocket 和 HTTP 各自有其适用的场景和优势。HTTP 适合传统的请求-响应模式,而 WebSocket 则更适合需要实时、双向交互的应用。在现代 Web 开发中,根据应用的需求选择合适的协议可以大大提高性能和用户体验。

HTTP 的应用场景

  • 静态网页加载:用于加载 HTML、CSS、JavaScript 文件等静态资源。
  • RESTful API:适合请求-响应模型,常用于获取、创建、更新和删除数据。
  • 文件下载和上传:支持大文件的上传和下载,适合需要处理文件的场景。
  • 搜索引擎:用于搜索查询,返回结果数据。
  • 内容管理系统:用于管理和发布网站内容,支持用户交互。

WebSocket的应用场景

  • 实时聊天应用:支持用户之间的即时消息传递,如聊天工具和社交平台。
  • 在线游戏:实时数据传输,确保游戏状态和玩家动作的即时更新。
  • 金融交易平台:实时更新股票价格、交易信息和市场动态,适合高频交易。
  • 实时协作工具:适合文档编辑、白板协作等场景,支持多人实时互动。
  • 物联网(IoT)应用:设备之间的实时数据交换和监控,适合智能家居和工业应用。
  • 推送通知:实时推送消息,如新闻更新、社交媒体通知等。

结论

  • HTTP 更适合于请求-响应式的交互,适用于大多数常规的数据传输需求。

  • WebSocket 则专为实时、双向的通信而设计,适合需要快速响应和持续连接的应用场景。选择合适的协议可以根据具体的应用需求和用户体验进行优化。