WebSocket 协议详解

1. WebSocket简介

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

1.1 单工、半双工和双工

这三个术语用于描述通信的方向性,主要适用于数据传输和网络通信。它们的定义如下:

  1. 单工(Simplex):单工通信是单向的,数据只能从发送方传送到接收方,而无法反向传输,通信过程不支持回复或反馈。

    • 电视广播:信号从发射台发送到电视,但观众无法向发射台发送信号。

    • 传统的广播电台:信号从发射台发送到收音机,但听众无法向发射台发送信号。

  2. 半双工(Half-Duplex):半双工通信是双向的,但在任意时刻只能有一个方向的数据传输。发送方和接收方可以交替发送和接收数据,但不能同时进行。

    • 对讲机:一个人说话时,另一方需要等待,直到说话者结束才能回复。
  3. 双工(Full-Duplex):双工通信是完全双向的,数据可以同时在两个方向传输。发送方和接收方可以同时发送和接收数据。

    • 电话通话:两人可以同时讲话和听到对方的声音。

    • 网络通信(如以太网)。

因此,关于单工、半双工、双工通信的方向性我们可以做如下总结:

  • 单工:单向通信,无法反馈。
  • 半双工:双向通信,但不能同时进行。
  • 双工:双向通信,可以同时进行。

所以,我们可以根据不同的应用场景,选择合适的通信方式来提高数据传输的效率和用户体验。

1.2 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 等。理解这些局限性有助于在设计系统时做出更明智的选择。

2. WebSocket 协议

2.1 协议标准

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 的基本结构:

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

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

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

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

  1. 非加密的 WebSocket 连接

    1
    ws://example.com/socket
  2. 加密的 WebSocket 连接

    1
    wss://example.com/socket
  3. 指定端口的连接

    1
    ws://example.com:8080/socket
  4. 带路径的连接

    1
    wss://example.com/api/v1/chat

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

2.2 工作原理

2.2.1 握手阶段

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

  1. 客户端发起 HTTP 请求,并在请求头中包含特定的 WebSocket 字段以表示希望建立 WebSocket 连接。

    示例Http请求消息(部分):

    1
    2
    3
    4
    5
    6
    GET /chat HTTP/1.1
    Host: example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    Sec-WebSocket-Version: 13

    关键字段说明:

    • Upgrade: websocket:表示希望将协议升级为 WebSocket。

    • Connection: Upgrade:告知服务器此次连接希望协议升级。

    • Sec-WebSocket-Key:客户端生成的随机密钥,用于服务器生成握手应答的 Sec-WebSocket-Accept

    • Sec-WebSocket-Version:指定 WebSocket 的版本,当前标准版本是 13

  2. 服务器收到请求后,返回 101 状态码,并附带确认的握手信息。

    示例 http响应消息(部分):

    1
    2
    3
    4
    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

    关键字段说明:

    • HTTP/1.1 101 Switching Protocols :状态行,它由三个部分组成:

      1. 协议版本HTTP/1.1 表示使用的 HTTP 版本。
      2. 状态码101 是状态码,表示服务器已接受客户端的请求并正在切换协议。
      3. 状态描述Switching Protocols 是对状态码的描述,说明了服务器的响应内容。
    • Sec-WebSocket-Accept:由 Sec-WebSocket-Key 和特定算法生成的哈希值,用于确认握手的安全性。

2.2.2 数据帧传输

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

  • 文本帧(Text Frame):用于传输文本数据,通常是 UTF-8 编码的字符串。
  • 二进制帧(Binary Frame):用于传输二进制数据,如图片、音视频数据等。
  • 关闭帧(Close Frame):用于关闭连接。
  • Ping 和 Pong 帧:用于检测连接的活跃性,Ping 由客户端或服务器发送,Pong 由对方响应。

每个 WebSocket 数据帧的格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 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字段的值来确定长度。

2.2.3 保持连接与心跳

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

Ping-Pong 机制的示例流程:

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

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

  1. 双向检测:

    客户端和服务器都可以发送 Ping 帧,并期望对方发送 Pong 帧。这使得双方都能检测到连接的活跃性。

  2. 超时处理:

    如果发送 Ping 帧的一方在一定时间内没有收到 Pong 帧,可以认为连接已经断开或不活跃,并采取相应的措施(如关闭连接或重新连接)。

  3. Payload Data:

    Ping 和 Pong 帧的 Payload Data 是可选的,但通常会包含一些简单的数据(如时间戳),以便双方能够进行更详细的检测和分析。

