首页 > 其他分享 >WebSocket 心得分享 转载

WebSocket 心得分享 转载

时间:2024-12-30 16:41:17浏览次数:1  
标签:转载 WebSocket socket null private 重连 心得 连接

一、前言

本文将介绍 WebSocket 的封装,比如:心跳机制,重连和一些问题如何去处理

二、背景

之前,钱包相关的查询,我们是使用的轮询方案来做的,后来更新了一次需求,需要做一些实时数据统计的更新,然后顺带给钱包的余额也用长连接来做了,好,那么故事就开始了...

某天,

「老板:」 我钱怎么没了,但是我这里查账户还有。

「我的内心:」 恩?这玩意难道说... 后端没返?

和后端沟通以后,感觉是返回了的,被挤账号了?排查了一段时间以后,最终我将问题锁定在手机息屏的操作上。

因为我们是一个 「H5」 的项目,APP 是嵌套在 webview 中,所以不能操作原生的事件来处理,只能将方案控制在浏览器提供的事件来处理。

好了,接下来各位可以看我是如何处理这个问题,如果没有搞过也是可以有不少收获,也欢迎大神评论区交流其他方案。

三、WebSocket

3.1 什么是 WebSocket ?为什么使用他?

以下是百度百科中对 「WebSocket」 的定义:

WebSocket 是一种在单个 TCP 连接上进行 全双工 通信的协议。WebSocket  通信协议于2011年被 IETF 定为标准 RFC 6455,并由 RFC7936 补充规范。WebSocket API 也被 W3C 定为标准。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

「WebSocket 的关键特点」

  1. 「双向通信(Full Duplex)」

    • 客户端和服务器都可以主动发送数据,而不是像 HTTP 一样只能由客户端发起请求。
  2. 「实时性」

    • 消息可以实时传递,延迟更低,适合需要实时更新的场景。
  3. 「持久化连接」

    • 使用单个 TCP 连接完成多次数据交互,无需为每次通信重新建立连接。
  4. 「轻量级协议」

    • WebSocket 头部信息非常小,比传统 HTTP 请求的头部要轻量。
  5. 「节约资源」

    • 长连接减少了资源消耗,特别是在频繁通信的场景中。

上述中,是 AI 给我们总结的 WebSocket 的特点,接下来我们要知道我们为什么使用他,HTTP 他能不能做,他的局限性又在哪里?

「传统 HTTP 的局限性:」

  1. HTTP 是基于请求-响应模型的,客户端必须发起请求,服务器才能返回数据。
  2. 如果需要实时更新(如股票价格、在线聊天),通常需要使用轮询(Polling)或长轮询(Long Polling),这会导致:
    • 高资源消耗(频繁的连接建立和断开)。
    • 高网络流量(每次请求都包含冗长的 HTTP 头部信息)。
    • 更高的延迟(数据可能需要等待较长时间才能返回)。

其实 HTTP 是可以实现的,如果 HTTP 请求频繁三次握手和四次挥手的操作会占用大量资源,HTTP/1.1 以后开启了 「Keep-Alive (长连接)」,可以复用连接,但是实时的情况下,响应模型仍然会导致较高的延迟和资源消耗。

相比之下,WebSocket 通过一次握手建立连接以后,就可以保持双向通信,服务器可以主动推送数据,无需客户端轮询。解决了 HTTP 带来的一些痛点。

四、封装 WebSocket

我们将实现以下几个功能点:

  • 「重连」
  • 「心跳机制」
  • 「事件回调」
  • 「连接状态管理」
  • 「销毁」

4.1 Javascript 版本

