WebSocket 客户端开发:浏览器实战

在前两篇文章中,我们深入探讨了 WebSocket 的基础原理和服务端开发。今天,让我们把目光转向客户端,看看如何在浏览器中构建强大的 WebSocket 客户端。我曾在一个实时协作项目中,通过优化 WebSocket 客户端的重连机制和消息队列,使得用户即使在网络不稳定的情况下也能保持良好的体验。

基础架构设计

一个可靠的 WebSocket 客户端需要考虑以下几个关键点:

  1. 连接管理
  2. 消息处理
  3. 重连机制
  4. 心跳检测
  5. 错误处理

让我们从基础架构开始:

// websocket-client.js
class WebSocketClient {
  constructor(url, options = {}) {
    this.url = url
    this.options = {
      reconnectInterval: 1000,
      maxReconnectAttempts: 5,
      heartbeatInterval: 30000,
      ...options
    }

    this.handlers = new Map()
    this.messageQueue = []
    this.reconnectAttempts = 0
    this.isConnecting = false
    this.heartbeatTimer = null

    this.connect()
  }

  // 建立连接
  connect() {
    if (this.isConnecting) return

    this.isConnecting = true

    try {
      this.ws = new WebSocket(this.url)
      this.setupEventListeners()
    } catch (error) {
      console.error('Connection error:', error)
      this.handleReconnect()
    }
  }

  // 设置事件监听
  setupEventListeners() {
    this.ws.onopen = () => {
      console.log('Connected to WebSocket server')
      this.isConnecting = false
      this.reconnectAttempts = 0

      // 启动心跳
      this.startHeartbeat()

      // 处理队列中的消息
      this.processMessageQueue()

      // 触发连接事件
      this.emit('connect')
    }

    this.ws.onclose = () => {
      console.log('Disconnected from WebSocket server')
      this.cleanup()
      this.handleReconnect()

      // 触发断开事件
      this.emit('disconnect')
    }

    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error)
      this.emit('error', error)
    }

    this.ws.onmessage = (event) => {
      try {
        const message = JSON.parse(event.data)
        this.handleMessage(message)
      } catch (error) {
        console.error('Message parsing error:', error)
      }
    }
  }

  // 发送消息
  send(type, data) {
    const message = {
      type,
      data,
      id: this.generateMessageId(),
      timestamp: Date.now()
    }

    if (this.isConnected()) {
      this.sendMessage(message)
    } else {
      // 添加到消息队列
      this.messageQueue.push(message)
    }

    return message.id
  }

  // 实际发送消息
  sendMessage(message) {
    try {
      this.ws.send(JSON.stringify(message))
      this.emit('sent', message)
    } catch (error) {
      console.error('Send error:', error)
      // 添加到重试队列
      this.messageQueue.push(message)
    }
  }

  // 处理消息队列
  processMessageQueue() {
    while (this.messageQueue.length > 0) {
      const message = this.messageQueue.shift()
      this.sendMessage(message)
    }
  }

  // 处理收到的消息
  handleMessage(message) {
    // 处理心跳响应
    if (message.type === 'pong') {
      this.handleHeartbeatResponse()
      return
    }

    // 触发消息事件
    this.emit('message', message)

    // 调用特定类型的处理器
    const handler = this.handlers.get(message.type)
    if (handler) {
      handler(message.data)
    }
  }

  // 注册消息处理器
  on(type, handler) {
    this.handlers.set(type, handler)
  }

  // 移除消息处理器
  off(type) {
    this.handlers.delete(type)
  }

  // 触发事件
  emit(event, data) {
    const handler = this.handlers.get(event)
    if (handler) {
      handler(data)
    }
  }

  // 处理重连
  handleReconnect() {
    if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
      console.log('Max reconnection attempts reached')
      this.emit('reconnect_failed')
      return
    }

    this.reconnectAttempts++
    const delay = this.calculateReconnectDelay()

    console.log(`Reconnecting in ${delay}ms... (attempt ${this.reconnectAttempts})`)

    setTimeout(() => {
      this.connect()
    }, delay)

    this.emit('reconnecting', {
      attempt: this.reconnectAttempts,
      delay
    })
  }

  // 计算重连延迟
  calculateReconnectDelay() {
    // 使用指数退避算法
    return Math.min(
      1000 * Math.pow(2, this.reconnectAttempts),
      30000
    )
  }

  // 启动心跳
  startHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer)
    }

    this.heartbeatTimer = setInterval(() => {
      this.sendHeartbeat()
    }, this.options.heartbeatInterval)
  }

  // 发送心跳
  sendHeartbeat() {
    if (this.isConnected()) {
      this.send('ping', {
        timestamp: Date.now()
      })
    }
  }

  // 处理心跳响应
  handleHeartbeatResponse() {
    // 可以在这里添加心跳延迟统计等逻辑
  }

  // 清理资源
  cleanup() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer)
      this.heartbeatTimer = null
    }
  }

  // 检查连接状态
  isConnected() {
    return this.ws && this.ws.readyState === WebSocket.OPEN
  }

  // 生成消息ID
  generateMessageId() {
    return Math.random().toString(36).substr(2, 9)
  }

  // 关闭连接
  close() {
    if (this.ws) {
      this.ws.close()
    }
    this.cleanup()
  }
}

