如何封装一个通用的,扩展性高的webSocket 对象

491 阅读6分钟

前端 自定义封装一个webSocket 客户端

  • 创建一个webSocket 连接

通过new 的方式创建一个webSocket 的连接对象。

第一个参数是ws 地址

第二个参数是可选的, 可以选择子协议连接webSocket

// Create WebSocket connection.
const socket = new WebSocket("ws://localhost:8080");
  • 使用子协议的方式

单个协议字符串或协议字符串数组。这些字符串用于指示子协议,以便单个服务器可以实现多个 WebSocket 子协议(例如,您可能希望一台服务器能够根据指定处理不同类型的交互protocol)。

如果省略,则默认使用空数组,即[]。

不同的子协议 ,后端可以选择不同的协议来进行不同的事件对象做处理。

  • 如何封装一个公用的webSocket 呢?

封装的必要性:

webSocket 所有的处理事件都是处于 创建的webSocket 对象上的,不管是往后端发送数据,还是说接受后端发送的消息,都是在一个对象上处理的,而且都是通过重写方法的形式来触发的,在具体讲如何封装之前我们先来讲一下webSocket 在使用的时候比较常见的一些方法吧。


  1. message

接受后端推送的消息数据。有点像dom 的事件触发函数

// Listen for messages
socket.addEventListener("message", (event) => {
  console.log("Message from server ", event.data);
});

socket.onmessage = (event) => {};
  1. open

当打开open与 a 的连接时会触发该事件。

适合进行一些初始化的操作,比如说提示用户webSocket 连接成功,或者是应用的一些初始化操作。

socket.addEventListener("open", (event) => {});

socket.onopen = (event) => {};

这里的event 十分的重要

event 里面包含了我们当前webSocket 实例的一个引用的地址, 而且里面还有其他很多重要的信息,比如在message 里面后端返回的数据就是在onmessage 里面的。

  1. close

close当与 a 的连接WebSocket关闭时会触发该事件。

addEventListener("close", (event) => {});

onclose = (event) => {};

问题

上面的message 方法存在很严重的问题。

因为在日常的开发中,我们可能会在不同的情况下对服务端返回的不同的一个data 进行不同的处理,如果是正常使用的话,我们就需要在message 里面不停的去对不同的事件做不同的处理,而且我们也不可以主动的去重新写message ,因为我们一旦重新写了,也就意味着我们之前的message 会被覆盖掉,所以这个地方只能加。不断地加逻辑判断。

所以我们必须把onmessage 进行分离出来,在使用的时候不直接的去操作onmessage这个方法,我们暴露一个方法或者一个新的对象,在这个对象里面去维护的webSocket 对象,这样我们就可以对message 进行有个有效的拦截,而且通过使用策略模式的方式对不同的一个事件处理进行一个Map 映射,然后在外部去引入,这样就很有效的把事件的处理逻辑抽离到了外面。

但是这样优雅吗?

不,不够优雅,我更想要的方式是提供给我一个方法,我传入一个后端定义好的事件code ,以及他的一个处理函数,在后端主动推送这个code 消息的事件的时候,主动的把后端的参数返回给我的回调函数里面,这样我就可以默认我这个回调函数就是后端主动推送成功的时候的回调函数。

先来看看如何来实现


建立一个通用的事件调度中心

(这里的场景还没有特别复杂,所以建立的事件中心比较简单)

这里采用的是比较平常的发布订阅方式。

相信各位大神肯定有更加的方法,可以在后面告知一二。

event.js

可以// 事件是否存在
const existEvent = (map, key) => {
  return map.has(key) && isFunction(map.get(key));
}

/**
 * 
 * @param {Map} map 
 * @param {事件的key} key 
 * @param {事件的值} value 
 * @returns 
 */
const addEvent = (map, key, value) => {
    if(existEvent(map, key)){
        console.warn("ws 事件中心已经注册了该key", key);
        return
    }
    map.set(key, value);
} 

// 订阅事件
const subScription = (map, key, value) => {
    // 添加事件
    addEvent(map, key, value)
}

// 触发事件
const emitScription = (map, key, params) => {
    if(!existEvent(map, key)){
        console.warn("ws 事件还未被注册", key);
        return ()=>{

        }
    };
    return map.get(key);
}

// 创建事件监听的对象

const createEventListener = () => {
    const EventMap = new Map();

    return {
        // 订阅事件
        subscribe(eventName, callback){
            subScription(EventMap, eventName, callback);
        },
        
        // 发布事件, 也就是触发事件
        publish(eventName, params){
            emitScription(EventMap, eventName)(params);
        },

        // 发布一次的事件
        publishOnce(eventName, params){
            // 触发
            emitScription(EventMap, eventName).apply(this, [params]);
            this.removeEvent(eventName);
        },

        // 取消事件
        removeEvent(eventName){
           let flag = EventMap.delete(eventName);
           if(!flag){
            console.warn("当前的事件未注册");
           }
        }
    }
}

export default createEventListener;

