前端学习之websocket

客户端

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
</head>

<body>
  <button id="connect">连接</button>
  <button disabled id="sendMessage">发送</button>
  <button disabled id="destroy">关闭</button>
  <script type="module">
    import { messageCenter } from "./lib/messageCenter.js";
    import MyWebSocket from "./lib/webSocket.js";
    const name = "zhangsan"; //连接用户名
    const connect = document.querySelector("#connect");
    const sendMessage = document.querySelector("#sendMessage");
    const destroy = document.querySelector("#destroy");
    const wsUrl = "ws://127.0.0.1:2048/ws/?name=" + name;
    let myWebSocket;
    messageCenter.on("changeBtnState", setButtonState); //设置按钮样式
    messageCenter.on("reconnect", reconnectWebSocket); //接收重连消息
    connect.addEventListener("click", reconnectWebSocket);
    sendMessage.addEventListener("click", function (e) {
      // 发消息
      myWebSocket.sendMsg({
        ModeCode: "message",
        msg: "hello",
      });
    });
    destroy.addEventListener("click", function (e) {
      clear(myWebSocket);
    });

    function reconnectWebSocket() {
      // 入口函数
      if (myWebSocket) {
        //防止多个websocket同时执行
        clear(myWebSocket);
      }
      connectWebSocket();
    }
    // 终止连接
    function clear(myWebSocket) {
      myWebSocket.clear();
      myWebSocket = null;
    }
    function connectWebSocket() {
      myWebSocket = new MyWebSocket(wsUrl);
      myWebSocket.init(
        {
          //time:心跳时间间隔 timeout:心跳超时间隔 reconnect:断线重连时间,一般的,断线重连时间等于心跳时间间隔加断线重连时间(忽略超时等待)
          time: 10 * 1000,
          timeout: 1 * 1000,
          reconnect: 5 * 1000,
        },
        true
      );
    }

    /*
     * 设置按钮是否可点击
     * @param state:open表示开启状态,close表示关闭状态
     */
    function setButtonState(state) {
      switch (state) {
        case "open":
          connect.disabled = true;
          sendMessage.disabled = false;
          destroy.disabled = false;
          break;
        case "close":
          connect.disabled = false;
          sendMessage.disabled = true;
          destroy.disabled = true;
          break;
      }
    }
  </script>
</body>

</html>

websocket客户端

import { messageCenter } from "./messageCenter.js";

const ModeCode = {
  //websocket消息类型
  MSG: "message", //普通消息
  HEART_BEAT: "heart_beat", //心跳
};

export default class MyWebSocket extends WebSocket {
  constructor(url, protocols) {
    super(url, protocols);
    return this;
  }

  /*
   * 入口函数
   * @param heartBeatConfig  time:心跳时间间隔 timeout:心跳超时间隔 reconnect:断线重连时间间隔
   * @param isReconnect 是否断线重连
   */
  init(heartBeatConfig, isReconnect) {
    this.onopen = this.openHandler; //连接上时回调
    this.onclose = this.closeHandler; //断开连接时回调
    this.onmessage = this.messageHandler; //收到服务端消息
    this.onerror = this.errorHandler; //连接出错
    this.heartBeat = heartBeatConfig;
    this.isReconnect = isReconnect;
    this.reconnectTimer = null; //断线重连时间器
    this.waitingTimer = null; // 超时等待时间器
    this.heartTimer = null; // 心跳时间器
    this.webSocketState = false; //socket状态 true为已连接
  }

  openHandler() {
    messageCenter.emit("changeBtnState", "open"); //触发事件改变按钮样式
    this.webSocketState = true; //socket状态设置为连接,做为后面的断线重连的拦截器
    !!this.heartBeat &&
      !!this.heartBeat.time &&
      this.startHeartBeat(this.heartBeat.time); //是否启动心跳机制
    console.log("开启");
  }