2.2.4 关闭连接

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

  1. FIN 位: 1 bit
  2. RSV 位: 3 bits (保留位,一般用于扩展)
  3. Opcode: 4 bits (对于关闭帧,值为 0x8)
  4. MASK: 1 bit (指示消息是否被掩码,客户端消息必须掩码)
  5. Payload length: 7 bits, 7+16 bits, 或 7+64 bits (指示随后的有效负载的长度)
  6. Masking key: 0或4 bytes (如果 MASK 位为 1,则使用,包含用于掩码的密钥)
  7. 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 之间的代码(一定要注意避免与标准代码冲突)。

示例关闭帧字节流:

1
| 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 连接将被正式关闭。

    • 清理资源

      关闭连接时,双方可以释放相关资源,如定时器、事件监听器等。

2.2.5 安全性

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

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

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

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

3. webSocket和Http对比

3.1 相同点和不同点

相同点

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

不同点

特性 HTTP WebSocket
连接模式 短连接 长连接
通信方式 单向(请求-响应) 双向(全双工通信)
连接建立 每次请求建立新连接,之后关闭连接 初始通过 HTTP 握手后建立持久连接
延迟 每次请求都需要重新建立连接,延迟较高 连接一旦建立后,延迟较低,可实时传输数据
数据格式 基于文本的请求/响应,通常是 JSON 或 HTML 数据帧,支持文本和二进制数据
状态保持 无状态(每个请求都是独立的) 有状态(连接保持,允许持续交互)
头信息 每个请求都带有完整的 HTTP 头信息 头信息仅在握手时使用,后续数据传输不需要
适用场景 静态网页加载、API 请求等 实时聊天、在线游戏、数据流等
安全性 依赖于 HTTPS 加密 通过 WSS 进行加密,确保数据传输安全
心跳机制 无法主动检测连接是否存活 支持 Ping/Pong 心跳机制,确保连接活跃性
  1. 连接模式
    • HTTP:每次请求都是独立的,需要重新建立连接,适合于一次性请求的场景。
    • WebSocket:连接建立后可以保持很长时间,适合需要频繁数据交换的场景。
  2. 通信方式
    • HTTP:客户端发起请求,服务器响应,适用于请求-响应模式。
    • WebSocket:客户端和服务器可以随时发送消息,适合双向通信。
  3. 延迟
    • HTTP:每次请求都需要经过建立和关闭连接的过程,导致较高的延迟。
    • WebSocket:连接建立后,数据传输几乎是即时的,延迟较低。
  4. 数据格式
    • HTTP:通常以文本格式传输,如 JSON、HTML 等。
    • WebSocket:支持更灵活的数据格式,包括二进制数据,适合多种应用场景。
  5. 状态保持
    • HTTP:每个请求都是独立的,无状态设计。
    • WebSocket:连接是持久的,可以保持状态,适合需要长时间交互的应用。
  6. 心跳机制
    • HTTP:没有内置机制检测连接的存活状态。
    • WebSocket:通过 Ping/Pong 帧来保持连接活跃性,检测连接状态。

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

3.2 应用场景

HTTP 的应用场景

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

WebSocket 的应用场景

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

因此我们就可以得到以下结论:

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

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

在Qt中使用WebSocket

附录

RESTful,通常被称为 REST(Representational State Transfer),是一种软件架构风格,用于设计网络应用程序。它基于HTTP协议,并遵循一组设计原则和约束,以实现可伸缩的、灵活的网络服务。

REST 的设计理念是:

  1. 资源 - 网络上的每个实体都是一个资源。资源可以是一个文档、一个人、一个事件等。
  2. 表现形式(Representations) - 资源的某个具体状态可以通过不同的表现形式来表示。例如,一个人的资源可以通过文本、图片或其他任何形式来表示。
  3. 状态转移(State Transfer) - 客户端通过发送请求到服务端,请求可能会改变服务端的状态(例如,创建新的资源,更新资源状态,读取资源状态等)。

RESTful 应用程序的典型特征包括:

  • 使用 HTTP方法(如GET、POST、PUT、DELETE)来操作数据。
  • 通过 URL(统一资源定位符)来表示资源的地址。
  • 使用 标准数据格式(如JSON或XML)来描述资源的表现形式。
  • 分离 客户端和服务器端状态,以便每个请求可以独立完成操作。
  • 无状态的设计,每次请求都包含必要的信息,不依赖于之前的请求(这有助于实现高可伸缩性)。
  • 超媒体作为自描述的指南,在响应中包含链接,以此作为导航的指南。

RESTful 设计原则通常用于创建现代网络上通信的 Web 服务和 API。由于其无状态性和分层架构,RESTful API 适合处理高并发请求,并且可以通过缓存和代理来改善性能和扩展性。