有了统一的事件调度中心了,我们就可以创建我们的socket 对象了

Socket 对象的创建

因为我比较喜欢函数式的写法,所以这个地方就使用工厂函数来进行创建了。

socket.js

/**
 * 创建WebSocket连接,并嵌入心跳检测和事件侦听
 * @param {string} url WebSocket服务地址
 * @param {string|Array} protocol 协议,可以是单一协议字符串或协议数组
 * @param {Object} props 附加属性
 * @returns WebSocket实例和控制方法
 */
const createWebSocket = (url, protocol, props = {}) => {
    // 当前只接受一个协议,正常来说可以接受一个协议数组, 但是后端无法识别,所以这里只发送一个协议即可
    protocol =  getProtocol(protocol)
    let ws = new WebSocket(url,protocol);

    let reconnectAttempts = 0;

    const maxReconnectAttempts = 5;

    const { heartHandle = () => { }, heart= false } = props;
    // 内部通信系统
    const eventListener = createEventListener();

    // 内部的on message
    ws.onmessage = (wsEvent) => {
        const data = parseJSON(wsEvent.data, "object");

        // 从后端返回的状态中解析出
        const { code, params } = data;
        if(code){
            eventListener.publish(code, params);
            return;
        }
        eventListener.publish(11111,data)
    }

    const heartbeat = createHeartBeat({
        initHeartFn: (key) => {
            // 向ws 事件中心注册事件
            heart && eventListener.subscribe(key, (value) => {
                heartHandle(value);
            });
        },

        heartCallback(key) {
            ws.send(key);
        }
    });



    return {
        // 手动触发重新连接
        reConnection() {
            // 尝试重连超出了
            if (reconnectAttempts > maxReconnectAttempts) {
                return;
            }

            reconnectAttempts++;
            console.log(`尝试第${reconnectAttempts}次重连...`);
            setTimeout(() => {
                ws = reConnection(ws, protocol);
            }, reconnectInterval)
        },
        // 关闭webSocket连接
        close: ws.close,
        send:(msg)=>{
            if(!WebSocketIsConnection(ws)){
                return false;
            }
            ws.send(msg);
            return true
        },
        // 同时也支持用户自己设置
        changeMessage: ws.onmessage,
        message(eventName, callback = () => { }) {
            eventListener.subscribe(eventName, callback);
        },
        ...heartbeat
    };
}

export default createWebSocket;

这里提供了一个心跳检测的机制,当然为了之后的便捷,用户可以选择开启或者不开启


部分其他的代码

socket.js

// 判断当前webSocket 是否处于连接状态
const WebSocketIsConnection = (ws) => ws.readyState === WebSocket.OPEN


/**
 * 
 * @param {WebSocket} ws 当前的webSocket 连接 
 * @returns 
 */
const reConnection = (ws, protocol) => {
    // 检查当前WebSocket的状态,如果已经是打开状态,则不需要重新连接
    if (WebSocketIsConnection()) {
        return;
    }
    // 闭包中访问WebSocket实例,并创建新的WebSocket连接
    return new WebSocket(url, protocol);
}



// 心跳检测机制
const createHeartBeat = ({
    heartKey = HEART_KEY,
    heartCallback = () => { },
    initHeartFn = () => { }
}) => {

    initHeartFn(heartKey);
    // 心跳函数
    const heartbeat = () => {
        heartCallback(key);
    };

    let heartbeatTimer = null;
    // 启动心跳
    const startHeartbeat = () => {
        clearInterval(heartbeatTimer);
        heartbeatTimer = setInterval(heartbeat, heartbeatInterval);
    };

    // 清理心跳
    const clearHeartbeat = () => {
        clearInterval(heartbeatTimer);
    };
    return { startHeartbeat, clearHeartbeat, heartbeat }
}


使用方式

  • 创建
// 初始化SubWs
const initSubWs= ()=>{
    if(WS.INIT){
        return;
    }
    const labsWs = createWebSocket(WS_URL, [
        ProtocolsMap.labs
    ]);

    const runNodesStatusWs = createWebSocket(WS_URL ,[
        ProtocolsMap.runNodesStatus
    ]);

    const runNodesConsoleWs = createWebSocket(WS_URL,[
        ProtocolsMap.runNodesConsole
    ])
    return {
        [ProtocolsMap.labs]:labsWs,
        [ProtocolsMap.runNodesStatus]:runNodesStatusWs,
        [ProtocolsMap.runNodesConsole]:runNodesConsoleWs
    };
}

// 初始化WS
const initWs = () => {
    WS.SubWs = initSubWs();
    WS.INIT = true;
}

  • 使用
   // 请求WS
  const labWs = getWebSocketByProtocols(ProtocolsMap.runNodesStatus);
  clearImmediate(timer);
  // 向labWs 中发送消息
  timer = setInterval(()=>{
  
   let flag = labWs.send("hello");
   if(!flag){
    clearInterval(timer)
   }
  
  }, 5000);

  labWs.message(11111, (data) => {
    console.log(data);
  })