项目级WebSocket客户端工厂类设计详解及技巧总结

项目级WebSocket客户端工厂类设计详解及技巧总结

1. 设计思路

WebSocket客户端工厂类是一个用于管理和控制WebSocket连接的实用工具。本文将详细介绍它的设计思路以及包含的属性和方法。

1.1 设计思路

  1. 参数接口和构造方法 在代码中,首先定义了一个WebSocketParam接口,用于配置WebSocket连接的参数,包括IP地址、消息处理函数和是否可关闭连接等。然后,在构造方法中通过参数初始化工厂类的属性,包括参数配置和相关回调函数的设置。

  2. 连接管理和重连机制 工厂类通过使用WebSocket实例化对象来建立连接。在连接建立时,设置一个心跳检查定时器,以定期向服务器发送心跳消息,维持连接的稳定性。如果发生连接异常,则自动进行重新连接,确保连接的可靠性。

  3. 消息处理函数和发送消息接口 工厂类通过设置onmessage回调函数来处理接收到的消息。当有消息到达时,调用该回调函数进行处理。同时,提供了一个sendMsg接口用于向WebSocket发送消息。

  4. 连接的关闭和销毁 通过设置closeCallback函数,判断是否允许关闭WebSocket连接。如果不允许关闭,则执行重新连接操作。此外,提供了一个destroy方法,用于关闭连接并重置相关参数,释放资源。

1.2 属性和方法

  • 属性:

    • wsInstance: WebSocket实例对象
    • param: WebSocketParam对象,用于配置WebSocket连接的参数
    • lockReconnect: 重连锁,防止重连方法被触发多次
    • checkTimer: 心跳间隔器的句柄,用于定时执行心跳检查
    • closeable: WebSocket连接是否可关闭的标志
    • isFirstConnect: 是否是首次连接的标志
    • onmessage: 消息处理函数
  • 方法:

    • connectCallback(): WebSocket连接建立时的回调函数,设置心跳检查定时器并发送不同的消息给后端
    • errorCallback(e): WebSocket出现错误时的回调函数,重新连接
    • closeCallback(): WebSocket连接关闭时的回调函数,判断是否允许关闭连接,如不允许则重新连接
    • connect(): 建立WebSocket连接的方法,实例化WebSocket对象并设置回调函数
    • reconnect(): 重新连接WebSocket的方法,根据设定的时间间隔进行连接重试
    • sendMsg(msg: any): 向WebSocket发送消息的方法
    • destroy(): 销毁WebSocket连接的方法,包括清除定时器和关闭连接等操作

1.3 技巧总结

  • 使用参数接口:通过定义参数接口,可以更清晰地配置和管理WebSocket连接的参数,增加代码的可读性和维护性。
  • 利用构造方法进行初始化:构造方法是实例化工厂类时的入口,可以在其中进行相关属性的初始化和回调函数的设置。
  • 设置心跳检查定时器:通过定时器定期发送心跳消息,可以保持WebSocket连接的稳定性。
  • 处理错误和关闭事件:根据实际需求,实现错误处理和关闭事件的回调函数,保证针对性的操作,如重新连接或判断是否允许关闭连接。
  • 提供发送消息接口:封装一个发送消息的接口,方便外部调用者向WebSocket发送消息。
  • 销毁连接时的资源释放:在销毁连接时,经常需要释放定时器和关闭连接,确保资源的正确释放和重置。

通过以上设计思路和技巧,我们实现了一个可靠的WebSocket连接和重连机制的工厂类。这个工厂类简化了WebSocket连接的管理和操作,提供了方便的接口和灵活的配置选项,使得开发人员能够更轻松地实现实时通信功能,并增加了连接的稳定性和可靠性。

2. 实现代码

ts 复制代码
export interface WebSocketParam {
  ip: string;
  onmessage: (msg: any) => void;
  closeable: boolean;
}

type TimerInsert = {
  mountedTimerRelative?: (msg: any) => void;
};

// websocket客户端工厂
export default class WSClientFactory {
  // websocket实例对象
  wsInstance: WebSocket & TimerInsert = null;
  // 构造参数
  param: WebSocketParam;
  // 重联锁
  lockReconnect = false;
  // 心跳间隔器的句柄
  checkTimer: NodeJS.Timer = null;
  // 表明此websocket是否是可以关闭的(如果不可以关闭,则断开重连之后会自动重新连接)
  closeable = false;
  // 是否是第一次连接
  isFirstConnect = true;
  // 消息处理函数
  onmessage = Function();

  constructor(param: WebSocketParam) {
    const { closeable = false, onmessage = Function() } = param;
    this.param = param;
    this.closeable = closeable;
    this.onmessage = onmessage;
  }

  private connectCallback() {
    // 在通道打开的时候就设置一个间隔器用来做心跳检查
    this.checkTimer = setInterval(() => {
      if (this.wsInstance) {
        try {
          this.wsInstance.send("alive");
        } catch (e) {
          this.reconnect();
        }
      }
    }, 15000);

    // 分两种情况:首次和非首次连接到后端;两种情况下连接之后像后端发送不同的消息
    if (!this.isFirstConnect) {
      this.wsInstance.send(
        JSON.stringify({})
      );
    } else {
      this.wsInstance.send(JSON.stringify({}));
    }

    // 修改首次连接的token
    this.isFirstConnect = false;
  }

  // ws实例(wsInstance)发生错误时的回调函数,功能为重新连接
  private errorCallback(e) {
    this.reconnect();
  }

  // ws实例关闭事件的回调函数,功能为检测此ws实例能否被关闭,如果不允许关闭则重新连接
  private closeCallback() {
    if (!this.closeable) this.reconnect();
  }

