前语

vue项目实时通信完成常用办法:
一、原生HTML5 WebSocket完成,vue中运用websocket

二、插件socket.io官网 ,Socket.io是一个WebSocket库,包括了客户端js和服务器端的nodejs,会自动依据浏览器从WebSocket、AJAX长轮询、Iframe流等等各种办法中选择最佳的办法来完成网络实时运用,最低支撑IE5.5; 说白了就是假如浏览器支撑websocket,那么socket.io就等同于websocket。当然 socket.io还用到了其它的技能来模拟websocket,所以当你运用socket.io的时候,不管浏览器是否支撑websocket,你都可以完成异步操作。运用留意:客户端运用了socket.io 服务端也必须对应运用

三、vue-socket.io是vue对socket.io的封装,运用办法与socket.io大同小异,核心参数仍是要参照socket.io官网相关装备;当时在运用中也存在部分坑

当时文章主要说vue-socket.io以下问题:

  1. 初始化大局挂载运用,事情订阅与毁掉(根本无坑,需留意订阅事情要对应毁掉,不然存在屡次订阅引发的音讯重复接纳问题)
  2. 带token验证的大局挂载办法(有坑,有解决方案与解决后运用留意事项)
  3. 组件内运用

安装

主张直接运用taobao镜像,淘宝镜像地址主张运用新地址,老地域名将于2022年05月31日零时起中止服务概况
nrm use taobao || npm config set registry registry.npmmirror.com/

npm i vue-socket.io@3.0.10 -S  //个人运用了当时版别

运用办法一 (官方用法)[大局挂载,不验证]

需留意:事情只要在订阅后才可接接纳回来音讯(订阅办法与后端约好称号)。

实例创立后,大局挂载实例办法,this.$socket,this.sockets。

其中事情订阅、撤销与监听办法在this.sockets封装;this.$socket是socket.io实例中io的封装。

// 大局挂载
// main.js  
import VueSocketIO from 'vue-socket.io'
Vue.use(new VueSocketIO({
  debug: true,// 出产环境封闭,翻开可在控制台检查socket衔接和事情监听的信息
  options: {
    autoConnect: false //创立时是否自动衔接,默许封闭,运用时用open敞开链接
  },
  connection: 'http://127.0.0.1:9527' //链接地址
}))  
//1.大局挂载-大局运用
//main.js  
newVue({
sockets:{
   connecting() { console.log('正在衔接') },
        connect() { console.log('衔接成功') },
        disconnect() { console.log('断开衔接') },
        connect_failed() { console.log('衔接失利') },
        error() { console.log('过错产生,而且无法被其他事情类型所处理') },
        reconnecting() { console.log('正在重连') },
        reconnect_failed() { console.log('重连失利') },
        reconnect() { console.log('重连成功') },
        welcome: data => {//大局监听订阅事情,需求与后端约好好
            console.log('welcome data', data)
        } 
} 
}) 
//2. 大局挂载-组件内运用
//demo.vue
<template> 
  <div> 
    <button @click="socketOpen">衔接Socket</button>
    <button @click="closeSocket">断开链接</button>
    <button @click="submsgContent(true)">订阅事情</button>
    <button @click="submsgContent(false)">撤销订阅事情</button>
    <button @click="socketSendmsg">发送数据</button>
    <button @click="lockResult">检查链接参数</button>
  </div>
</template>
<script>
export default {
  beforeDestroy() { //订阅事情记得要撤销---不然屡次订阅会引发屡次音讯回来
    if (!this.$socket) return
    this.sockets.unsubscribe('msgContent')
    this.$socket.close()
  },
  sockets: { //监听用的是this.sockets   发送音讯是this.$socket,不要弄混
        connecting() { console.log('正在衔接') },
        connect() { console.log('衔接成功') },
        disconnect() { console.log('断开衔接') },
        connect_failed() { console.log('衔接失利') },
        error() { console.log('过错产生,而且无法被其他事情类型所处理') },
        reconnecting() { console.log('正在重连') },
        reconnect_failed() { console.log('重连失利') },
        reconnect() { console.log('重连成功') },
        welcome: data => {//大局监听订阅事情,需求与后端约好好
            console.log('welcome data', data)
        }
  },
  methods: {
    socketOpen() { 
      this.$socket.open()// 开端衔接 socket
    },
    socketSendmsg() { // 发送音讯
      this.$socket.emit('hello', '这里是客户端')
    },
    lockResult() {
      console.log('链接状况', this.$socket.connected)
      console.log('this.$socket', this.$socket)
      console.log('this.sockets', this.sockets)
    },
    closeSocket() {
      this.$socket.close()
    },
    submsgContent(flag) {  
        if (flag) { //事情订阅
          this.sockets.subscribe('welcome', data => { //组件内监听
            console.log('组件内监听-welcome', data)
          })
        } else {//撤销订阅
          this.sockets.unsubscribe('welcome')
        } 
    }
  }
}
</script>  

