websocket简介及上手,node + vue实现websocket服务

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

websocket简介及上手

1.websocket初识

WebSocket 是 HTML5 提供的一种全双工通讯的协议类似于http同样建立在TCP上的传输协议被称为ws,加密传输称为wss。

WebSocket 使得客户端和服务器之间的数据交换变得简单些服务端可以主动向客户端推送数据而在传统的http协议中服务端是不能主动推送数据给客户端的可以保证数据的实时性。在 WebSocket API 中浏览器和服务器只需要完成一次握手两者之间就可以建立持久性的连接并进行双向数据传输。

在这里插入图片描述
2.websocket优势

传统的http协议如果想要做数据及时更新应用那么只能使用轮询的方式不断的发起请求得到数据而这个发起请求得到数据的过程会浪费很多带宽资源而且服务端不能主动传数据给客户端这是因为http协议中请求是无状态的即一次发起请求到结束响应后就会和服务端断开连接每次请求中请求头和响应头都带有很多无用数据那么这将大大浪费带宽及服务器压力而websocket只在建立连接的时候会携带不常用信息之后建立长连接后就可以保持有状态此时可以由服务器主动推送消息等现在很多实时更新数据的应用都是利用websocket协议开发的例如聊天室等应由。

3.前端项目中使用WebSocket:

封装WebSocket类

前端项目vue中使用websocketwindow对象提供了WebSocket对象使用时创建一个对象即可为了方便起见将这一部分装为一个类在vue项目main.js中创建一个实例即可具体分装如下

export default class WebSocketClient {
  // 单例模式创建websocket客户端
  static instance = null

  // 方法前面加get调用时不用加小括号判断此类是否被创建了对象如果创建了的话就直接return
  static get Instance () {
    if (!this.instance) {
      this.instance = new WebSocketClient()
    }
    return this.instance
  }

  // ws为websocket实例对象下面会赋WebSocket对象寄存器地址给ws
  ws = null

  // 定义一个是否连接成功的标识来解决当服务没有连接就请求数据报错的bug
  connected = false

  // 记录重连ws服务器的次数可用于延迟发送请求和延迟掉线重连服务
  reConnectCount = 0

  //  临时回调函数容器,存储回调函数用来接收数据以socket匹配名称为key函数寄存器地址为value
  callBackMap = {}

  // 连接服务器
  // 掉线重新连接定时器id
  timeId = null
  connect () {
    if (!window.WebSocket) {
      return console.log('浏览器不支持WebSocket')
    }
    // 创建连接先判断当前浏览器是否支持WebSocket,支持的话创建websocket客户端并将寄存器地址赋值给ws变量
    if ('WebSocket' in window) {
      // 创建websocket客户端对象并连接到指定服务器(后端项目所在服务器ip才可以访问到否则连接失败!)
      this.ws = new WebSocket('ws://127.0.0.1:3001')
      //可以直接绑定域名
      // this.ws = new WebSocket('ws://******.com:3001')
    } else {
      alert('您的浏览器不支持Websocket通信协议请使用Chrome或者Firefox浏览器')
    }

    // 监听客户端与服务端连接成功
    this.ws.onopen = () => {
      // 将是否连接的状态修改为已经连接成功
      this.connected = true
      // 并将掉线重连次数重新赋值为0
      this.reConnectCount = 0
      // 连接成功后将掉线重连定时器停止
      clearInterval(this.timeId)
      console.log('连接websocket服务器成功')
    }

    // 监听服务器主动断开或连接失败
    this.ws.onclose = () => {
      // 当服务器主动断开或连接失败时将是否连接状态修改为未连接并将掉线重连次数累加
      this.connected = false
      this.reConnectCount++
      console.log('服务器主动断开了连接')
      // 当监听到与服务端断开后每隔一段时间调用自己connect尝试重新连接服务器,当连接成功后在监听连接成功的事件中停止定时器
      this.timeId = setTimeout(() => {
        this.connect()
      }, this.reConnectCount * 1000)
    }

    // 监听服务器端发送数据过来
    this.ws.onmessage = response => {
      const responseData = JSON.parse(response.data)
     // 拿到后端返回数据中的websocket标识符api的值调用回调函数将数据传给回调函数
      const api = responseData.api
      if (this.callBackMap[api]) {
        this.callBackMap[api].call(this, responseData)
      }
    }
  }
  // 请求的回调函数

