前端视角下关于 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.pswp.cn/diannao/94960.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/94960.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自动化一键部署 LNMP 环境

第一步&#xff1a;准备环境 & 准备脚本文件1. 你在 CentOS 7 的服务器/虚拟机里打开终端&#xff0c;确认你有 root 权限或者能用 sudo。输入下面命令确认你的系统版本&#xff1a;cat /etc/centos-release你应该看到类似&#xff1a;CentOS Linux release 7.9.2009 (Core…

react之React.cloneElement()

react提供的这个方法克隆组件的方法&#xff0c;可能我们在平常的开发中用的很少&#xff0c;主要可能是我们并不知道或者并不了解这个方法。因为我在之前react的children文章中用到过&#xff0c;所以我就进行了一系列的测试&#xff0c;发现真的非常的好用。我们同样使用一些…

学习Java的Day27

今天学习的主要内容是在IntelliJ IDEA开发环境中&#xff0c;通过部署Tomcat服务器并连接MySQL数据库&#xff0c;实现了一个完整的留言板系统。这个项目涵盖了前后端开发的全流程&#xff0c;具体包括以下关键环节&#xff1a;开发环境搭建使用IntelliJ IDEA Ultimate版&#…

【计算机网络 | 第3篇】物理媒介

文章目录物理媒介介绍与物理媒体的分类&#x1f95d;成本考量引导型传输媒体&#x1f34b;引导型传输媒体&#xff1a;双绞线&#x1f34b;‍&#x1f7e9;双绞线类别双绞线的发展历程双绞线的物理限制引导型传输媒体&#xff1a;同轴电缆&#x1f34b;‍&#x1f7e9;结构组成…

golang的切片

切片 为什么需要切片 用于元素的个数不确定&#xff0c;所以无法通过数组的形式来进行统计。此时就需要切片 切片&#xff0c;也因此可以粗略地理解为动态数组数组的长度不能用变量来确定&#xff0c;这时候切片slice也就派上用场了 切片地基本介绍 切片的英文是slice切片是数组…

在labview中实现视频播放

这里分享一个迅雷的视频播放控件APlayer&#xff0c;非常的好用。具体操作步骤如下&#xff1a; 1.下载控件: 首先下载http://aplayer.open.xunlei.com/codecs.zip&#xff0c;将codecs文件解压后打开&#xff0c;按快捷键contrlA,随后contrlc复制里面所有的文件&#xff1b;…

ubuntu 22.04 使用yaml文件 修改静态ip

前提&#xff1a; 启动服务 sudo systemctl start systemd-networkd 设置开机自启 sudo systemctl enable systemd-networkd 检查状态&#xff08;确保显示 active (running)&#xff09; sudo systemctl status systemd-networkd 若想停止&#xff1a; 停止当前运行的服务 sud…

闸机控制系统从设计到实现全解析:第 4 篇:Redis 缓存与分布式锁实现

第 4 篇&#xff1a;Redis 缓存与分布式锁实现 一、Redis 在系统中的核心作用票证信息缓存&#xff1a;将高频访问的票证数据&#xff08;如状态、有效期&#xff09;缓存至 Redis&#xff0c;减少数据库查询&#xff0c;提升验证响应速度。分布式锁&#xff1a;在高并发场景下…

北京天津唐山廊坊沧州打捞日记

北京天津唐山廊坊沧州打捞日记 打捞搜蚯蚓疏通 北京&#xff1a;护城河畔的情谊打捞 清晨&#xff0c;北京的护城河在朝阳的映照下泛着微光。我接到一位年轻小伙的电话&#xff0c;声音中满是焦急。原来&#xff0c;他与女友在河边约会时&#xff0c;不小心将女友送他的定情玉佩…

全志刷机工具:PhoenixSuit-全志芯片处理器-刷机工具安装包及最详细使用教程指南

全志刷机工具&#xff1a;PhoenixSuit-全志芯片处理器刷机工具安装包及最详细使用教程指南&#xff0c;此文章主要是分享机顶盒、电视盒子&#xff0c;全志芯片盒子&#xff08;其中包含全志处理器、全志芯片、全志CPU等等&#xff09;的刷机工具、刷机工具安装教程以及如何使用…

浅谈 VM 桥接模式:让虚拟机像真实电脑一样接入网络