状态管理

实现可靠的状态管理机制:

// state-manager.js
class StateManager {
  constructor() {
    this.state = {
      connected: false,
      reconnecting: false,
      messageCount: 0,
      lastMessageTime: null,
      errors: [],
      latency: 0
    }

    this.listeners = new Set()
  }

  // 更新状态
  update(changes) {
    const oldState = { ...this.state }
    this.state = {
      ...this.state,
      ...changes
    }

    this.notifyListeners(oldState)
  }

  // 获取状态
  get() {
    return { ...this.state }
  }

  // 添加监听器
  addListener(listener) {
    this.listeners.add(listener)
  }

  // 移除监听器
  removeListener(listener) {
    this.listeners.delete(listener)
  }

  // 通知监听器
  notifyListeners(oldState) {
    this.listeners.forEach(listener => {
      listener(this.state, oldState)
    })
  }

  // 重置状态
  reset() {
    this.update({
      connected: false,
      reconnecting: false,
      messageCount: 0,
      lastMessageTime: null,
      errors: [],
      latency: 0
    })
  }
}

消息队列管理

实现可靠的消息队列管理:

// message-queue.js
class MessageQueue {
  constructor(options = {}) {
    this.options = {
      maxSize: 1000,
      retryLimit: 3,
      retryDelay: 1000,
      ...options
    }

    this.queue = []
    this.processing = false
  }

  // 添加消息
  add(message) {
    if (this.queue.length >= this.options.maxSize) {
      this.handleQueueOverflow()
    }

    this.queue.push({
      message,
      attempts: 0,
      timestamp: Date.now()
    })

    this.process()
  }

  // 处理队列
  async process() {
    if (this.processing) return

    this.processing = true

    while (this.queue.length > 0) {
      const item = this.queue[0]

      try {
        await this.sendMessage(item.message)
        this.queue.shift()
      } catch (error) {
        if (item.attempts >= this.options.retryLimit) {
          this.queue.shift()
          this.handleFailedMessage(item)
        } else {
          item.attempts++
          await this.wait(this.options.retryDelay)
        }
      }
    }

    this.processing = false
  }

  // 发送消息
  async sendMessage(message) {
    // 实现具体的发送逻辑
    return new Promise((resolve, reject) => {
      // 模拟发送
      if (Math.random() > 0.5) {
        resolve()
      } else {
        reject(new Error('Send failed'))
      }
    })
  }

  // 处理队列溢出
  handleQueueOverflow() {
    // 可以选择丢弃最旧的消息
    this.queue.shift()
  }

  // 处理失败的消息
  handleFailedMessage(item) {
    console.error('Message failed after max retries:', item)
  }

  // 等待指定时间
  wait(ms) {
    return new Promise(resolve => setTimeout(resolve, ms))
  }

  // 清空队列
  clear() {
    this.queue = []
    this.processing = false
  }

  // 获取队列状态
  getStatus() {
    return {
      size: this.queue.length,
      processing: this.processing
    }
  }
}

使用示例

让我们看看如何使用这个 WebSocket 客户端:

// 创建客户端实例
const client = new WebSocketClient('ws://localhost:8080', {
  reconnectInterval: 2000,
  maxReconnectAttempts: 10,
  heartbeatInterval: 20000
})

// 状态管理
const stateManager = new StateManager()

// 消息队列
const messageQueue = new MessageQueue({
  maxSize: 500,
  retryLimit: 5
})

// 注册事件处理器
client.on('connect', () => {
  stateManager.update({ connected: true })
  console.log('Connected!')
})

client.on('disconnect', () => {
  stateManager.update({ connected: false })
  console.log('Disconnected!')
})

client.on('message', (message) => {
  stateManager.update({
    messageCount: stateManager.get().messageCount + 1,
    lastMessageTime: Date.now()
  })

  console.log('Received:', message)
})

client.on('error', (error) => {
  const errors = [...stateManager.get().errors, error]
  stateManager.update({ errors })

  console.error('Error:', error)
})

// 监听状态变化
stateManager.addListener((newState, oldState) => {
  // 更新UI或触发其他操作
  updateUI(newState)
})