  addCallBack (socketType, callBakc) {
    this.callBackMap[socketType] = callBakc
  }
  // 移出存储回调函数中的某个函数

  removeCallBack (socketType) {
    console.log('移出回调函数')
    this.callBackMap[socketType] = null
  }

  // 定义发送请求数据的方法request为请求对象格式{api:'要匹配的接口', body:{请求体对象}}
  send (request) {
    // 因为我的接口中需要token验证所以我们将每次请求都加上token值
    const token = window.sessionStorage.getItem('token')
    const obj = { token: token, ...request }
    // 当ws服务连接成功后才可以发送请求
    if (this.connected) {
      this.reConnectCount = 0
      this.ws.send(JSON.stringify(obj))
    } else {
      this.reConnectCount++
      // 否则延迟this.reConnectCount * 500ms再次尝试请求数据,这里不写死时间是因为当多次连接是每次将时间拉长可以减少服务压力
      setTimeout(() => {
        this.ws.send(JSON.stringify(obj))
      }, this.reConnectCount * 1000)
    }
  }
}

main.js中创建WebSocket实例并挂载到vue全局

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
// 导入分装的websocket类
import websocket from './websocket'
import '@/assets/iconfont/iconfont.css'
import './assets/css/global.less'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import axios from 'axios'
axios.interceptors.request.use(config => {
  config.headers.authorization = window.sessionStorage.getItem('token')
  return config
})
Vue.use(ElementUI)
Vue.prototype.$http = axios
// 通过单例模式连接ws服务器
websocket.Instance.connect()
// 将websocket挂载到vue原型上
Vue.prototype.$ws = websocket.Instance

Vue.config.productionTip = false
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

注意 在vue.config.js中升级ws协议不升级的话上线后无法连接服务端ws服务如

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    // 升级ws协议不配置的话连接不上
    host: 'localhost',
    port:3001,
    client: {
      webSocketURL: 'ws://0.0.0.0:3001/',
    },
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
    // 配置后端项目反向代理服务器
    proxy: {
      '/api': {
        target: 'http://127.0.0.1:3000/', // 对应自己的接口地址
        changeOrigin: true,
        ws: true,
        pathRewrite: {
          '/api': '/api'
        }
      },
      '/spublic': {
        target: 'http://127.0.0.1:3000/', // 对应自己的静态图片地址
        changeOrigin: true,
        ws: true,
        pathRewrite: {
          '/spublic': '/spublic'
        }
      }
    }
  }
})

vue组件中使用websocket请求数据

<script>
export default {
  mounted () {
    // 组件创建或挂载时调用addCallBack方法注册回调,将第一个参数作为key 参数二为回调函数寄存器地址存到WebSocket中callBackMap对象中当服务端有数据发送过来其中api对应的值和注册时key值相同是调用回调函数接收数据addCallBack('devicedata', this.sendgetdatamethods)中devicedata是每个请求的唯一标识可以理解为api匹配url如'/api/login'
    this.$ws.addCallBack('devicedata', this.sendgetdatamethods)
  },
  destroyed () {
    // 组件销毁时将注册的回调函数销毁掉
    this.$ws.removeCallBack('devicedata')
  },
  methods: {
    // 接收数据的回调函数参数data是服务端返回的数据服务器发送数据过来如果唯一标识匹配注册时的唯一标识devicedata就会触发调用此回调函数
    sendgetdatamethods (data) {
      console.log(data)
    },
    // 发送数据给服务端获取数据如果后端支持的话可以自动推送数据
    sendMsgHandle () {
      const obj = { api: 'devicedata', body: {id: 1} }
      this.$ws.send(obj)
    }
  }
}
</script>

4.后端node.js项目中使用websocket

websocket配置 node.js中默认没有websocket服务需要引入第三方包进行开发第三方包有好几种例如socket.io、nodejs-websocket、ws等这里我使用ws封装了websocket服务如下

// 导入查询数据的方法
const queryData = require('../serves/websocketserve/websocketapi')
// 引入ws模块
const Ws = require('ws')