运用办法二 (组件挂载运用)[可验证]

因在组件内运用,所以可将逻辑写在登录后加载挂载组件,大局或指定页面运用。

组件内创立留意事项:因vue-socket.io,在new.use()时挂载办法$socket到大局,在创立实例时挂载sockets监听目标到当时实例,所以,在组件中运用后会导致以下问题:

问题:
 1. $socket可正常运用,与$socket也就是io相关的办法都可正常运用(例如:this.$socket.open()、close等等...)
 2. sockets相关办法无法运用(例如:this.sockets.unsubscribethis.sockets.subscribe等等)
处理办法:运用socket.io原生办法,
 1. 事情订阅 
    实例.emitter.addListener
 2. 撤销订阅
    实例.emitter.addListener 
 //demo.vue
 <template>
  <div> 
    <button @click="createSocket">创立socket</button>
    <div v-if="createSocketIO ? true:false ">
      <button @click="socketOpen">衔接Socket</button>
      <button @click="closeSocket">断开链接</button>
      <button @click="submsgContent(true)">订阅事情</button>
      <button @click="submsgContent(false)">撤销订阅事情</button>
      <button @click="socketSendmsg">发送数据</button>
      <button @click="lockResult">检查链接参数</button>
    </div>
  </div>
</template>
<script> 
import VueSocketIO from 'vue-socket.io'
export default {
  data() {
    return {
      createSocketIO: null,
      createSocketEmitter: null
    }
  },
  beforeDestroy() { //订阅事情记得要撤销
    if (this.createSocketIO) {
      this.createSocketEmitter.removeListener('msgContent', this)
      this.createSocketIO.close()
    }
  },
  methods: {
   async createSocket() {//也可在页面初始话调用  
      let socketUrl
      /* // 动态ip与token完成,ip+端口可经过接口拿取,token及用户信息可经过vuex拿取
            const userId = store.getters.userInfo.userId
            const ipResult = await getHostIp()
            if (ipResult.code !== 0) return
            const { ip, port } = ipResult.data
            const protocol = window.location.protocol
            const socketUrl = `${protocol}//${ip}:${port}?userId=${userId}`
            console.log('socketUrl', socketUrl)
       */    
      socketUrl = 'http://127.0.0.1:9527'
      const createSocketItem = new VueSocketIO({
        debug: true,
        options: {
          autoConnect: false,//默许封闭,创立后翻开,组件内运用可直接翻开,就不需求用io.open()
          transports: ['websocket'], 
          query: {  
              token: 77777777777 //带着的额外参数也可经过url拼接完成
          }
        },
        connection:socketUrl 
      })
      const { io, emitter } = createSocketItem
      io.query.ttt = 8888888888
      this.createSocketIO = io
      this.createSocketEmitter = emitter
      io.open()
      io.on('connecting', () => { console.log('正在衔接---888') })
      io.on('connect', () => { console.log('衔接成功---888') })
      io.on('disconnect', () => { console.log('断开衔接---888') })
      io.on('connect_failed', () => { console.log('衔接失利---888') })
      io.on('error', () => { console.log('过错产生,而且无法被其他事情类型所处理') })
      io.on('reconnect_attempt', () => {console.log('触发测验重新衔接', 888)})
      io.on('reconnecting', () => { console.log('正在重连---888') })
      io.on('reconnect_failed', () => { console.log('重连失利---888') })
      io.on('reconnect', () => { console.log('重连成功---888') })
      emitter.addListener('welcome', (data) => {
        console.log('data', data)
      }, this) 
    },
    socketOpen() {
      this.createSocketIO.open()
    },
    socketSendmsg() { // 发送音讯
      this.createSocketIO.emit('hello', '这里是客户端')
    },
    lockResult() {
      console.log('链接状况', this.createSocketIO.connected)
    },
    closeSocket() {
      this.createSocketIO.close()
    },
    submsgContent(flag) {
      if (flag) {
        this.createSocketEmitter.addListener('welcome', (data) => {
          console.log('data', data)
        }, this)
      } else {
        this.createSocketEmitter.removeListener('welcome', this)
      } 
    }
  }
}
</script> 