class ReSocket {
  constructor(url, options = {}) {
    this.url = url; // WebSocket 服务器地址
    this.options = options; // 可选参数
    this.socket = null; // WebSocket 实例
    this.maxReconnectTimes = options.maxReconnectTimes || 5; // 最大重连次数
    this.reconnectTimes = 0; // 当前重连次数
    this.reconnectInterval = options.reconnectInterval || 3000; // 重连间隔时间(毫秒)
    this.isClosed = false; // 是否已关闭
    this.isOpen = false; // 是否已打开
    this.isConnect = false; // 是否已连接
    this.isReconnecting = false; // 是否正在重连
    this.isDestroyed = false; // 是否已销毁
    this.reconnectTimer = null; // 重连定时器
    this.heartbeatTimer = null; // 心跳定时器
    this.heartbeatInterval = options.heartbeatInterval || 30000; // 心跳间隔时间(默认30秒)
    this.heartbeatData = options.heartbeatData || "ping"; // 心跳数据
    this.onMessageCallback = null; // 消息接收回调
    this.onOpenCallback = null; // 连接成功回调
    this.onCloseCallback = null; // 连接关闭回调
  }


  
  // 创建WebSocket实例
  createSocket() {
    this.socket = new WebSocket(this.url);

    this.socket.onopen = () => {
      this.isOpen = true;
      this.isConnect = true;
      this.reconnectTimes = 0; // 重连次数归零
      this.startHeartbeat(); // 启动心跳机制
      if (this.onOpenCallback) this.onOpenCallback(); // 调用连接成功回调
    };

    this.socket.onmessage = event => {
      if (this.onMessageCallback) this.onMessageCallback(event.data); // 调用消息接收回调
    };

    this.socket.onclose = () => {
      this.isOpen = false;
      this.isConnect = false;
      this.stopHeartbeat(); // 停止心跳机制
      if (this.onCloseCallback) this.onCloseCallback(); // 调用连接关闭回调
      if (!this.isClosed && this.reconnectTimes < this.maxReconnectTimes) {
        this.reconnect(); // 尝试重连
      }
    };

    this.socket.onerror = error => {
      console.error("WebSocket 错误: ", error); // 错误处理
    };
  }

  // 开始连接
  connect() {
    if (this.isDestroyed) return; // 如果已销毁,则不再连接
    this.createSocket(); // 创建WebSocket实例
  }

  // 重连
  reconnect() {
    if (this.isReconnecting || this.reconnectTimes >= this.maxReconnectTimes)
      return; // 防止重复重连

    this.isReconnecting = true;
    this.reconnectTimes++; // 增加重连次数

    this.reconnectTimer = setTimeout(() => {
      console.log(`正在重连... (${this.reconnectTimes})`); // 打印重连次数
      this.createSocket(); // 再次创建WebSocket实例
      this.isReconnecting = false; // 重连状态设置为false
    }, this.reconnectInterval); // 按设定时间重连
  }

  // 发送消息
  send(data) {
    if (this.isOpen) {
      this.socket.send(data); // 发送数据
    } else {
      console.error("WebSocket 未打开,无法发送消息。"); // 提示错误
    }
  }

  // 设置消息接收回调
  onMessage(callback) {
    this.onMessageCallback = callback; // 绑定接收消息的回调
  }

  // 设置连接成功回调
  onOpen(callback) {
    this.onOpenCallback = callback; // 绑定连接成功的回调
  }

  // 设置连接关闭回调
  onClose(callback) {
    this.onCloseCallback = callback; // 绑定连接关闭的回调
  }

  // 启动心跳机制
  startHeartbeat() {
    this.heartbeatTimer = setInterval(() => {
      if (this.isOpen) {
        this.send(this.heartbeatData); // 发送心跳数据
      }
    }, this.heartbeatInterval); // 按设定的时间间隔发送
  }

  // 停止心跳机制
  stopHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer); // 清除心跳定时器
      this.heartbeatTimer = null;
    }
  }

  // 关闭连接
  close() {
    this.isClosed = true; // 设置为已关闭
    this.isOpen = false;
    this.socket.close(); // 关闭WebSocket连接
    this.stopHeartbeat(); // 停止心跳机制
    clearTimeout(this.reconnectTimer); // 清除重连定时器
  }

  // 销毁实例
  destroy() {
    this.isDestroyed = true; // 设置为已销毁
    this.close(); // 关闭连接
  }
}

4.2 Typescript 版本

type ReSocketOptions = {
  maxReconnectTimes?: number; // 最大重连次数
  reconnectInterval?: number; // 重连间隔时间(毫秒)
  heartbeatInterval?: number; // 心跳间隔时间(毫秒)
  heartbeatData?: string; // 心跳数据
};

class ReSocket {
  private url: string;
  private socket: WebSocket | null = null;
  private maxReconnectTimes: number;
  private reconnectTimes: number = 0;
  private reconnectInterval: number;
  private isClosed: boolean = false;
  private isOpen: boolean = false;
  private isConnect: boolean = false;
  private isReconnecting: boolean = false;
  private isDestroyed: boolean = false;
  private reconnectTimer: NodeJS.Timeout | null = null;
  private heartbeatTimer: NodeJS.Timeout | null = null;
  private heartbeatInterval: number;
  private heartbeatData: string;
  private onMessageCallback: ((message: string) => void) | null = null;
  private onOpenCallback: (() => void) | null = null;
  private onCloseCallback: (() => void) | null = null;

