浅谈websocket以及封装使用

  • WebSocket是一种在Web应用程序中提供双向通信的协议。它允许服务器和客户端之间建立持久的连接,实现实时数据传输。相比传统的HTTP请求,WebSocket具有更低的延迟更高的效率
  • WebSocket使用了一种新的特殊的协议头,在客户端和服务器之间进行握手,建立连接后,可以通过发送消息进行双向通信。这使得服务器能够主动向客户端推送数据,而不需要客户端发起请求。
  • WebSocket可以在浏览器中使用,也可以在服务器端使用。在浏览器中,你可以使用JavaScript的 WebSocket API 来创建WebSocket连接,并处理接收和发送的消息。在服务器端,你可以使用相应的编程语言库或框架提供的WebSocket实现来创建和管理WebSocket连接。
  • WebSocket在很多应用场景中都非常有用,比如实时聊天应用、实时数据更新、多人协作和实时游戏等。它提供了一种高效可靠实时的通信方式,已经成为现代Web开发中常用的技术之一。

本文主要实现web端websocket封装及创建,框架以vue3piniatypescript项目为例

实现

stores

extractStore.ts
import type {
  PiniaCustomStateProperties,
  StoreActions,
  StoreGeneric,
  StoreGetters,
  StoreState,
} from "pinia";
import type { ToRefs } from "vue";
import { isReactive, isRef, toRaw, toRef } from "vue";

type Extracted<SS> = ToRefs<
  StoreState<SS> & StoreGetters<SS> & PiniaCustomStateProperties<StoreState<SS>>
> &
  StoreActions<SS>;

export function extractStore<SS extends StoreGeneric>(
  store: SS,
): Extracted<SS> {
  const rawStore = toRaw(store);
  const refs: Record<string, unknown> = {};

  for (const [key, value] of Object.entries(rawStore)) {
    if (isRef(value) || isReactive(value)) {
      refs[key] = toRef(store, key);
    } else if (typeof value === "function") {
      refs[key] = value;
    }
  }
  return refs as Extracted<SS>;
}
permission
state.ts
import { defineStore } from "pinia";
import { State } from "./permission-types";
interface State {
  ws: WebSocket | null;
  message: any;
}
export const usePermissionState = defineStore({
  id: "permission.state",
  state: (): State => {
    return {
      ws: null,
      message: "",
    };
  },
});
getters.ts
import { computed } from "vue";
import { defineStore } from "pinia";
import { usePermissionState } from "./state";

export const usePermissionGetters = defineStore("permission.getters", () => {
  const state = usePermissionState();
  return {
    message: computed((): any => state.message),
  };
});
action.ts
import { defineStore } from "pinia";
import { usePermissionState } from "./state";
export const usePermissionActions = defineStore("permission.actions", () => {
  const state = usePermissionState();
  const createWebsocket = async (): Promise<void> => {
    const ws = new WS();
    state.ws = ws;
    state.ws.create();
  };
  const getWebsocketMsg = (payload: any): void => {
    state.message = payload;
  };
  const sendWebsocketMsg = async (
    payload: any,
  ): Promise<ReturnType<typeof getWebsocketMsg>> => {
    return getWebsocketMsg(payload);
  };
  return {
    createWebsocket,
    sendWebsocketMsg,
  };
});
index.ts
import { extractStore } from "@/stores/extractStore";
import { defineStore } from "pinia";
import { usePermissionActions } from "./actions";
import { usePermissionGetters } from "./getters";
import { usePermissionState } from "./state";

export const usePermissionStore = defineStore("permission", () => {
  return {
    ...extractStore(usePermissionActions()),
    ...extractStore(usePermissionGetters()),
    ...extractStore(usePermissionState()),
  };
});

websocket

websocket-type.ts
export declare interface IWS {
  create(): void;
  send: (msg: any) => void;
}
export declare interface IConfig {
  url?: string;
  port?: number;
  protocol?: string;
  time?: number;
}
export declare type GetLocal = (key: string) => string;
websocket.ts
import { IWs, IConfig, GetLocal } from "./websocket-type";
import { usePermissionStore } from "@/stores/permission";
const permissionStore = usePermissionStore()
const getLocal: GetLocal = (key) => {
  let value = localStorage.getItem(key) || "";
  if (value.startsWith("[") || value.includes("{")) {
    return JSON.parse(value);
  } else {
    return value;
  }
};
class WS implements IWs {
  private url: string;
  private port: number;
  private protocol: string;
  private time: number;
  private ws: null | WebSocket = null;
  private timer: null | ReturnType<typeof setTimeout> = null;
  constructor(config: IConfig = {}) {
    this.url = config.url || "localhost"; //路径
    this.port = config.port || 4000; //端口号
    this.protocol = config.protocol || "ws"; //协议
    this.time = config.time || 30 * 1000; //心跳检测时间
  }
  create() {
    this.ws = new WebSocket(`${this.protocol}://${this.url}:${this.port}`);
    this.ws.onopen = this.onOpen; //连接成功后事件
    this.ws.onmessage = this.onMessage; //收到消息后事件
    this.ws.onclose = this.onClose; //连接关闭事件
    this.ws.onerror = this.onError; //连接异常事件
  }
  private onOpen = () => {
    //发送消息
    this.ws?.send(
      JSON.stringify({
        //鉴权
        type: "auth",
        data: getLocal("token"),
      }),
    );
  };
  private onMessage = (e: MessageEvent): void => {
    let { type, data } = JSON.parse(e.data);
    switch (type) {
      case "noAuth":
        console.log("没有权限");
        break;
      case "heartCheck": //心跳检测
        this.checkServer();
        this.ws?.send(
          JSON.stringify({
            type: "heartCheck",
            msg: "yes",
          }),
        );
        break;
      default:
        permissionStore.sendWebsocketMsg(data) // 发送数据
    }
  };
  private onClose = (): void => {
    this.ws?.close();
  };
  private onError = (): void => {
    setTimeout(() => {
      this.create();
    }, 1000);
  };
  send = (msg: any) => {
    this.ws?.send(JSON.stringify(msg));
  };
  private checkServer(): void {
    this.timer && clearTimeout(this.timer); //防抖
    this.timer = setTimeout(() => {
      this.onClose();
      this.onError();
    }, this.time + 1000); //断线重联
  }
}
export default WS;

参考文档

  • vue3
  • pinia
  • typescript
  • WebSocket
  • WebSocket 教程