  // 建立连接的方法(如果不进行封装,则ws连接会在new WebSocket()也就是WebSocket实例化的时候连接上,这一点非常的不具有语义性,封装之后就好多了)
  public connect() {
    if (!this.wsInstance) {

      // 获得WebSocket的实例化对象
      this.wsInstance = new WebSocket(
        `ws://${this.param.ip}/ws?token=${localStorage.getItem('token')}` // 一般建立ws都要用凭证的,而凭证就存放在localStorage里面
      );
      
      // 设置ws实例对象的onopen回调参数
      this.wsInstance.onopen = (e) => {
        this.connectCallback();
      };
      // 设置ws实例对象的onerror回调参数
      this.wsInstance.onerror = (e) => {
        this.errorCallback(e);
      };
      // 设置ws实例对象的onopen回调参数
      this.wsInstance.onmessage = (e) => {
        this.param.onmessage(e.data);
      };
      // 设置ws实例对象的onclose回调参数
      this.wsInstance.onclose = (e) => {
        this.closeCallback();
      };
    }
  }

  // 重新连接的方法
  private reconnect() {
    // 如果重新连接功能被禁止,则无需再执行后面的逻辑;
    // 锁的目的是为了保证reconnect方法不会被出发多次
    if (this.lockReconnect) return;
    this.lockReconnect = true;
    // ws实例请求连接之后,立即释放重连锁,可勉强是可以的,因为重连方法的入口只有这里一处,这里使用的是一个定时器,所以下一次重连在五秒
    setTimeout(() => {
      this.destroy();
      this.connect();
      this.lockReconnect = false;
    }, 5000);
  }

  // 暴露一个发送信息的接口
  public sendMsg(msg: any) {
    this.wsInstance?.send(msg);
  }

  // 向外暴露一个取消此封装实体的方法:1. 消除心跳间隔器; 2. 关闭ws实例连接之后删除ws实例对象
  public destroy() {
    // 处理定时器
    clearInterval(this.checkTimer);
    this.checkTimer = null;

    // 处理ws实例
    this.closeable = true;
    this.wsInstance.close();
    this.wsInstance = null;

    // 重置其它参数
    this.lockReconnect = false;
    this.isFirstConnect = true;

    // 没有处理this.params这是因为,params可以在合适的时机继续使用:const newWS = new WSClientFactory(oldWS.params);
  }
}

3. 服务端代码

js 复制代码
const WebSocket = require('ws');

const wss = new WebSocket.Server({
    noServer: true
});

wss.on('connection', (ws) => {
    console.log('WebSocket connection established.');

    // 发送确认连接的消息给客户端
    ws.send('Connection established.');

    ws.on('message', (message) => {
        console.log('Received message:', message);
    });

    ws.on('close', () => {
        console.log('WebSocket connection closed.');
    });
});

const server = require('http').createServer();

server.on('upgrade', (request, socket, head) => {
    wss.handleUpgrade(request, socket, head, (ws) => {
        wss.emit('connection', ws, request);
    });
});

server.listen(8080, '127.0.0.1', () => {
    console.log('WebSocket server listening on ws://127.0.0.1:8080');
});

4. 测试用例

js 复制代码
const param = {
    ip: "127.0.0.1:8080", closeable: false, onmessage: function (msg) {
        console.log(msg);
    }
};
const ws = new WSClientFactory(param);
console.log('ws: ', ws); // ws:  WSClientFactory {wsInstance: null, lockReconnect: false, checkTimer: null, closeable: false, isFirstConnect: true, ...}
ws.connect(); // Connection established.
ws.sendMsg('1123213');
ws.destroy();
const ws2 = new WSClientFactory(ws.param);
ws2.connect(); // Connection established.

3. 总结:

WebSocket客户端工厂类的设计思路包括:

  • 通过参数接口进行配置
  • 使用构造方法进行初始化
  • 设置心跳检查定时器
  • 处理错误和关闭事件
  • 提供发送消息接口
  • 销毁连接时的资源释放

这些技巧和实践使得工厂类设计更加合理和可靠,提高了开发效率和代码质量。

同时,这个工厂类的设计也提供了一种可扩展的基础,可以根据实际需求进行进一步封装和定制化。

相关推荐
Bio Coder5 分钟前
学习用 Javascript、HTML、CSS 以及 Node.js 开发一个 uTools 插件,学习计划及其周期
javascript·学习·html·开发·utools
糊涂涂是个小盆友9 分钟前
前端 - 使用uniapp+vue搭建前端项目(app端)
前端·vue.js·uni-app
浮华似水33 分钟前
Javascirpt时区——脱坑指南
前端
专注VB编程开发20年35 分钟前
WebSocket和HTTP协议的性能比较与选择
websocket·网络协议·http
王二端茶倒水36 分钟前
大龄程序员兼职跑外卖第五周之亲身感悟
前端·后端·程序员
_oP_i40 分钟前
Web 与 Unity 之间的交互
前端·unity·交互
钢铁小狗侠42 分钟前
前端(1)——快速入门HTML
前端·html
凹凸曼打不赢小怪兽1 小时前
react 受控组件和非受控组件
前端·javascript·react.js
狂奔solar1 小时前
分享个好玩的,在k8s上部署web版macos
前端·macos·kubernetes
qiyi.sky1 小时前
JavaWeb——Web入门(8/9)- Tomcat:基本使用(下载与安装、目录结构介绍、启动与关闭、可能出现的问题及解决方案、总结)
java·前端·笔记·学习·tomcat