  constructor(url: string, options: ReSocketOptions = {}) {
    this.url = url;
    this.maxReconnectTimes = options.maxReconnectTimes || 5;
    this.reconnectInterval = options.reconnectInterval || 3000;
    this.heartbeatInterval = options.heartbeatInterval || 30000;
    this.heartbeatData = options.heartbeatData || 'ping';
  }

  private createSocket(): void {
    this.socket = new WebSocket(this.url);

    this.socket.onopen = () => {
      this.isOpen = true;
      this.isConnect = true;
      this.reconnectTimes = 0;
      this.startHeartbeat();
      if (this.onOpenCallback) this.onOpenCallback();
    };

    this.socket.onmessage = (event: MessageEvent) => {
      if (this.onMessageCallback) this.onMessageCallback(event.data);
    };

    this.socket.onclose = () => {
      this.isOpen = false;
      this.isConnect = false;
      this.stopHeartbeat();
      if (this.onCloseCallback) this.onCloseCallback();
      if (!this.isClosed && this.reconnectTimes < this.maxReconnectTimes) {
        this.reconnect();
      }
    };

    this.socket.onerror = (error: Event) => {
      console.error("WebSocket 错误: ", error);
    };
  }

  public connect(): void {
    if (this.isDestroyed) return;
    this.createSocket();
  }

  private reconnect(): void {
    if (this.isReconnecting || this.reconnectTimes >= this.maxReconnectTimes) return;

    this.isReconnecting = true;
    this.reconnectTimes++;

    this.reconnectTimer = setTimeout(() => {
      console.log(`正在重连... (${this.reconnectTimes})`);
      this.createSocket();
      this.isReconnecting = false;
    }, this.reconnectInterval);
  }

  public send(data: string): void {
    if (this.isOpen && this.socket) {
      this.socket.send(data);
    } else {
      console.error("WebSocket 未打开,无法发送消息。");
    }
  }

  public onMessage(callback: (message: string) => void): void {
    this.onMessageCallback = callback;
  }

  public onOpen(callback: () => void): void {
    this.onOpenCallback = callback;
  }

  public onClose(callback: () => void): void {
    this.onCloseCallback = callback;
  }

  private startHeartbeat(): void {
    this.heartbeatTimer = setInterval(() => {
      if (this.isOpen && this.socket) {
        this.send(this.heartbeatData);
      }
    }, this.heartbeatInterval);
  }

  private stopHeartbeat(): void {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
  }

  public close(): void {
    this.isClosed = true;
    this.isOpen = false;
    if (this.socket) {
      this.socket.close();
    }
    this.stopHeartbeat();
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
    }
  }

  public destroy(): void {
    this.isDestroyed = true;
    this.close();
  }
}

export { ReSocket };

4.3 如何使用?

首先简单写个 ws 的服务,我的 「Node」 环境是 20.18.0

创建一个 Socket 的文件夹 vscode  打开执行:

npm init -y

生成完毕 package.json 之后,我们安装 ws :

npm i ws

创建 app.js 写一个简单服务 :

const WebSocket = require("ws");

// 创建 WebSocket 服务器,监听端口 8080
const wss = new WebSocket.Server({ port: 8080 });

// 监听客户端连接
wss.on("connection", (ws) => {
  console.log("客户端已连接");

  // 监听客户端发送的消息
  ws.on("message", (message) => {
    console.log("收到客户端消息:", message);

    // 向客户端发送回复
    ws.send(`服务器回复: ${message}`);
  });

  // 发送一条欢迎消息给客户端
  ws.send("欢迎连接 WebSocket 服务器");
});

// 打印服务器地址
console.log("WebSocket 服务器已启动: ws://localhost:8080");

执行运行命令 :

node .\app.js

这里可以先用一个 WebSocket 调试工具试试是否创建成功 这里我是用的是 WebSocket在线测试工具 ,效果如下图:

图片

