如何使用Socket.IO实现微应用之间的通讯?

149 阅读4分钟

为什么?

我微应用加载器都自己写了,通讯怎么可能用乾坤的东西?

****​​​​​​​​​​​​​​Socket.IO是什么?

1. Socket.IO 是一个库,可以在客户端和服务器之间实现 低延迟, 双向基于事件的 通信。

2. Socket.IO 不是 WebSocket实现。尽管 Socket.IO 确实在可能的情况下使用 WebSocket 进行传输,但它为每个数据包添加了额外的元数据。这就是为什么 WebSocket 客户端将无法成功连接到 Socket.IO 服务器,而 Socket.IO 客户端也将无法连接到普通 WebSocket 服务器。

3. Socket.IO 并不打算在移动应用程序的后台服务中使用。Socket.IO 库保持与服务器的开放 TCP 连接,这可能会导致用户消耗大量电池。请为此用例使用FCM等专用消息传递平台。

Socket.IO有什么特点?

HTTP 长轮询回退​

如果无法建立 WebSocket 连接,连接将回退到 HTTP 长轮询。

这个特性是人们在十多年前创建项目时使用 Socket.IO 的原因(!),因为浏览器对 WebSockets 的支持仍处于起步阶段。

即使现在大多数浏览器都支持 WebSockets(超过97%),它仍然是一个很棒的功能,因为我们仍然会收到来自用户的报告,这些用户无法建立 WebSocket 连接,因为他们使用了一些错误配置的代理。

自动重新连接​

在某些特定情况下,服务器和客户端之间的 WebSocket 连接可能会中断,而双方都不知道链接的断开状态。

这就是为什么 Socket.IO 包含一个心跳机制,它会定期检查连接的状态。

当客户端最终断开连接时,它会以指数回退延迟自动重新连接,以免使服务器不堪重负。

数据包缓冲​

当客户端断开连接时,数据包会自动缓冲,并在重新连接时发送。

更多信息在这里.

收到后的回调​

Socket.IO 提供了一种方便的方式来发送事件和接收响应:

发件人

socket.emit("hello", "world", (response) => {
  console.log(response); // "got it"
});

接收者

socket.on("hello", (arg, callback) => {
  console.log(arg); // "world"
  callback("got it!");
});

您还可以添加超时:

socket.timeout(5000).emit("hello", "world", (err, response) => {
  if (err) {
    // 另一方未在给定延迟内确认事件
  } else {
    console.log(response); // "got it"
  }
});

广播​

在服务器端,您可以向所有连接的客户端客户端的子集发送事件:

// 到所有连接的客户端
io.emit("hello");

// 致“news”房间中的所有连接客户端
io.to("news").emit("hello");

这在扩展到多个节点时也有效。

多路复用​

命名空间允许您在单个共享连接上拆分应用程序的逻辑。例如,如果您想创建一个只有授权用户才能加入的“管理员”频道,这可能很有用。

io.on("connection", (socket) => {
  // 普通用户
});

io.of("/admin").on("connection", (socket) => {
  // 管理员用户
});

那如果我用它来实现微应用通讯会怎么样?稍加改造封成组件,组件逻辑如下配合代码看就清晰了

展开看代码:

interface.d.ts

declare module 'socket.io-client' {
  io: any;
  export default io;
}

eventEmitter.ts

/**
 * 自定义事件
 * author hjj
 */

type EventItemType = { [key: string]: { listener: (data?: any) => void; timer: number }[] };

class EventEmitter {
  events: EventItemType;
  constructor() {
    this.events = {};
  }
  getEvents() {
    return this.events;
  }
  once(eventName: string, listener: (...args: any) => void) {
    return this.on(eventName, listener, 0);
  }
  on(eventName: string, listener: (...args: any) => void, timer = -1) {
    let listeners = this.getListeners(eventName);
    listeners.push({
      listener,
      timer,
    });
  }
  emit(eventName: string, ...args: any) {
    return this.trigger(eventName, args);
  }
  remove(eventName: string) {
    this.events[eventName] && delete this.events[eventName];
  }
  off(eventName: string, listener: (...args: any) => void) {
    let listeners = this.getListeners(eventName);
    let index = listeners.findIndex((item) => item.listener === listener);
    index !== -1 && listeners.splice(index, 1);
  }
  clear() {
    this.events = {};
  }
  trigger(eventName: string, args: any) {
    let listeners = this.getListeners(eventName);
    for (let i = 0; i < listeners.length; i++) {
      let listener = listeners[i];
      if (listener) {
        listener.listener.apply(this, args || []);
        listener.timer === 0 && listeners.splice(i, 1);
        listeners.length === 0 && delete this.events[eventName];
        listener.timer !== -1 && listener.timer--;
      }
    }
  }
  getListeners(eventName: string) {
    return this.events[eventName] || (this.events[eventName] = []);
  }
}

export default EventEmitter;

index.ts

import EventEmitter from './eventEmitter';
import logger from '../logger'; //日志打印组件就是console.log一样
import cache from '../cache'; //缓存组件这里用来获取session里的token
import io from 'socket.io-client';
//继承自定义事件类型
class SocketEmitter extends EventEmitter {
  sokect: any;
  sokectEvents: string[];
  subActions: { [key: string]: (data?: any) => void };
  constructor() {
    super();
    this.sokectEvents = [];
    this.subActions = {};
  }
  connect(url: string, options: any) {
    if (this.sokect && this.sokect.connected) {
      return;
    }
    const token = cache.getCache('token', 'session');
    logger.debug('第一次尝试连接socket');
    this.sokect = io(url, { path: '/socket.io', query: { token }, transports: ['websocket'], ...options });
    this.sokect.on('connect', function () {
      logger.debug('socket连接成功');
    });
    this.sokect.on('disconnect', function () {
      logger.debug('socket断开连接');
    });
  }
  disconnect() {
    this.clear();
    this.sokect && this.sokect.disconnect();
    this.sokect = null;
  }

  /**
   * 抽象socket监听方法,这几个方法就是解决了多个listener会和服务创建多条连接,如图那样,调这些方法会汇总处理后只与服务创建一条连接。
   * @param {*} param0
   */
  subscribe(eventName: string, listener: (...args: any) => void) {
    this.on(eventName, listener);
    if (this.sokectEvents.includes(eventName) || !this.sokect) {
      return;
    }
    this.sokectEvents.push(eventName);
    this.subActions[eventName] = this.subAction(eventName);
    this.sokect && this.sokect.on(eventName, this.subActions[eventName]);
    logger.debug(`socket.io listener event -> ${eventName}`);
  }

  subAction(eventName: string) {
    return (data: any) => {
      let json;
      try {
        json = JSON.parse(data);
      } catch (e) {
        json = data;
      }
      this.emit(eventName, json);
    };
  }

  /**
   * 抽象socket监听方法
   * @param {*} param0
   */
  unsubscribe(eventName: string, listener: (...args: any) => void) {
    this.off(eventName, listener);
    this.sokect && this.sokect.off(eventName, this.subActions[eventName]);
    delete this.subActions[eventName];
    this.sokectEvents = this.sokectEvents.filter((v) => v !== eventName);
    logger.debug(`socket.io unlistener event -> ${eventName}`);
  }
}
//保证全局单例
const _global = window as any;
const event = new SocketEmitter();

const LMEvent: SocketEmitter = (function () {
  if (_global._LM_EVENT_) {
    return _global._LM_EVENT_;
  } else {
    _global._LM_EVENT_ = event;
    return event;
  }
})();

export default LMEvent;