• WebSocket客户端工厂类的设计思路详解
  • 发布于 2个月前
  • 200 热度
    0 评论
一. 设计思路
WebSocket客户端工厂类是一个用于管理和控制WebSocket连接的实用工具。本文将详细介绍它的设计思路以及包含的属性和方法。
1.1 设计思路
.参数接口和构造方法
在代码中,首先定义了一个WebSocketParam接口,用于配置WebSocket连接的参数,包括IP地址、消息处理函数和是否可关闭连接等。然后,在构造方法中通过参数初始化工厂类的属性,包括参数配置和相关回调函数的设置。

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

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

.连接的关闭和销毁
通过设置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连接的管理和操作,提供了方便的接口和灵活的配置选项,使得开发人员能够更轻松地实现实时通信功能,并增加了连接的稳定性和可靠性。


二. 实现代码
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();
  }
  // 堆代码 duidaima.com
  // 建立连接的方法(如果不进行封装,则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);
  }
}
三. 服务端代码
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');
});
四. 测试用例
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.
五 总结:
WebSocket客户端工厂类的设计思路包括:
1.通过参数接口进行配置
2.使用构造方法进行初始化
3.设置心跳检查定时器
4.处理错误和关闭事件
5.提供发送消息接口
6.销毁连接时的资源释放

这些技巧和实践使得工厂类设计更加合理和可靠,提高了开发效率和代码质量。同时,这个工厂类的设计也提供了一种可扩展的基础,可以根据实际需求进行进一步封装和定制化。

用户评论