  messageHandler(e) {
    const data = this.getMsg(e);
    switch (data.ModeCode) {
      case ModeCode.MSG: //普通消息
        console.log("收到消息" + data.msg);
        break;
      case ModeCode.HEART_BEAT: //心跳
        this.webSocketState = true;
        console.log("收到心跳响应" + data.msg);
        break;
    }
  }

  closeHandler() {
    //socket关闭
    messageCenter.emit("changeBtnState", "close"); //触发事件改变按钮样式
    this.webSocketState = false; //socket状态设置为断线
    console.log("关闭");
  }

  errorHandler() {
    //socket出错
    messageCenter.emit("changeBtnState", "close"); //触发事件改变按钮样式
    this.webSocketState = false; //socket状态设置为断线
    this.reconnectWebSocket(); //重连
    console.log("出错");
  }

  sendMsg(obj) {
    this.send(JSON.stringify(obj));
  }

  getMsg(e) {
    return JSON.parse(e.data);
  }

  /*
   * 心跳初始函数
   * @param time:心跳时间间隔
   */
  startHeartBeat(time) {
    this.heartTimer = setTimeout(() => {
      this.sendMsg({
        ModeCode: ModeCode.HEART_BEAT,
        msg: new Date(),
      });
      this.waitingTimer = this.waitingServer();
    }, time);
  }

  //延时等待服务端响应,通过webSocketState判断是否连线成功
  waitingServer() {
    this.webSocketState = false;
    return setTimeout(() => {
      if (this.webSocketState) return this.startHeartBeat(this.heartBeat.time);
      console.log("心跳无响应,已断线");
      this.reconnectTimer = this.reconnectWebSocket();
    }, this.heartBeat.timeout);
  }

  //重连操作
  reconnectWebSocket() {
    if (!this.isReconnect) return;
    return setTimeout(() => {
      messageCenter.emit("reconnect");
    }, this.heartBeat.reconnect);
  }
  // 清除所有定时器
  clearTimer() {
    clearTimeout(this.reconnectTimer);
    clearTimeout(this.heartTimer);
    clearTimeout(this.waitingTimer);
  }
  // 关闭连接
  clear(isReconnect = false) {
    this.isReconnect = isReconnect;
    this.clearTimer();
    this.close();
  }
}

eventbus

/*
 * @Author: Hunter
 * @Date: 2022-04-14 15:32:29
 * @LastEditTime: 2022-09-15 11:28:03
 * @LastEditors: Hunter
 * @Description:
 * @FilePath: \website\lib\messagecenter.js
 * 可以输入预定的版权声明、个性签名、空行等
 */