看到欢迎连接的时候,说明我们这个服务已经成功启动了,接下来就是 Javascript 中如何使用了,创建一个 index.html ,然后引入我们封装好的

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ws 调试</title>
</head>
<script src="./socket.js"></script>
<script>
    var ws = new ReSocket('ws://localhost:8080');

    ws.connect()
    ws.onMessage((res) => {
        console.log('onMessage', res)
    })
</script>

<body>

</body>

</html>

打开浏览器之后在控制台中看日志,如图:

图片

在网络中我们需要在这里看:

图片

到这里,如果你跟着做了一遍,你已经掌握了,如果感觉现在没时间,可以收藏,点赞,留个标记,毕竟收藏等于学会了

标签:转载,WebSocket,socket,null,private,重连,心得,连接
From: https://www.cnblogs.com/testzcy/p/18641660

相关文章

  • 【转载】什么是Banner以及测试时需要注意的点
    大家好,我是莫宁。相信很多新手小白在近几年也有听说过“banner”吧,是不是很疑惑。反正莫宁刚入门的时候是对这个词很陌生,不知道什么。今天就来为各位小伙伴解答这个疑问吧!什么是Banner?大家都知道“banner”翻译过来是横幅的意思,所以在设计中,banner是指网幅广告、横幅广告等,可以简......
  • 关于PY打包文件的解包的记录(转载为主)
    由于这个很玄学,加之很多人的文章太老了,所以我打算写一篇文档.首先得会打包,才能解包.故贴上打包文章:Pythonpyinstaller打包exe最完整教程_pythonexe-CSDN博客我没细看,稍微看了下原理,不知道对不对.1简介python提供了多种方法用于将普通的*.py程序文件编译成exe文件(有......
  • Python-IO(转载)
    Python文件I/O本章只讲述所有基本的I/O函数,更多函数请参考Python标准文档。打印到屏幕最简单的输出方法是用print语句,你可以给它传递零个或多个用逗号隔开的表达式。此函数把你传递的表达式转换成一个字符串表达式,并将结果写到标准输出如下:#!/usr/bin/python#-*-coding:......
  • 消息传递机制(转载)
    进程间的通信方式——pipe(管道)-CSDN博客1.进程间通信每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提......
  • MD语法笔记(转载)
    [Markdown+Typora/VSCode超全教程]给大一新生安利的文本神器Sakiyary2022/7/16[Markdown+Typora/VSCode超全教程]给大一新生安利的文本神器!_哔哩哔哩_bilibili......
  • Z3求解器(转载)
    title:Z3求解器date:2024/12/2717:31:00toc:truecategories:REVERSE以下全部摘自别人,这个工具我懒得看官方文档,等需要时来看吧.Z3简介Z3是一个微软出品的开源约束求解器,能够解决很多种情况下的给定部分约束条件寻求一组满足条件的解的问题(可以简单理解为解方程......
  • Fleck:一个轻量级的C#开源WebSocket服务端库
    推荐一个简单易用、轻量级的C#开源WebSocket服务端库,方便我们快速实现WebSocket的开发。01项目简介Fleck是一个用C#编写的轻量级WebSocket服务器库。它提供了一个简单而直观的API,使得开发者可以轻松地在他们的应用程序中集成WebSocket功能,比如一些常见的实时通信应用,......
  • 学习HTML你有哪些心得?
    学习HTML对于前端开发来说是非常基础且重要的一步。以下是我在学习HTML过程中的一些心得和体会:理解基础语法:HTML的语法相对简单,主要由标签(tags)构成。理解如何正确地使用标签,包括开标签、闭标签以及自闭合标签,是编写有效HTML代码的关键。同时,掌握HTML文档的基本结构,如<!DOCTYPEh......
  • SQL Server变更数据捕获(CDC)(转载)
    一、CDC简介二、开启CDC的必要条件三、开启数据库CDC1、在需要开启CDC的数据库上执行脚本如下2、查询数据库的CDC开启状态四、开启表CDC1、添加数据文件组和文件2、执行以下脚本,开启某个表的CDC3、查看某个表CDC的开启状态五、CDC使用1、对表C开启CDC后,会生成系统表以及......
  • Animate.css 转载
    前言当你想要吸引用户的注意力,动画是一个强大的工具。Animate.css是一个功能丰富的库,用于在网页上快速轻松地添加动画。无需深入JavaScript或复杂的CSS,你可以给元素添加预设的动画效果。本文将指导你如何使用Animate.css为你的网站增添活力。使用教程第一步:引入Animate.css......