// 发送消息
function sendMessage(type, data) {
  // 添加到消息队列
  messageQueue.add({
    type,
    data
  })

  // 通过WebSocket发送
  client.send(type, data)
}

// 更新UI
function updateUI(state) {
  // 实现UI更新逻辑
  console.log('State updated:', state)
}

// 使用示例
sendMessage('chat', {
  text: 'Hello, WebSocket!',
  user: 'Alice'
})

写在最后

通过这篇文章,我们详细探讨了如何在浏览器中构建可靠的 WebSocket 客户端。从基础架构到状态管理,从消息队列到错误处理,我们不仅关注了功能实现,更注重了实际应用中的各种挑战。

记住,一个优秀的 WebSocket 客户端需要在用户体验和性能之间找到平衡。在实际开发中,我们要根据具体需求选择合适的实现方案,确保客户端能够稳定高效地运行。

如果觉得这篇文章对你有帮助,别忘了点个赞 👍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/949199.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python爬虫基础——认识网页结构(各种标签的使用)

1、添加<div>标签的代码定义了两个区块的宽度和高度均为100px&#xff0c;边框的格式也相同&#xff0c;只是区块中显示的内容不同&#xff1b; 2、添加<ul>和<ol>标签分别用于定义无序列表和有序列表。<il>标签位于<ul>标签或<ol>标签之…

基于W2605C语音识别合成芯片的智能语音交互闹钟方案-AI对话享受智能生活

随着科技的飞速发展&#xff0c;智能家居产品正逐步渗透到我们的日常生活中&#xff0c;其中智能闹钟作为时间管理的得力助手&#xff0c;也在不断进化。基于W2605C语音识别与语音合成芯片的智能语音交互闹钟&#xff0c;凭借其强大的联网能力、自动校时功能、实时天气获取、以…

Python提取目标Json键值:包含子嵌套列表和字典

目标&#xff1a;取json中所有的Name、Age字典 思路&#xff1a;递归处理字典中直接包含子字典的情况&#xff0c; import jsondef find_targ_dicts(data,key1,key2):result {}if isinstance(data, dict):if key1 in data and key2 in data: # 第一层字典中包含key1和key2re…

你已经分清JAVA中JVM、JDK与JRE的作用和关系了吗?

你已经分清JAVA中JVM、JDK与JRE的作用和关系了吗&#xff1f; 一. JVM、JDK与JRE的关系二. JVM、JDK与JRE的作用2.1 什么是JVM&#xff1f;2.2 什么是JDK&#xff1f;2.3 什么是JRE&#xff1f; 前言 点个免费的赞和关注&#xff0c;有错误的地方请指出&#xff0c;看个人主页有…

深度学习blog-RAG构建高效生成式AI的优选路径

RAG&#xff08;Retrieval-Augmented Generation&#xff09; 随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;模型的性能和应用场景也不断扩展。其中&#xff0c;检索增强生成&#xff08;RAG, Retrieval-Augmented Generation&#xff09;模型作为一种新…

数据中台与数据治理服务方案[50页PPT]

本文概述了数据中台与数据治理服务方案的核心要点。数据中台作为政务服务数据化的核心&#xff0c;通过整合各部门业务系统数据&#xff0c;进行建模与加工&#xff0c;以新数据驱动政府管理效率提升与政务服务能力增强。数据治理则聚焦于解决整体架构问题&#xff0c;确保数据…

AI生成PPT,效率与创意的双重升级

AI生成PPT&#xff0c;效率与创意的双重升级&#xff01;在信息化高速发展的今天&#xff0c;我们的工作节奏被无限压缩&#xff0c;效率成为了衡量工作能力的重要指标。而制作PPT这种事&#xff0c;总是让人又爱又恨——既想做得出彩&#xff0c;又不想花费大量时间。现在有了…

【HF设计模式】05-单例模式

声明&#xff1a;仅为个人学习总结&#xff0c;还请批判性查看&#xff0c;如有不同观点&#xff0c;欢迎交流。 摘要 《Head First设计模式》第5章笔记&#xff1a;结合示例应用和代码&#xff0c;介绍单例模式&#xff0c;包括遇到的问题、采用的解决方案、以及达到的效果。…

嵌入式linux系统中QT信号与槽实现

第一:Qt中信号与槽简介 信号与槽是Qt编程的基础。因为有了信号与槽的编程机制,在Qt中处理界面各个组件的交互操作时变得更加直观和简单。 槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。 案例操作与实现: #ifndef …

php有两个数组map比较 通过id关联,number可能数量变化 比较他们之间增加修改删除

