echarts项目学习笔记——WebSocket

背景

解决http协议通讯延迟的问题,后端数据有变化无法主动向前端发送数据,实现多个客户端数据的实时联动响应。该项目使用技术为 vue + echarts + nodejs

知识点

WebSocket是一种协议,设计用于提供低延迟、全双工和长期运行的连接

  • 全双工:是一种通信方式,通信的两个参与方可以同时发送和接收数据,不需要等待对方的响应或传输完成

  • 实时通信:即时消息传递、音视频通话、在线会议和实时数据传输等,可以实现即时的数据传输和交流,不需要用户主动请求或刷新来获取更新数据

之前解决通讯延时的方法:

  • 轮询:客户端定期向服务器发送请求

  • 长轮询:客户端发出请求后,保持连接打开,等待新数据响应后再关闭连接

  • Comet:保持长连接,在返回请求后继续保持连接打开,基于HTTP的模型

  • 缺点:造成不必要的网络开销和延迟

基本使用

后端

  1. 安装 :npm i ws -S

  2. 使用:
    // 创建对象
    const WebSocket = require('ws')
    const wss = new WebSocket({ port: 9998 })
    
    // 监听连接事件
    wss.on('connection',client => {
        console.log('有客户端连接成功')
        // 监听接收数据事件
        wss.on('message',msg => {
            console.log('客户端发来了数据' + msg)
        })
        // 发送数据
        client.send('hello socket from backend')
    })

前端

// WebSocket 是 window 对象提供的,不需要安装额外的包
const ws = new WebSocket('ws://localhost:9998')
// 连接成功事件
ws.onopen = () => {
    console.log('连接服务端成功了....')
}
// 接收数据事件
ws.onmessage = msg => {
    console.log('接收到服务端发送过来的数据了'+msg.data)
}
// 关闭连接事件
ws.onclose = () => {
    console.log('连接服务器失败')
}
// 发送数据
send.onclick = function() {
    ws.send('hello socket from frontend')
}

项目实战

基于已完成的之前使用http请求的 echart 项目修改

后端

const WebSocket = require('ws')
const wss = new WebSocket.Server({ port: 9998 })
const path = require('path')
const fileUtil = require('../utils/file_util')
module.exports.listen = () => {
  wss.on('connection', (client) => {
    console.log('客户端连接成功')
    client.on('message', async (msg) => {
      const payload = JSON.parse(msg)
      const action = payload.action
      if (action === 'getData') {
        let file_path = '../data/' + payload.chartName + '.json'
        file_path = path.join(__dirname, file_path)
        const ret = await fileUtil.getFileJsonData(file_path)
        // 将前端需要的数据存储在 data 字段下,和前端传入的数据一起再以字符串的形式返回给前端
        payload.data = ret
        client.send(JSON.stringify(payload))
      } else {
        // 如果不需要读取数据,则直接将前端传入的字符串数据传给其他所有客户端
        wss.clients.send(msg)
      }
    })
  })
}
// app.js
const WebSocket = require('./service/web_socket_service')
WebSocket.listen()

前端

export default class SocketService {
  static instance = null
  static get Instance() {
    if (!this.instance) {
      // 这里一定要 return 返回
      return (this.instance = new SocketService())
    } else {
      return this.instance
    }
  }

  callBackMapping = {}
  ws = null
  connect() {
    if (!window.WebSocket) {
      return console.log('您的浏览器不支持WebSocket')
    }
    this.ws = new WebSocket('ws://localhost:9998')
    this.ws.onopen = () => {
      console.log('服务器连接成功')
    }
    this.ws.onclose = () => {
      console.log('服务器关闭')
    }
    this.ws.onmessage = (msg) => {
      // msg 返回的是一个消息事件,里面的 data 才是需要的数据,然后通过 JSON.parse 将字符串解析为js对象
      const ret = JSON.parse(msg.data)
      console.log(ret.data)
      // 实际需要的数据
      const realData = ret.data
      if (ret.action === 'getData') {
        if (this.callBackMapping[ret.socketType]) {
          // 根据socketType调用对应组件注册的函数,将返回的数据作为参数传入
          this.callBackMapping[ret.socketType].call(this, realData)
        } else {
          console.log('没有找到函数')
        }
      } else if (ret.action === 'fullScreen') {
        // console.log(ret)
      } else if (ret.action === 'themeChange') {
        // console.log(ret)
      }
    }
  }

  registerCallBack(socketType, callback) {
    this.callBackMapping[socketType] = callback
  }

  unRegisterCallBack(socketType) {
    this.callBackMapping[socketType] = null
  }

  send(data) {
    this.ws.send(JSON.stringify(data))
  }
}

笔记思路

  1. 创建 utils/socket_service.js 文件,里面默认导出 SocketService 类

  2. 在类里面用单例模式,使项目中只有一个 socketservice 实例对象

    原理是调用静态成员,判断当前是否有实例对象,如果没有则创建一个,如果有则返回已有的实例对象

  3. 里面有4个方法,和后端连接的 connect 方法,组件用来注册回调函数的 registerCallBack 方法,组件销毁的时候注销回调函数的 unRegisterCallBack 方法,组件向服务器发送数据的 send 方法

    1. connect 方法,首先判断浏览器是否支持 WebSocket,然后创建 ws 对象,监听开启数据传输服务器关闭的事件,由于其他方法会调用 ws 对象的方法,所有将 ws 提前声明

    2. registerCallBack 方法,需要传入2个参数,用来标注是哪个组件的回调函数的 socketType 和 回调函数 callback,提前声明存储所有回调函数的对象 callBackMapping

    3. unRegisterCallBack 方法,传入1个参数,需要清除的回调函数的标注 socketType

    4. send 方法,传入1个参数,执行向服务器传送数据的行为

  4. 绑定原型,为了方便组件使用注册函数和发送数据,将 WebSocket 实例对象 $socket 绑定到 vue 的原型上

  5. 组件改造,在 created 周期里将当前组件的获取数据后执行的函数注册在 SocketService 类里的 callBackMapping 属性上;在 mounted 周期里,执行 $socket 里的发送数据到服务器;将获取数据后执行的函数修改为,接收参数 res,通过 JSON.stringify 将 res 解析为 js 对象后使用;destroyed 周期里,注销存储在 SocketService 类里的执行函数

  6. 执行逻辑:SocketService 作为一个和后端连接的中转站,开启和后端的连接;项目中所有需要和后端有交互的,都需要先将拿到数据后的执行函数注册在这个中转站里面;中转站将自己的所有方法都放在一个公共区域,所有需要发送数据到后端的组件,都可以从公共区域(即绑定在Vue原型上的$socket)调用发送数据的方法;组件销毁后要注销放在中转站里面的执行函数;中转站通过监听后端开启、发送数据、关闭来执行操作;后端传输数据首先被中转站接收,中转站对接收的数据字段与存储的执行函数进行比对后调用

  • 19
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值