export class MessageCenter {
  events = {};
  /**
   * 注册事件至调度中心
   * @param type 事件类型,特指具体事件名
   * @param handler 事件注册的回调
   */
  on(type, handler) {
    //订阅者
    this.checkHandler(type, handler);
    if (!this.has(type)) {
      //若调度中心未找到该事件的队列,则新建某个事件列表(可以对某个类型的事件注册多个回调函数)
      this.events[type] = [];
    }
    this.events[type].push(handler);
    return this;
  }
  /**
   * 触发调度中心的某个或者某些该事件类型下注册的函数
   * @param type 事件类型,特指具体事件名
   * @param data 发布者传递的参数
   */
  emit(type, data) {
    //发布者
    if (this.has(type)) {
      this.runHandler(type, data);
    }
    return this;
  }
  //销毁监听
  un(type, handler) {
    this.unHandler(type, handler);
    return this;
  }
  // 只注册一次监听,执行即销毁
  once(type, handler) {
    this.checkHandler(type, handler);
    const fn = (...args) => {
      this.un(type, fn);
      return handler(...args);
    };
    this.on(type, fn);
    return this;
  }
  // 重置调度中心
  clear() {
    this.events = {};
    return this;
  }
  // 判断事件是否被订阅
  has(type) {
    return !!this.events[type];
  }
  // 同一个事件被绑定了多少函数
  handlerLength(type) {
    return this.events[type]?.length ?? 0;
  }
  // 监听invoke的消息,若handler中进行了计算或者异步操作,会反馈给invoke
  watch(type, handler) {
    this.checkHandler(type, handler);
    const fn = (...args) => {
      this.emit(this.prefixStr(type), handler(...args));
    };
    this.on(type, fn);
    return this;
  }
  // 触发watch事件,并且接收watch处理结果
  invoke(type, data) {
    return new Promise((resolve) => {
      this.once(this.prefixStr(type), resolve);
      this.emit(type, data);
    });
  }
  // 批量执行调度中心中某个函数集
  runHandler(type, data) {
    for (let i = 0; i < this.events[type].length; i++) {
      this.events[type][i] && this.events[type][i](data);
    }
  }
  // 批量销毁调度中心中某个函数集
  unHandler(type, handler) {
    !handler && (this.events[type] = []);
    handler && this.checkHandler(type, handler);
    for (let i = 0; i < this.events[type].length; i++) {
      if (this.events[type][i] && this.events[type][i] === handler) {
        this.events[type][i] = null;
      }
    }
  }
  prefixStr(str) {
    return `@${str}`;
  }
  /**
   * 检查参数是否符合标准
   * @param type 事件名
   * @param handler 事件钩子
   */
  checkHandler(type, handler) {
    if (type?.length === 0) {
      throw new Error("type.length can not be 0");
    }
    if (!handler || !type) {
      throw new ReferenceError("type or handler is not defined");
    }
    if (typeof handler !== "function" || typeof type !== "string") {
      throw new TypeError(
        `${handler} is not a function or ${type} is not a string`
      );
    }
  }
  //返回当前类的实例的单例
  static Instance(Fn) {
    if (!Fn._instance) {
      Object.defineProperty(Fn, "_instance", {
        value: new Fn(),
      });
    }
    return Fn._instance;
  }
}

export const messageCenter = MessageCenter.Instance(MessageCenter);
export default MessageCenter;

服务端

const http = require("http");
const WebSocketServer = require("./websocket").WebSocketServer;
const port = 2048; //端口
const pathname = "/ws/"; //访问路径
const server = http.createServer();
const clientKey = "name"; //客户端标识符

const webSocketServer = new WebSocketServer(
  { noServer: true },
  {
    clientKey,
  }
);
const initSocket = ({ name, req, socket, head }) => {
  webSocketServer.handleUpgrade(req, socket, head, (ws) => {
    console.log("shoyan")
    ws[clientKey] = name; //添加索引,方便在客户端列表查询某个socket连接
    webSocketServer.addClient(ws);
    webSocketServer.ws = ws;
  });
};
server
  .on("upgrade", (req, socket`在这里插入代码片`, head) => {
    //通过http.server过滤数据
    const url = new URL(req.url, `http://${req.headers.host}`);
    const name = url.searchParams.get(clientKey); //获取连接标识
    if (!checkUrl(url.pathname, pathname)) {
      //未按标准
      socket.write("路由未按标准访问");
      socket.pipe(socket);
      return;
    }
    initSocket({ name, req, socket, head });
  })
  .listen(port, () => {
    console.log("服务开启");
  });

//验证url标准
function checkUrl(url, key) {
  //判断url是否包含key
  return url.includes(key);
}

websocket 服务端

