• webSocket学习与使用


    1.webSocket的简介

    WebSocket 可以保持着浏览器和客户端之间的长连接, 通WebSocket 可以实现数据由后端推送到前端,保了数据传输的实时性。

    2.后端代码的改造(以koa2为例)

    koa2博客

    • 安装 WebSocket 包

      npm i ws -S

    • 创建 WebSocket 实例对象

     const WebSocket = require("ws") 
      // 创建出WebSocket实例对象 
      const wss = new WebSocket.Server({ 
      ​      port: 9998 
      })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 监听事件

      demo

    wss.on("connection", client => { 
      ​	console.log("有客户端连接...") 
      ​	client.on("message", msg => { 
      ​		console.log("客户端发送数据过来了")// 发送数据给客户端 
      ​	client.send('hello socket')}) 
      })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    现实使用的

    	web_socket_service.js
    
      const path = require('path')
      const fileUtils = require('../utils/file_utils')
      const WebSocket = require('ws')
      // 创建WebSocket服务端的对象, 绑定的端口号是9998
      const wss = new WebSocket.Server({
        port: 9998
      })
      // 服务端开启了监听
      module.exports.listen = () => {
        // 对客户端的连接事件进行监听
        // client:代表的是客户端的连接socket对象
        wss.on('connection', client => {
          console.log('有客户端连接成功了...')
          // 对客户端的连接对象进行message事件的监听
          // msg: 由客户端发给服务端的数据
          client.on('message',async msg => {
            console.log('客户端发送数据给服务端了: ' + msg)
            let payload = JSON.parse(msg)
            const action = payload.action
            if (action === 'getData') {
              let filePath = '../data/' + payload.chartName + '.json'
              // payload.chartName // trend seller map rank hot stock
              filePath = path.join(__dirname, filePath)
              const ret = await fileUtils.getFileJsonData(filePath)
              // 需要在服务端获取到数据的基础之上, 增加一个data的字段
              // data所对应的值,就是某个json文件的内容
              payload.data = ret
              client.send(JSON.stringify(payload))
            } else {
              // 原封不动的将所接收到的数据转发给每一个处于连接状态的客户端
              // wss.clients // 所有客户端的连接
              wss.clients.forEach(client => {
                client.send(msg)
              })
            }
            // 由服务端往客户端发送数据
            // client.send('hello socket from backend')
          })
        })
      }
    
      app.js
    
      // 服务器的入口文件
      // 1.创建KOA的实例对象
      const Koa = require('koa')
      const app = new Koa()
      // 2.绑定中间件
      // 绑定第一层中间件
      const respDurationMiddleware =  require('./middleware/koa_response_duration')
      app.use(respDurationMiddleware)
      // 绑定第二层中间件
      const respHeaderMiddleware = require('./middleware/koa_response_header')
      app.use(respHeaderMiddleware)
      // 绑定第三层中间件
      const respDataMiddleware = require('./middleware/koa_response_data')
      app.use(respDataMiddleware)
      // 3.绑定端口号 8888
      app.listen(8888)
    
      const webSocketService = require('./service/web_socket_service')
      webSocketService.listen()
       // 开启服务端的监听, 监听客户端的连接
      // 当某一个客户端连接成功之后, 就会对这个客户端进行message事件的监听
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    - 前端测试代码
    
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
      </head>
      <body>
        <button id="connect">连接</button>
        <button id="send" disabled="true">发送数据</button> <br>
        从服务端接收的数据如下: <br>
        <span id="recv"></span>
        <script>
          var connect = document.querySelector('#connect')
          var send = document.querySelector('#send')
          var recv = document.querySelector('#recv')
          let ws = null
          connect.onclick = function(){
            ws = new WebSocket('ws://localhost:9998')
            ws.onopen = () => {
              console.log('连接服务端成功了...')
              send.disabled = false
            }
            ws.onclose = () => {
              console.log('连接服务器失败')
              send.disabled = true
            }
            ws.onmessage = msg => {
              console.log('接收到从服务端发送过来的数据了')
              console.log(msg)
              recv.innerHTML = msg.data
            }
          }
          send.onclick = function(){
            ws.send(JSON.stringify({
              action: 'themeChange',
              socketType: 'themeChange',
              chartName: '',
              value: 'chalk'
            }))
          }
        </script>
      </body>
      </html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    3.前端响应(vue实例案例)

    • 创建 src/utils/socket_service.js 文件
    • 定义单例
      - export default class SocketService { 
        /**
        \* 单例 
        */ 
    	    static instance = null 
    	    static get Instance () { 
    		    if (!this.instance) { 
    		    	this.instance = new SocketService() 
    		    }
    	    return this.instance 
    	    } 
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 监听 WebSocket 事件
      • 定义 connect 函数,将创建的 WebSocket 赋值给实例属性
    export default class SocketService { 
    ...... 
    	// 实例属性 
    	ws = null 
    	// 初始化连接websocket 
    	connect () { 
    		if (!window.WebSocket) { 
    			return console.log('您的浏览器不支持 WebSocket!') 
    		}
    		this.ws = new WebSocket('ws://localhost:9998') 
    	} 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 监听事件
      connect () { 
    	  if (!window.WebSocket) { 
    	  	return console.log('您的浏览器不支持 WebSocket!') 
    	  }
    	  this.ws = new WebSocket('ws://localhost:9998') 
    	  // 监听连接成功 
    	  this.ws.onopen = () => { 
    	  	console.log('WebSocket 连接成功') 
    	  }
    	  // 1.服务器连接不成功 2.服务器关闭了连接 
    	  this.ws.onclose = e => { 
    	  	console.log('服务器关闭了连接') 
    	  }
    	  // 监听接收消息 
    	  this.ws.onmessage = msg => { 
    	 	 console.log('WebSocket 接收到数据') 
    	  } 
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 定义注册函数

      记录一下当得到数据时, 应该调用的函数回调

    export default class SocketService { 
    	  // 业务类型和回调函数的对于关系 
    	  callBackMapping = {} 
    	  /**
    	
    	  \* socketType 
    	
    	  \* trendData sellerData mapData rankData hotData stockData 
    	
    	  \* fullScreen 
    	
    	  \* themeChange 
    	
    	  \* callBack 
    	
    	  \* 回调函数 
    	
    	  */ 
    	  registerCallBack (socketType, callBack) { 
    	  // 往 callBackMap中存放回调函数 
    	  	this.callBackMapping[socketType] = callBack 
    	  }
    	  unRegisterCallBack (socketType) { 
    	  	this.callBackMapping[socketType] = null 
    	  } 
      } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 连接服务端

      • 在 main.js 中连接服务器端
    import SocketService from '@/utils/socket_service' 
    SocketService.Instance.connect() 
    
    • 1
    • 2
    • 将 SocketService 实例对象挂载到 Vue 的原型对象上
    Vue.prototype.$socket = SocketService.Instance 
    
    • 1
    • 发送数据给服务端

      • 在 socket_service.js 中定义发送数据的方法
      export default class SocketService { 
    	    ...... 
    	    send (data) { 
    	   		 console.log('发送数据给服务器:') 
    	    this.ws.send(JSON.stringify(data)) 
    	
    	    } 
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • vue组件注册回调函数
     created () { 
        // 当socket来数据的时候, 会调用getData这个函数 
        	this.$socket.registerCallBack('trendData', this.getData) 
        } 
    
    • 1
    • 2
    • 3
    • 4
    • destroyed 中取消注册
     destroyed () { 
        	this.$socket.unRegisterCallBack('trendData') 
        } 
    
    • 1
    • 2
    • 3
    • mounted 中往 socket 发送数据
      mounted () { 
    	    this.initChart() 
    	    // this.getData() 先将getData的调用注释起来 
    	    this.$socket.send({ 
    		    action: 'getData', 
    		    socketType: 'trendData', 
    		    chartName: 'trend' 
    	    })
    	    ...... 
        }, 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 运行代码, 发现数据发不出去
      因为在刷新界面之后, 客户端和服务端的连接并不会立马连接成功, 在处于连接状态下就调用,send 是发送不成功的, 因此需要修改 service_socket.js 中的 send 方法进行容错处理 。
      // 是否已经连接成功 
        connected = false 
        sendRetryCount = 0 
        send (data) { 
    	    console.log('发送数据给服务器:') 
    	    if (this.connected) { 
    		    this.sendRetryCount = 0 
    		    this.ws.send(JSON.stringify(data)) 
    	    } else { 
    		    setTimeout(() => { 
    		    this.sendRetryCount++ 
    		    this.send(data) 
    	    	}, 200 * this.sendRetryCount) // 发送数据尝试的次数越大, 则下一次连接的 
        延迟也就越长 
        	} 
        } 
        在 onopen 时设置 connected 的值
        connect () { 
    	    ...... 
    	    this.ws.onopen = () => { 
    		    console.log('WebSocket 连接成功') 
    		    this.connected = true 
    	    } 
        }
    
        在 socket_service.js 中修改接收到消息的代码处理
        connect () { 
    	    // 监听接收消息 
    	    this.ws.onmessage = msg => { 
    		    console.log('WebSocket 接收到数据') 
    		    const recvData = JSON.parse(msg.data) // 取出服务端传递的数据 
    		    const socketType = recvData.socketType // 取出业务类型,要根据业务类 型,得到回调函数 
    		    // 先判断有没有回调函数 
    		    if (this.callBackMapping[socketType]) { 
    			    if (recvData.action === 'getData') { 
    				    const realData = recvData.data // 得到该图表的数据 
    				    this.callBackMapping[socketType].call(this, 
    				    JSON.parse(realData)) 
    			    } 
    	    } 
        } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 断开重连机制
      如果初始化连接服务端不成功, 或者连接成功了, 后来服务器关闭了, 这两种情况都会触发 onclose 事件, 我们需要在这个事件中,进行重连 。
      connectRetryCount = 0 // 重连次数, 重连次数越大, 下一次再发起重连的延时也就越长
    connect () { 
        this.ws.onopen = () => { 
    	    ...... 
    	    this.connectRetryCount = 0 // 连接成功之后, 重置重连次数 
    	}
    	...... 
    	// 1.服务器连接不成功 2.服务器关闭了连接 
    	this.ws.onclose = e => { 
    		console.log('服务器关闭了连接') 
    		setTimeout(() => { 
    			this.connectRetryCount++ 
    			this.connect() 
    		}, 200 * this.connectRetryCount) 
    	} 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    完整前端案例
    app.js

    import SocketService from '@/utils/socket_service'
    // 对服务端进行websocket的连接
    SocketService.Instance.connect()
    // 其他的组件  this.$socket
    Vue.prototype.$socket = SocketService.Instance
    
    • 1
    • 2
    • 3
    • 4
    • 5

    socket_service.js

    export default class SocketService {
      /**
       * 单例
       */
      static instance = null
      static get Instance() {
        if (!this.instance) {
          this.instance = new SocketService()
        }
        return this.instance
      }
      // 和服务端连接的socket对象
      ws = null
      // 存储回调函数
      callBackMapping = {}
      // 标识是否连接成功
      connected = false
      // 记录重试的次数
      sendRetryCount = 0
      // 重新连接尝试的次数
      connectRetryCount = 0
      //  定义连接服务器的方法
      connect() {
        // 连接服务器
        if (!window.WebSocket) {
          return console.log('您的浏览器不支持WebSocket')
        }
        this.ws = new WebSocket('ws://localhost:9998')
    
        // 连接成功的事件
        this.ws.onopen = () => {
          console.log('连接服务端成功了')
          this.connected = true
          // 重置重新连接的次数
          this.connectRetryCount = 0
        }
        // 1.连接服务端失败
        // 2.当连接成功之后, 服务器关闭的情况
        this.ws.onclose = () => {
          console.log('连接服务端失败')
          this.connected = false
          this.connectRetryCount++
          setTimeout(() => {
            this.connect()
          }, 500 * this.connectRetryCount)
        }
        // 得到服务端发送过来的数据
        this.ws.onmessage = msg => {
          console.log('从服务端获取到了数据')
          // 真正服务端发送过来的原始数据时在msg中的data字段
          // console.log(msg.data)
          const recvData = JSON.parse(msg.data)
          const socketType = recvData.socketType
          // 判断回调函数是否存在
          if (this.callBackMapping[socketType]) {
            const action = recvData.action
            if (action === 'getData') {
              const realData = JSON.parse(recvData.data)
              this.callBackMapping[socketType].call(this, realData)
            } else if (action === 'fullScreen') {
              this.callBackMapping[socketType].call(this, recvData)
            } else if (action === 'themeChange') {
              this.callBackMapping[socketType].call(this, recvData)
            }
          }
        }
      }
    
      // 回调函数的注册
      registerCallBack (socketType, callBack) {
        this.callBackMapping[socketType] = callBack
      }
    
      // 取消某一个回调函数
      unRegisterCallBack (socketType) {
        this.callBackMapping[socketType] = null
      }
    
      // 发送数据的方法
      send (data) {
        // 判断此时此刻有没有连接成功
        if (this.connected) {
          this.sendRetryCount = 0
          this.ws.send(JSON.stringify(data))
        } else {
          this.sendRetryCount++
          setTimeout(() => {
            this.send(data)
          }, this.sendRetryCount * 500)
        }
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93

    组件使用

    created () {
       // 在组件创建完成之后 进行回调函数的注册
       this.$socket.registerCallBack('hotData', this.getData)
     },
     mounted () {
        this.$socket.send({
          action: 'getData',
          socketType: 'hotData',
          chartName: 'hot',
          value: ''
        })
      },
      destroyed () {
        window.removeEventListener('resize', this.screenAdapter)
        this.$socket.unRegisterCallBack('hotData')
      },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
  • 相关阅读:
    Qtday3
    Java 持久性框架比较
    几个非常有意思的javascript API
    Git - 只merge某个commit到指定的branch
    python高级在线题目训练-第一套
    echarts静态横向柱状图
    【牛客网-公司真题-前端入门篇】——58同城2021校招笔试-前端
    从内核世界透视 mmap 内存映射的本质(源码实现篇)
    区块链与比特币学习笔记二
    【Leetcode合集】2342. 数位和相等数对的最大和
  • 原文地址:https://blog.csdn.net/qq_38799387/article/details/125883123