在虚拟化环境中&#xff0c;虚拟机&#xff08;Virtual Machine, VM&#xff09;与外部网络之间的通信方式有多种&#xff0c;比如 NAT 模式、Host-Only 模式、桥接模式&#xff08;Bridged Networking&#xff09; 等。其中&#xff0c;桥接模式是最接近“真实物理机”网络行为…

计算机视觉(1)-图像采集设备选型全景表(工业 + 医疗 + 车载)

图像采集设备选型全景表&#xff08;工业 医疗 车载&#xff09;一份面向工程师的“场景—设备—协议”速查表1 工业 & 医疗 & 通用场景应用场景主流设备形态接口 / 协议典型性能突出优势致命短板动态范围工业检测AOI / 量测 / 缺陷工业相机 采集卡Camera Link HSCo…

计算机视觉(3)深度学习模型部署平台技术选型与全栈实践指南

一、部署平台概述与分类 深度学习模型部署平台的分类需兼顾技术特性与应用场景的适配性&#xff0c;基于“技术定位-场景适配”双维度分类法&#xff0c;可将其划分为通用开源框架、云厂商服务及专用边缘工具三大类&#xff0c;各类别在设计目标、核心能力与场景覆盖上呈现显著…

Scratch编程:枪战游戏(附源码)

&#x1f3ae; 操作说明 W / A / S / D 或 方向键&#xff1a;移动 C&#xff1a;滑铲 空格键&#xff1a;取消滑铲 鼠标点击&#xff1a;开火 数字键 1 / 2 / 3 / 4&#xff1a;切换武器 G&#xff1a;快速使用道具 F&#xff1a;近战攻击 Q&#xff1a;瞄准 / 使用技能…

应急响应复现

一、前言&#xff1a;当企业发生黑客入侵、系统崩溃或其它影响业务正常运行的安全事件时&#xff0c;急需第一时间进行处理&#xff0c;使企业的网络信息系统在最短时间内恢复正常工作&#xff0c;进一步查找入侵来源&#xff0c;还原入侵事故过程&#xff0c;同时给出解决方案…

分布式事务Seata TCC模式篇

介绍 ​ 官网: https://seata.apache.org/zh-cn/docs/user/mode/tcc ​ 回顾Seata AT 模式基于 支持本地 ACID 事务 的 关系型数据库&#xff0c;如下&#xff1a; 一阶段 prepare 行为&#xff1a;在本地事务中&#xff0c;一并提交业务数据更新和相应回滚日志记录。二阶段 c…

Day37--动态规划--52. 携带研究材料(卡码网),518. 零钱兑换 II,377. 组合总和 Ⅳ,57. 爬楼梯(卡码网)

Day37–动态规划–52. 携带研究材料&#xff08;卡码网&#xff09;&#xff0c;518. 零钱兑换 II&#xff0c;377. 组合总和 Ⅳ&#xff0c;57. 爬楼梯&#xff08;卡码网&#xff09; 本文全部都是 ” 完全背包 “ 问题&#xff0c;从零到入坑&#xff0c;从入坑到爬出来。 本…

Linux文件操作

Linux文件Linux下的文件类型b 块设备文件---->存储类设备&#xff08;硬盘&#xff09;c 字符设备文件--->输入输出设备d 目录文件--->文件夹- 普通文件--> xxx.c xxx.h xxx.txt xxx.jpg xxx.mp4 a.outl 软链接文件-->快捷方式s 套接字文件-->网络通信p 管道…

Linux epoll 触发模式详解:LT vs ET

两种核心触发模式 1. 水平触发 (Level-Triggered, LT) 工作方式: 当文件描述符处于就绪状态时,epoll 会持续通知 只要状态未改变,每次调用 epoll_wait 都会返回该描述符 特点: c // 内核处理逻辑 (ep_send_events_proc) if (!(epi->event.events & EPOLLET)) { /…

STM32学习笔记6-TIM-2输出比较功能

第二部分&#xff0c;定时器的输出比较功能OC&#xff08;Output Compare&#xff09;输出比较输出比较可以通过比较CNT与CCR寄存器值的关系&#xff0c;来对输出电平进行置1、置0或翻转的操作&#xff0c;用于输出一定频率和占空比的PWM波形每个高级定时器和通用定时器都拥有4…