运用办法三 (大局挂载运用)[可验证]

当时办法完成类似直接运用socket.io,根本相当于仅仅运用了vue-socket.io的库与大局挂载的this.$scoket办法

注: vue.use( new VueSocketIO({connection:'http://127.0.0.1:9527'})) 只将实例中io模块挂载了this.$scoket

完成思路与遭受问题:

  完成:
    经过本地轮询,实时获取用户登录状况,用户登录后挂载大局办法
  问题:
    同完成办法二问题,vue-socket.io,在new.use()时挂载办法$socket到大局(io模块),在创立实例时挂载sockets监听目标到当时实例;在轮询拿到用户当时挂载相关办法时,当时项目实例现已被创立,能拿到this.$socket,不能拿到this.sockets;需求运用页面后置加载,或重新加载后正常,挂载办法才干正常运用
  解决办法:
    在创立实例时,将除io,模块的事情(emitter)与监听(listener)也挂载到大局,运用相关界说办法在mounted生命周期中进行;登录前尽量把数据存储好后在进入体系

运用示例:

//main.js 进口文件大局挂载
// import Vue from 'vue'
import router from '@/router'
import store from '@/store'
import App from './App'  
import '@/util/socket.js'  //运用大局挂载
Vue.config.productionTip = false
const saasVue = new Vue({
  router,
  store,
  i18n,
  render: h => h(App)
}).$mount('#app')
window.saasVue = saasVue 
// socket.js 大局办法完成
import Vue from 'vue'
import VueSocketIO from 'vue-socket.io'
import store from '@/store'
import { getHostIp } from '@/api/admin/message' //获取后端动态ip地址
let socketIo, socketListener, socketEmitter
if (!getToken()) {
  const timer = setInterval(() => {
    console.count()
    if (!getToken()) return
    initSocket(getToken())
    window.clearInterval(timer)
  }, 1000)
} else { 
  initSocket(getToken())
}
function getToken() {//获取登录标识---请修改为自己项目标识
  return store.getters.access_token
}
async function initSocket(token) {
  const userId = store.getters.userInfo.userId //获取登录标识---请修改为自己项目用户id
  const ipResult = await getHostIp()
  if (ipResult.code !== 0) return
  const { ip, port } = ipResult.data
  const protocol = window.location.protocol
  const socketUrl = `${protocol}//${ip}:${port}?userId=${userId}`
  console.log('socketUrl', socketUrl)
  //const socketUrl = 'http://127.0.0.1:9527' //本地测验地址---nodejs服务代堆放后边
  const socket = new VueSocketIO({
    debug: process.env.NODE_ENV !== 'production',
    options: {
      autoConnect: false //现已过验证,大局运用可默许翻开,组件内运用则默许封闭,运用时在翻开
    },
    connection: socketUrl
  })
  const { emitter, io, listener } = socket
  socketIo = io
  socketListener = listener
  socketEmitter = emitter
  if (process.env.NODE_ENV !== 'production') {// 与socket链接相关大局处理,与后端预订界说事情在组件内订阅运用
    io.on('connecting', () => { console.log('socketjs---正在衔接') })
    io.on('connect', () => { console.log('socketjs---衔接成功') })
    io.on('disconnect', () => { console.log('socketjs---断开衔接') })
    io.on('connect_failed', () => { console.log('socketjs---衔接失利') })
    io.on('error', () => { console.log('socketjs---过错产生,而且无法被其他事情类型所处理') })
    io.on('reconnect_attempt', () => { console.log('socketjs---触发测验重新衔接')})
    io.on('reconnecting', () => { console.log('socketjs---正在重连') })
    io.on('reconnect_failed', () => { console.log('socketjs---重连失利') })
    io.on('reconnect', () => { console.log('socketjs---重连成功') })
  }
  //   Vue.use(socket) //仅仅挂载了io模块,存在已加载页面无法订阅问题,不如不运用,自己挂载便利
  Object.defineProperty(Vue.prototype, '$socketIo', { value: socketIo })
  Object.defineProperty(Vue.prototype, '$socketListener', { value: socketListener })
  Object.defineProperty(Vue.prototype, '$socketEmitter', { value: socketEmitter })
}
export default { socketIo, socketListener, socketEmitter } 

