WebSocket介绍与使用

388 阅读4分钟

如何让服务器主动给客户端推送消息?

我们可以非常轻松的捕获浏览器上发生的事件(比如用户点击了盒子),这个事件可以轻松产生与服务器的数据交互(比如Ajax)。但是,反过来却是不可能的:服务器端发生了一个事件,服务器无法将这个事件的信息实时主动通知它的客户端。只有在客户端查询服务器的当前状态的时候,所发生事件的信息才会从服务器传递到客户端。

让服务器主动给客户端推送消息常见的做法有下面几种方式。

  • 长轮询:客户端每隔很短的时间,都会对服务器发出请求,查看是否有新的消息,只要轮询速度足够快,例如1 秒,就能给人造成交互是实时进行的印象。这种做法是无奈之举,实际上对服务器、客户端双方都造成了大量的性能浪费。

  • 长连接:浏览器和服务器只需要要做一个握手的动作,在建立连接之后,双方可以在任意时刻,相互推送信息。同时,服务器与客户端之间交换的头信息很小。

WebScoket 是一种让客户端和服务器之间能进行双向实时通信的技术。它是HTML 最新标准HTML5 的一个协议规范,本质上是个基于TCP 的协议,它通过HTTP/HTTPS 协议发送一条特殊的请求进行握手后创建了一个TCP 连接,此后浏览器/客户端和服务器之间便可以通过此连接来进行双向实时通信。

后端代码的实现

WebSocket 的使用

  1. 安装WebSocket 包
npm i ws -S
  1. 创建WebSocket 实例对象
const WebSocket = require("ws")
// 创建出WebSocket实例对象
const wss = new WebSocket.Server({
  port: 9998
})
  1. 监听事件
wss.on("connection", client => {
  console.log("有客户端连接...")
  // 成功接收的信息的回调
  client.on("message", msg => {
    console.log("客户端发送数据过来了")
    // 发送数据给客户端
    client.send('hello socket')
  })
})
  1. 前端的测试代码如下:
<body>
<button id="connect">连接</button>
<button id="send" disabled="true">发送数据</button> <br>
从服务器接收的数据如下:<br>
<span id="content"></span>
  <script>
    var connect = document.querySelector('#connect')
    var send = document.querySelector('#send')
    var content = document.querySelector('#content')
    var ws = null
    connect.onclick = function() {
      // WebSocket对象为浏览器自带
      ws = new WebSocket('ws://localhost:9998')
      ws.onopen = () => {
        console.log('连接服务器成功')
        send.disabled = false
      }
      ws.onmessage = msg => {
        console.log('从服务器接收到了数据')
        content.innerHTML = msg.data
      }
      ws.onclose = e => {
        console.log('服务器关闭了连接')
        send.disabled = true
      }
    }
    send.onclick = function(){
      //  通过WebSocket发送数据
      ws.send('hello websocket from frontend')
    }
  </script>
</body>
  1. 后端的测试代码如下:
  const WebSocket = require("ws")
  // 创建出WebSocket实例对象
  const wss = new WebSocket.Server({
    port: 9998
  })
  module.exports.listen = function() {
    // 客户端成功连接
    wss.on("connection", ws => {
      console.log("有客户端连接...")
      // 客户端接收到消息后的message事件
      ws.on("message", msg => {
          wss.clients.forEach(client => {
            console.log("*****************************************")
            client.send(msg)
          })
      })
    })
  }

前端代码的改造

通过单例方式创建WebSocket 实例对象

定义单例:

export default class SocketService {
/**
* 单例
*/
static instance = null
static get Instance() {
  if (!this.instance) {
    this.instance = new SocketService()
  }
  return this.instance
}

监听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')
  }
}

在 connect 函数监听事件:

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 接收到数据')
  }
}

连接服务端

import SocketService from '@/utils/socket_service'
SocketService.Instance.connect()

发送数据给服务端

在socket_service.js 中定义发送数据的方法

export default class SocketService {
  ......
  send (data) {
    console.log('发送数据给服务器:')
    this.ws.send(JSON.stringify(data))
  }
}

运行代码, 发现数据发不出去

因为在刷新界面之后, 客户端和服务端的连接并不会立马连接成功, 在处于连接状态下就调用 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
  }
}

断开重连机制

如果初始化连接服务端不成功, 或者连接成功了, 后来服务器关闭了, 这两种情况都会触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)
  }
}

总结

WebSocket 可以保持着浏览器和客户端之间的长连接, 通过WebSocket 可以实现数据由后端推送到前端,保证了数据传输的实时性。通过以上的代码可以有效地实现前端与后端的长连接,确保数据的实时性。