// 导出websocket服务配置
module.exports = () => {
  // 创建websocket服务
  const wsServe = new Ws.Server({
    // 必须配置ip地址否则上线后连接不上
    host: "127.0.0.1",
    port: 3001
  })
  // 监听客户端连接
  wsServe.on('connection',client => {
    console.log('有客户端接入了')
    // 监听客户端有消息发送过来
    client.on('message',async msg => {
      console.log('有客户端消息发送过来了')
      console.log(msg)
      const request = JSON.parse(msg)
      try {
        // 调用查询数据的方法从数据库中查询数据
        const response = await queryData(request)
        // 将查到的数据返回给客户端
        client.send(JSON.stringify(response))
      } catch (err) {
        client.send(JSON.stringify({cod:404,msg:'获取数据失败'}))
      }

      // wsServe.clients中存所有当前连接的客户端可以用来做广播
      wsServe.clients.forEach(itemClient => {
        itemClient.send('发送数据到当前已连接的所有websocket客户端')
      })
    })
  })
}

查询数据的方法queryData文件websocketapi代码如下
这个文件可以配置需要进行websocket通信的api接口如

// 引入token验证
const {verifyToken} = require('../../commethods/creattoken')
// 引入数据库配置
const connection = require('../../config/mysqldbconfig')
// 引入动态数据库配置
const dataConnection = require('../../config/mysqldbdataconfig')
// 导入时间格式化函数
const dateFormatter2 = require('../../commethods/dateformat2')

// 导出查询到的数据参数request.api是前端传过来唯一标识可以理解为api接口url即'/api/login'只是只拿login当与下面某个条件匹配时调用匹配的函数查询数据并返回给调用处之后websocket服务将调用处的数据响应给客户端
module.exports = (request) => {
  // 查询表格实时值请求
  if (request.api === 'devicedata') {
    return devicedata(request)
  }
  // 查运维数据界面实时值请求
  if (request.api === 'getinstpr') {
    return getinstpr(request)
  }
}

// 查时刻值方法这里做测试我简单查下数据库数据即可实际开发中这里可以利用异步查询多个数据在resolve返回
function devicedata (request) {
  const {token} = request
  let isOk = verifyToken(token)
  const {id} = request.body
  // return 一个Promsie
  return new Promise((resolve,reject) => {
    isOk.then(() => {
      let sql = 'SELECT * FROM channe_name WHERE pid = "'+paramsn+'" LIMIT 1'
      connection.query(sql,(error,data)=>{
        try {
          if (error) {
            throw error
          } else {
            // 响应数据时也要加上唯一标识api:XXXXX用于前端调用回调函数拿到响应数据
            resolve({api:'devicedata', cod:200,msg:'获取实时值数据成功',valueArray: data})
          }
        } catch(err){
          console.log('通道名称接口错误'+err)
        }
      })
    }).catch(() => {
      reject({api:'devicedata', cod:201,msg:'获取数据失败'})
    })
  })
}

// 查运维数据界面实时值请求方法这里做测试只是将上面方法复制到这里并修改了需要修改的参数可加深理解
function getinstpr (request) {
  const {token} = request
  let isOk = verifyToken(token)
  const {id} = request.body
  // return 一个Promsie
  return new Promise((resolve,reject) => {
    isOk.then(() => {
      let sql = 'SELECT * FROM instr_data WHERE pid = "'+paramsn+'" LIMIT 1'
      connection.query(sql,(error,data)=>{
        try {
          if (error) {
            throw error
          }else{
            resolve({api:'getinstpr', cod:200,msg:'获取实时值数据成功',valueArray: data})
          }
        } catch(err){
          console.log('通道名称接口错误'+err)
        }
      })
    }).catch(() => {
      reject({api:'getinstpr', cod:201,msg:'获取数据失败'})
    })
  })
}

在后端入口文件中开启websocket服务

const express = require('express')

// 引入websocket配置
const WebSocketServe = require('./config/websocketconfig')
// 开启websocket服务
WebSocketServe()


const app = express()
app.listen(3000,'127.0.0.1',()=>{
  console.log('serve is running...');
})

5.配置nginx

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    client_max_body_size 10m;

    server {
        # 配置websocket代理
        listen       3001;
        # 下面两项必须配置才可支持websocket服务
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        location / {
            proxy_pass  http://127.0.0.1:3001; #设置接口代理服务器
        }

    }
}

提示本文图片等素材来源于网络若有侵权请发邮件至邮箱810665436@qq.com联系笔者 删除。

笔者苦海

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: vue