组件内运用:

//demo.vue  订阅事情与毁掉在组件中进行
<template>
  <div> 
    <button @click="socketOpen">衔接Socket</button>
    <button @click="closeSocket">断开链接</button>
    <button @click="submsgContent(true)">订阅事情</button>
    <button @click="submsgContent(false)">撤销订阅事情</button>
    <button @click="socketSendmsg">发送数据</button>
    <button @click="lockResult">检查链接参数</button>
  </div>
</template>
<script>
export default {
  beforeDestroy() { //订阅事情记得要撤销
    if (this.$socketIo) {
      this.$socketEmitter.removeListener('msgContent', this)
      this.$socketIo.close()
    }
  },
  mounted() {
    this.$socketIo.open()//初始化翻开链接 
    this.$socketEmitter.addListener('welcome', (data) => {//组件初始化挂载后,订阅后端事情
      console.log('data', data)
    }, this)
  },
  methods: {
    socketOpen() {
      this.$socketIo.open() 
    },
    socketSendmsg() { // 发送音讯
      this.$socketIo.emit('hello', '这里是客户端')
    },
    lockResult() {
      console.log('链接状况', this.$socketIo.connected)
    },
    closeSocket() {
      this.$socketIo.close()
    },
    submsgContent(flag) {
      if (flag) {
        this.$socketEmitter.addListener('welcome', (data) => {
          console.log('data', data)
        }, this)
      } else {
        this.$socketEmitter.removeListener('welcome', this)
      } 
    }
  }
}
</script> 

运用办法推荐

运用办法一:适用于大局告诉类业务类型多,不验证用户登录,一般是门口网站引流类型运用。
办法二:适用于告诉类型较少的业务项目
办法三:适用项目告诉类型多,业务场景模块较多情况,大局挂载,每个页面只需求做对应办法订阅,与毁掉;例如:体系音讯+业务音讯待办告诉,首屏监控、大屏展现下多个模块多订阅场景。 

nodejs服务端本地demo代码

相关依赖:npm install socket.io@2.0.4 -S
// nodeSocket.js
var http = require('http')
var io = require('socket.io')
// 创立server服务
var server = http.createServer(function(req, res) {
  var headers = {}
  headers['Access-Control-Allow-Origin'] = '*'
  headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, DELETE, OPTIONS'
  headers['Access-Control-Allow-Credentials'] = true
  headers['Access-Control-Max-Age'] = '86400' // 24 hours
  headers['Access-Control-Allow-Headers'] = 'X-Requested-With, Access-Control-Allow-Origin, X-HTTP-Method-Override, Content-Type, Authorization, Accept'
  res.writeHead(200, headers)
  res.end()
})
// 发动服务器  监听 1024 端口
server.listen(1024, function() {
  console.log('server runing at 127.0.0.1:1024')
})
// 发动socket服务
var socket = io.listen(server, { origins: '*:*' })
socket.use((socket, next) => {
  const query = socket.handshake.query
  console.log('token', query.token)
  if (query.token) {
    return next()
  }
  return next(new Error('authentication error 鉴权失利'))
})
// 监听客户端衔接
socket.on('connection', function(socket) {
  console.log('客户端有衔接') 
  // 监听客户端断开
  socket.on('disconnect', () => {
    console.log('客户端断开')
  })
  // 给客户端发送音讯
  setInterval(() => {
    socket.emit('welcome', '欢迎衔接socket')
    console.count(1)
  }, 2000)
  // 监听客户端音讯
  socket.on('hello', data => {
    console.log('接纳客户端数据---:', data)
  })
}) 
发动:node nodeSocket.js

总结

  1. vue-socket.io运用版别留意,3.0.8、3.0.9有部分bug,感兴趣的可以去试试;
  2. nodejs模块运用的socket.io版别留意,socket.io版别大于了2.0.4后运用办法有改变;
  3. 音讯订阅同一办法可屡次订阅,订阅几回,后端一次回来了几回订阅音讯。所以留意撤销订阅办法。
  4. 同一订阅办法,在不同页面运用,毁掉后会撤销一切运用当时办法的订阅音讯。需求不同模块与后端约好不同办法,依据项目音讯架构处理;

初次发表文章,如有过错,请多多谅解,不喜勿喷!