当前位置: 首页 > news >正文

前端视角下关于 WebSocket 的简单理解

参考 RFC 6455: The WebSocket Protocol

WebSocket 协议基础

  • 协议本质:在单个 TCP 连接上提供全双工通信通道的协议
  • 核心优势:
    • 双向实时通信(服务器主动推送)
    • 低延迟(相比 HTTP 轮询)
    • 高效数据传输(减少 HTTP 头部开销)
  • 协议握手:
# 来自客户端的握手数据
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
- Connection:设置 Upgrade,表示客户端希望连接升级
- Upgrade:设置 websocket,表示希望升级到 Websocket 协议
- Sec-WebSocket-Key:客户端发送的一个 base64 编码的密文,用于简单的认证秘钥。要求服务端必须返回一个对应加密的 Sec-WebSocket-Accept 应答,否则客户端会抛出错误,并关闭连接
- Sec-WebSocket-Protocol:子协议选择, 标识客户端支持的协议
- Sec-WebSocket-Version :表示支持的 Websocket 版本
- Sec-WebSocket-Extensions:户端期望使用的协议级别的扩展
# 服务端的握手响应
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
- HTTP/1.1 101 Switching Protocols:表示服务端接受 WebSocket 协议的客户端连接
- Sec-WebSocket-Accept:验证客户端请求报文,同样也是为了防止误连接。具体做法是把请求头里 Sec-WebSocket-Key 的值,加上一个专用的 UUID,再计算摘要
  • 结束握手:任何一端都可以发送一个包含特定关闭握手的控制帧数据。收到此帧后,另一端在不发送任何数据后会发送一个结束帧作为响应。收到另一端的结束帧后,最开始发送控制帧的端在没有数据需要发送时,就会安全的关闭此连接。在发送了一个表明连接需要被关闭的控制帧后,这个客户端不会再发送任何的数据;在收到一个表明连接需要被关闭的控制帧后,这个客户端会丢弃此后的所有数据。

WebSocket 帧结构

在 WebSocket 协议中,数据是通过一系列数据帧来进行传输的。为了避免安全问题,客户端必须在它发送到服务器的所有帧中添加掩码(Mask),服务端收到没有添加掩码的数据帧以后,必须立即关闭连接。另外服务端禁止在发送数据帧给客户端时添加掩码,客户端如果收到了一个添加了掩码的帧,必须立即关闭连接。

      0                   1                   2                   30 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 ...                |+---------------------------------------------------------------+

基础帧头

位偏移字段名称长度详细说明
0FIN1 bit标识是否为消息的最后一帧(1=最终帧,0=还有后续帧)
1-3RSV1, RSV2, RSV3各1 bit保留位,必须为0(除非扩展协议定义特殊用途)
4-7Opcode4 bits帧类型标识: 0x0=连续帧;0x1=文本帧;0x2=二进制帧;0x8=关闭帧; 0x9=PING;0xA=PONG
8Mask1 bit是否使用掩码(客户端到服务器必须设为1)
9-15Payload Length7 bits数据长度(实际值分三种情况): 0-125:实际长度;126:后续2字节表示长度;127:后续8字节表示长度

Opcode类型表

Hex类型描述
0x0Continuation连续帧(分片消息)
0x1TextUTF-8文本数据
0x2Binary二进制数据
0x8Close连接关闭指令
0x9Ping心跳检测请求
0xAPong心跳检测响应

长度解码规则

Payload Length值后续字节数实际长度范围
0-12500-125字节
1262126-65,535字节
127865,536-2^64-1字节

控制帧特殊说明

所有的控制帧必须有一个 126 字节或者更小的负载长度,并且不能被分片

  1. Close帧(0x8):

    • 前2字节:状态码(如1000表示正常关闭)
    • 可选 UTF-8 原因短语
  2. Ping/Pong 帧(0x9/0xA):

    • 必须实现心跳应答机制
    • Pong 的 Payload 需与对应 Ping 一致

示例帧解析

Hello 文本帧原始字节(Hex):

81 85 37 FA 21 3D 7F 9F 4D 51 58

解析结果:

  • 81 → FIN=1, Opcode=0x1(文本帧)
  • 85 → Mask=1, Payload Length=5
  • 37 FA 21 3D → 掩码密钥
  • 7F 9F 4D 51 58 → 加密后的 “Hello”

前端 WebSocket 高可用框架设计

暂时没有设计日志和统计系统,项目地址 websocket-pro-client

组件关系图

WebSocketManager
WebSocketClient
TaskScheduler
EventEmitter
Heartbeat
PriorityQueue
  • WebSocketManager:入口类,管理所有连接实例和共享资源
  • WebSocketClient:单个连接实例,处理连接生命周期和消息收发
  • Heartbeat:心跳检测管理,维护连接活性
  • TaskScheduler:任务调度器,控制并发消息发送
  • PriorityQueue:优先级队列,确保高优先级消息优先处理
  • EventEmitter:事件中心,统一处理所有连接事件

分层架构设计