在PHP中&#xff0c;比较两个通过ID关联的数组&#xff0c;并确定它们之间的增加、修改和删除操作&#xff0c;你可以使用以下步骤&#xff1a; 创建两个数组&#xff1a;假设你有两个数组&#xff0c;分别表示“旧数据”和“新数据”。使用ID作为键&#xff1a;为了方便比较&a…

C++和OpenGL实现3D游戏编程【连载19】——着色器光照初步(平行光和光照贴图)(附源码)

1、本节要实现的内容 我们在前期的教程中,讨论了在即时渲染模式下的光照内容。但在我们后期使用着色器的核心模式下,会经常在着色器中使光照,我们这里就讨论一下着色器光照效果,以及光照贴图效果,同时这里知识会为后期的更多光照效果做一些铺垫。本节我们首先讨论冯氏光照…

《learn_the_architecture_-_generic_interrupt_controller_v3_and_v4__overview》学习笔记

1.GIC是基于Arm GIC架构实现的&#xff0c;该架构已经从GICv1发展到最新版本GICv3和GICv4。 Arm 拥有多个通用中断控制器&#xff0c;可为所有类型的 Arm Cortex 多处理器系统提供一系列中断管理解决方案。这些控制器的范围从用于具有小型 CPU 内核数的系统的最简单的 GIC-400 …

健身房管理系统多身份

本文结尾处获取源码。 本文结尾处获取源码。 本文结尾处获取源码。 一、相关技术 后端&#xff1a;Java、JavaWeb / Springboot。前端&#xff1a;Vue、HTML / CSS / Javascript 等。数据库&#xff1a;MySQL 二、相关软件&#xff08;列出的软件其一均可运行&#xff09; I…

General OCR Theory: Towards OCR-2.0 via a Unified End-to-end Model

通用 OCR 理论&#xff1a;通过统一的端到端模型实现 OCR-2.0 Abstract 随着人们对人工光学字符的智能处理需求日益增长&#xff0c;传统的OCR系统&#xff08;OCR-1.0&#xff09;已越来越不能满足人们的使用需求。本文&#xff0c;我们将所有人工光学信号&#xff08;例如纯…

大数据组件(二)快速入门数据集成平台SeaTunnel

大数据组件(二)快速入门数据集成平台SeaTunnel SeaTunnel是一个超高性能的分布式数据集成平台&#xff0c;支持实时海量数据同步。 每天可稳定高效同步数百亿数据&#xff0c;已被近百家企业应用于生产。 SeaTunnel的运行流程如下图所示&#xff1a; 工作流程为&#xff1a;So…

前端如何判断多个请求完毕

在前端开发中&#xff0c;经常会遇到需要同时发起多个异步请求&#xff0c;并在所有请求都完成后再进行下一步操作的情况。 这里有几个常用的方法来实现这一需求&#xff1a; 使用 Promise.all() Promise.all() 方法接收一个 Promise 对象的数组作为参数&#xff0c;当所有的…

【机器学习】穷理至极,观微知著:微积分的哲思之旅与算法之道

文章目录 微积分基础&#xff1a;理解变化与累积的数学前言一、多重积分的高级应用1.1 高维概率分布的期望值计算1.1.1 多维期望值的定义1.1.2 Python代码实现1.1.3 运行结果1.1.4 结果解读 1.2 特征空间的体积计算1.2.1 单位球体的体积计算1.2.2 Python代码实现1.2.3 运行结果…

基于Arduino的FPV头部追踪相机系统

构建FPV头部追踪相机&#xff1a;让你置身于遥控车辆之中&#xff01; 在遥控车辆和模型飞行器的世界中&#xff0c;第一人称视角&#xff08;FPV&#xff09;体验一直是爱好者们追求的目标。通过FPV头部追踪相机&#xff0c;你可以像坐在车辆或飞行器内部一样&#xff0c;自由…

鸿蒙HarmonyOS开发:拨打电话、短信服务、网络搜索、蜂窝数据、SIM卡管理、observer订阅管理

文章目录 一、call模块&#xff08;拨打电话&#xff09;1、使用makeCall拨打电话2、获取当前通话状态3、判断是否存在通话4、检查当前设备是否具备语音通话能力 二、sms模块&#xff08;短信服务&#xff09;1、创建短信2、发送短信 三、radio模块&#xff08;网络搜索&#x…

高校教务系统登录页面JS分析——安徽大学

高校教务系统密码加密逻辑及JS逆向 最近有粉丝说安徽大学的教务系统换了&#xff0c;之前用的是正方出品的系统&#xff0c;今天我来看看新版教务系统怎么模拟登录&#xff0c;总体来说&#xff0c;还是比较简单的&#xff0c;就是一个哈希加密了密码&#xff0c;其次就是一个滑…