const WebSocket = require("ws");
const clientKey = "name"; //客户端标识符
exports.WebSocketServer = class extends WebSocket.Server {
  constructor(_, opts) {
    super(...arguments);
    this.clientKey = opts.clientKey ?? clientKey;
    this.webSocketClient = {}; //存放已连接的客户端
  }

  set ws(val) {
    //代理当前的ws,赋值时将其初始化
    this._ws = val;
    val.on("error", this.errorHandler);
    val.on("close", this.closeHandler);
    val.on("message", this.messageHandler);
  }

  get ws() {
    return this._ws;
  }

  messageHandler = (e) => {
    console.info("接收客户端消息");
    const data = JSON.parse(e);
    switch (data.ModeCode) {
      case "message":
        console.log(`收到${this.ws[this.clientKey]}消息${data.msg},已返回`);
        this.send(this.ws, data);
        break;
      case "heart_beat":
        console.log(`收到${this.ws[this.clientKey]}心跳${data.msg}`);
        this.send(this.ws, data);
        break;
    }
  };
  send = (ws, data) => {
    ws.send(JSON.stringify(data));
  };
  errorHandler = (e) => {
    this.removeClient(this.ws);
    console.info("客户端出错");
  };

  closeHandler = (e) => {
    this.removeClient(this.ws);
    console.info("客户端已断开");
  };

  addClient = (item) => {
    //设备上线时添加到客户端列表
    if (this.webSocketClient[item[this.clientKey]]) {
      console.log(item[this.clientKey] + "客户端已存在");
      this.webSocketClient[item[this.clientKey]].close();
    }
    console.log(item[this.clientKey] + "客户端已添加");
    this.webSocketClient[item[this.clientKey]] = item;
  };

  removeClient = (item) => {
    //设备断线时从客户端列表删除
    if (!this.webSocketClient[item[this.clientKey]]) {
      console.log(item[this.clientKey] + "客户端不存在");
      return;
    }
    console.log(item[this.clientKey] + "客户端已移除");
    this.webSocketClient[item[this.clientKey]] = null;
  };
};

说明:
1 创建 WebSocketServer 实例时,必须指定 port、server 或 noServer 选项中的一个且只能一个。
2 参数 { noServer: true } 的含义如下:
当设置 noServer: true 时,这表示创建一个不自动绑定到任何底层 HTTP/S 服务器的 WebSocket 服务器实例。
通常情况下,如果不设置这个参数或者设置为 false,创建的 WebSocket 服务器会自动尝试监听一个端口并处理传入的 HTTP 请求以进行 WebSocket 连接的升级。

const http = require('http');
const WebSocket = require('ws');

const server = http.createServer();

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

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

server.listen(8080);

在这个例子中,首先创建了一个普通的 HTTP 服务器 server。然后创建了一个 WebSocket 服务器 wss 并设置 noServer: true。当 HTTP 服务器接收到一个可能的 WebSocket 升级请求时,通过调用 wss.handleUpgrade 来处理这个请求,并将连接升级为 WebSocket 连接。

参考
1https://lonjinup.github.io/WebSocketManager/
2https://juejin.cn/post/6945057379834675230
3https://juejin.cn/post/7371365854012276747#heading-4

你好!要入门前端WebSocket,你需要了解以下几个方面: 1. WebSocket 是什么? WebSocket 是一种在客户端和服务器之间进行实时双向通信的协议。它与传统的 HTTP 请求不同,能够实现服务器向客户端推送数据,而不需要客户端发送请求。 2. 如何使用 WebSocket? 在前端,你可以使用 JavaScript 中提供的 WebSocket API 来建立 WebSocket 连接。通过创建一个 WebSocket 对象,指定连接的 URL,你可以连接到服务器并进行通信。 3. 建立 WebSocket 连接: 使用 JavaScript 的 `new WebSocket(url)` 方法可以创建一个 WebSocket 对象。`url` 参数是指定服务器的地址。例如,`ws://example.com/socket`。 4. WebSocket API: WebSocket 对象提供了一些常用的方法和事件,用于控制连接和处理数据。常见的方法包括:`send()` 用于向服务器发送数据,`close()` 用于关闭连接。常见的事件包括:`onopen` 连接建立时触发,`onmessage` 接收到消息时触发,`onclose` 连接关闭时触发等。 5. 服务器端处理: WebSocket 是一种双向通信协议,它需要在服务器端进行相应的处理。服务器端可以使用不同的编程语言来实现 WebSocket 的功能,如 Node.js 的 `ws` 模块或其他编程语言的相应库。 如果你是前端 WebSocket 的初学者,我建议你先了解 WebSocket 的基本概念和使用方法,然后通过实践来深入学习并体验其功能。你可以找一些示例代码来尝试建立连接、发送消息和接收消息等操作。希望对你有所帮助!如果你有更多问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值