外部服务
网络层
核心层
用户层
事件订阅
API调用
WebSocket Server
Browser WebSocket
Connection Pool
WebSocketClient1
WebSocketClient2
Heartbeat
Task Scheduler
PriorityQueue
EventEmitter
UI Components
WebSocketManager

架构说明

  1. 用户层

    • UI Components:业务组件,通过标准API与核心层交互
    • 事件流:通过EventEmitter实现松耦合通信
  2. 核心层

    • WebSocketManager:单例入口
    • Connection Pool:连接池维护策略:
      • 最大连接数限制(默认5个)
      • LRU(最近最少使用)淘汰机制
      • 相同URL自动复用连接
  3. 网络层

    • 封装原生WebSocket API,增加:
      • 自动重连装饰器
      • 二进制数据分片处理
      • CORS安全校验

数据流转过程

用户界面WebSocketManagerWebSocketClient网络层TaskSchedulerconnect('wss://api.example.com')创建新连接初始化WebSocketonopen启动心跳检测连接状态更新'connected'事件发送PING返回PONGloop[心跳检测]send(data, priority)添加发送任务有序发送数据onerror触发重连流程alt[网络异常]用户界面WebSocketManagerWebSocketClient网络层TaskScheduler

详细设计说明

连接管理

连接状态机:

connect()
onopen
onerror/onclose
close()
onclose
onerror/onclose
disconnected
connecting
connected
closing

连接池实现:

class WebSocketManager {private connectionPool: Map<string, WebSocketClient>;// 获取或创建连接public connect(url: string): WebSocketClient {if (this.connectionPool.has(url)) {return this.connectionPool.get(url)!; // 复用现有连接}const client = new WebSocketClient(url, this.config);this.connectionPool.set(url, client);return client;}// 关闭所有连接public closeAll(): void {this.connectionPool.forEach(client => client.close());}
}

连接池 vs 单连接

  • 优点:避免重复握手开销,支持多租户隔离
  • 缺点:增加内存占用,需要维护状态一致性

错误处理体系

错误类型处理方式
连接错误自动触发重连机制,累计重试次数
心跳超时主动关闭连接并标记为异常断开,触发快速重连
消息发送失败根据优先级存入队列,连接恢复后自动重发
协议错误关闭连接并触发error事件,不自动重连

错误捕获示例

// 网络层错误捕获
socket.addEventListener('error', (event) => {this.status = 'disconnected';this.emit('error', {type: 'network',error: event,willReconnect: this.reconnectAttempts < this.config.maxReconnectAttempts});this.scheduleReconnect();
});// 应用层错误处理
public send(data: any): Promise<void> {return new Promise((resolve, reject) => {if (this.status !== 'connected') {reject(new Error('Connection not ready'));return;}try {this.socket.send(data);resolve();} catch (error) {this.emit('error', {type: 'send',error,data});reject(error);}});
}

智能重连机制

重连算法流程:

ClientManager连接断开计算退避时间尝试重连连接恢复增加重试计数重新计算等待时间alt[成功][失败]loop[重试逻辑]ClientManager

核心代码:

private scheduleReconnect(): void {// 1. 检查重试上限if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {this.emit('reconnect_failed', {attempts: this.reconnectAttempts,maxAttempts: this.config.maxReconnectAttempts});return;}// 2. 指数退避计算const baseDelay = this.config.reconnectDelay;const exponent = Math.pow(this.config.reconnectExponent, this.reconnectAttempts);const cappedDelay = Math.min(baseDelay * exponent,this.config.maxReconnectDelay);// 3. 添加随机抖动(避免集群同时重连的"惊群效应")const jitterRatio = 0.2; // ±20%的随机波动const jitter = cappedDelay * jitterRatio * (Math.random() * 2 - 1); // [-0.2,0.2]范围const actualDelay = Math.max(1000, cappedDelay + jitter); // 保证至少1秒// 4. 设置定时器this.reconnectTimer = setTimeout(() => {this.reconnectAttempts++;this.emit('reconnect', {attempt: this.reconnectAttempts,nextDelay: actualDelay});// 5. 实际重连操作this.connect()}, actualDelay);
}

指数退避公式
每次重试间隔 = min(初始延迟 * (退避系数^重试次数), 最大延迟)

# 初始值
initialDelay = 1000ms, 
exponent = 1.5, 
maxDelay = 30000ms# 重试间隔增长示例:
第1次: 1000ms
第2次: 1500ms (1000*1.5^1)
第3次: 2250ms (1000*1.5^2)
...
第10次: 30000ms (达到上限)

另外可添加服务端过载保护:

if (this.reconnectAttempts > 3) {// 随机跳过1次重试if (Math.random() < 0.3) {this.reconnectAttempts++;this.scheduleReconnect();return;}
}

心跳检测系统

ClientServerPING收到PING后立即响应PONG启动超时计时器重置计时器标记连接异常主动关闭alt[正常响应][超时未响应]ClientServer

核心代码:

class Heartbeat {private lastPong: number = 0;public start(): void {this.intervalId = setInterval(() => {if (this.socket.readyState === WebSocket.OPEN) {this.socket.send('ping');this.timeoutId = setTimeout(() => {this.onTimeout(); // 心跳超时处理}, this.timeout);}}, this.interval);}public recordPong(): void {this.lastPong = Date.now();clearTimeout(this.timeoutId);this.emit('latency', Date.now() - this.lastPong);}
}

消息调度系统

优先级队列设计:

优先级消息类型默认权重
0系统控制消息(如心跳)最高
1用户关键操作
2普通数据更新
3批量日志/非实时数据

优先级调度策略:

  • 严格优先级,适用于金融交易系统等
  • 加权轮询,适用于物联网数据采集等
  • 动态调整,适用于视频流传输等

调度器核心代码:

interface Task {task: () => Promise<void>;priority: number;
}class PriorityQueue {private items: Task[] = [];public enqueue(task: Task): void {let added = false;for (let i = 0; i < this.items.length; i++) {if (task.priority > this.items[i].priority) {this.items.splice(i, 0, task);added = true;break;}}if (!added) {this.items.push(task);}}public dequeue(): Task | undefined {return this.items.shift();}public get length(): number {return this.items.length;}
}
class TaskScheduler {public addTask(task: () => Promise<void>, priority: number): Promise<void> {return new Promise((resolve, reject) => {this.queue.enqueue({task: async () => {try {await task();resolve();} catch (error) {reject(error);}},priority});this.run();});}private run(): void {while (this.runningTasks < this.maxConcurrent && this.queue.length > 0) {const { task } = this.queue.dequeue()!;this.runningTasks++;task().finally(() => {this.runningTasks--;this.run(); // 递归执行下一个任务});}}
}

性能优化策略

  • 使用ArrayBuffer传输图像数据
  • 消息压缩
  • 带宽自适应(自动适应从2G到5G的网络环境,基于网络类型调整策略,如心跳间隔,最大连接数等)

总结

WebSocket 是一种网络传输协议,位于 OSI 模型的应用层。可在单个 TCP 连接上进行全双工通信,能更好的节省服务器资源和带宽并达到实时通迅。客户端和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。

特点

  • 全双工,通信允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合
  • 二进制帧,采用了二进制帧结构,语法、语义与 HTTP 完全不兼容,相比 http/2,WebSocket 更侧重于“实时通信”,而 http/2 更侧重于提高传输效率,所以两者的帧结构也有很大的区别。不像 http/2 那样定义流,也就不存在多路复用、优先级等特性,自身就是全双工,也不需要服务器推送
  • 协议名,引入 wswss 分别代表明文和密文的 WebSocket 协议,且默认端口使用 80 或 443,几乎与 http 一致
  • 握手,WebSocket 也要有一个握手过程,然后才能正式收发数据。

优点

  • 较少的控制开销:数据包头部协议较小,不同于 http 每次请求需要携带完整的头部
  • 更强的实时性:相对于 http 请求需要等待客户端发起请求服务端才能响应,延迟明显更少
  • 保持创连接状态:创建通信后,可省略状态信息,不同于 http 每次请求需要携带身份验证
  • 更好的二进制支持:定义了二进制帧,更好处理二进制内容
  • 支持扩展:用户可以扩展 WebSocket 协议、实现部分自定义的子协议
  • 更好的压缩效果:WebSocket 在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率
http://www.dtcms.com/a/322299.html

相关文章:

  • 华为实验-VLAN基础
  • Kafka学习记录
  • UE蓝图节点Add Impulse和Add Torque in Radians
  • 面向软件定义汽车的确定性以太网网络解决方案
  • ARMv8 MMU页表格式及地址转换过程分析
  • [CUDA] CUTLASS | C++ GEMM内核--高度模板化的类
  • 快速使用selenium+java案例
  • 系统开发 Day1
  • PyLS简介
  • NumPy性能飞跃秘籍:向量化计算如何提升400倍运算效率?
  • 【C++详解】AVL树深度剖析与模拟实现(单旋、双旋、平衡因⼦更新、平衡检测)
  • 云服务器--阿里云OSS(2)【Springboot使用阿里云OSS】
  • Datawhale AI夏令营-记录2
  • Kotlin初体验
  • 【linux基础】Linux 文本处理核心命令指南
  • 代码随想录day59图论9
  • NY151NY152美光固态闪存NY153NY154
  • 利用whisper api实现若无字幕则自动下载音频并用 whisper 转写,再用 LLM 总结。
  • JVM相关(AI回答)
  • 等保测评-RabbitMQ中间件
  • 【Java EE初阶 --- 网络原理】JVM
  • 从零玩转Linux云主机:免费申请、连接终端、命令速查表
  • 分析报告:基于字节连续匹配技术的KV缓存共享实施可能性及其扩展
  • ✨ 基于 JsonSerialize 实现接口返回数据的智能枚举转换(优雅告别前端硬编码!)
  • 【Linux】Socket编程——UDP版
  • (nice!!!)(LeetCode 面试经典 150 题) 146. LRU 缓存 (哈希表+双向链表)
  • Go语言实战案例:文件上传服务
  • 香橙派 RK3588 部署千问大模型 Qwen2-VL-2B 推理视频
  • 在Docker中下载RabbitMQ(详细讲解参数)
  • BGP 笔记