Bootstrap

websocket: 了解并利用nodejs实现webSocket前后端通信

目录

第一章 前言

1.1 起源

1.2 短轮询与长轮询

1.2.1 短轮询

1.2.2 长轮询

1.2.3 长连接(SSE)

1.2.4 websocket

第二章 利用Node以及ws创建webSocket服务器

2.1 创建ws服务器(后端部分)

2.1.1 了解一下

2.1.2 代创建WebSocket服务器

2.1.3 监听前端连接websocket(ws.on的connection事件)

2.1.4 服务器接收数据(ws.on的message事件)

2.1.5 服务器发送数据(ws.send()方法)

2.1.6 关闭服务器(ws.on的close事件)

2.2 创建后台服务器(后端部分)

第三章 前端使用websocket的方法

3.1 常用的使用方法(前端部分)

3.1.1 代码引入

3.1.2 引入ws

3.1.3 打开ws连接(ws.onopen)

3.1.4 连接错误(ws.onerror)

3.1.5 前端接收服务器的数据(ws.onmessage)

3.1.6 关闭连接(ws.onclose)

第四章 效果展示

提示

源代码


第一章 前言

1.1 起源

在 Web 开发领域,我们最常用的协议是 HTTP,HTTP 协议和 WS 协议都是基于 TCP 所做的封装,但是 HTTP 协议它是从一开始便被设计成请求 -> 响应的模式,所以在很长一段时间内 HTTP 都是只能从客户端发向服务端并不具备从服务端主动推送消息的功能,这也导致在浏览器端想要做到服务器主动推送的效果只能用一些轮询和长轮询的方案来做,但因为它们并不是真正的全双工,所以在消耗资源多的同时,实时性也没理想中那么好。

1.2 短轮询与长轮询

1.2.1 短轮询

  • 解释:前端(客户端)利用循环定时器不断的向后台做HTTP请求(会产生多个HTTP请求),后台(服务端)接到请求后返回响应信息的一种方式
  • 适用于:适用于小型应用,或者同时在线人数较少的应用
  • 优点:简单省时,后端程序编写比较容易(几乎不用做什么特殊处理)
  • 缺点:不及时(得看定时器的间隔),消耗大 ( 服务器宽带和资源)

1.2.2 长轮询

  • 解释:长轮询只启动一个HTTP请求,其连接的服务端会挂起此次连接,后端定时器去查询数据库有没有新消息,直到有新消息才返回响应信息客户端处理完响应信息后再向服务器发送新的Http请求,以此类推。区别于轮询的就是没有新消息就不会发送新的请求
  • 适用于:适用于小型应用,或者同时在线人数较少的应用
  • 优点:可实现实时数据回传,长轮询和轮询比起来,明显减少了很多不必要的http请求次数,相比之下节约了资源。
  • 缺点:连接挂起也会导致资源的浪费(服务器压力大,频繁操作询问数据库有没有新结果)

1.2.3 长连接(SSE)

  • SSE是HTML5新增的功能,SSE(sever-sent events)服务器端推送事件,是指服务器推送数据给客户端,而不是传统的请求响应模式。简单的说,就是浏览器向服务器发送一个HTTP请求然后服务器不断单向地向浏览器推送“信息”而SSE最大的特点就是可以实现只要服务器端数据有更新,就可以马上发送到客户端。

1.2.4 websocket

  • websocket 最大的特点就是可以全双工的双向通信
  • 全双工:全双工是指两方能同时发送和接收数据
  • 半双工:半双工是指传输过程中只能向一个方向传输
  • 传输的消息类型:
  1. 文本消息(string)
  2. 二进制消息
  3. 分片消息(分片消息代表此消息是一个某个消息中的一部分,想想大文件分片)
  4. 连接关闭消息
  5. PING 消息
  6. PONG 消息(PING的回复就是PONG)

WebSocket 教程 - 阮一峰的网络日志

第二章 利用Node以及ws创建webSocket服务器

2.1 创建ws服务器(后端部分)

2.1.1 了解一下

  • Node.js原生API没有提供对WebSocket的支持,需要安装第三方包ws才能使用WebSocket功能
  • ws模块:是一个用于支持WebSocket客户端和服务器的框架。它易于使用,功能强大,且不依赖于其他环境
  • 安装
npm install ws

2.1.2 代创建WebSocket服务器

const WebSocket = require('ws')
// 创建WebSocket服务端的对象,绑定的端口号是9998
// 相当于为ws创建了个接口,这个就是连接websocket的链接,后续前端会用到
const wss = new WebSocket.Server({
  port: 9998
})

2.1.3 监听前端连接websocket(ws.on的connection事件)

const WebSocket = require('ws')
// 创建WebSocket服务端的对象,绑定的端口号是9998
const wss = new WebSocket.Server({
  port: 9998
})
module.exports.listener = () => {
    // 对客户端连接事件进行监听,只要有WebSocket连接到该服务器,就会触发'connection'事件
    // ws代表的是客户端的连接的socket对象;req对象可以用来获取客户端的信息,如ip、端口号
    wss.on('connection', (ws, req) => {
        console.log('有客户端连接成功了', ws, req)
    })
}
// 若要获取所有已连接的客户端信息,则可以使用server.clients数据集

2.1.4 服务器接收数据(ws.on的message事件)

const WebSocket = require('ws')
// 创建WebSocket服务端的对象,绑定的端口号是9998
const wss = new WebSocket.Server({
  port: 9998
})
module.exports.listener = () => {
  wss.on('connection', (ws, req) => {
    console.log('有客户端连接成功了', ws, req)

    // 对客户端的连接对象进行message事件的监听
    // 当客户端有消息发送给服务器时,服务器就能够触发该消息
    // msg:由客户端发给服务端的数据
    ws.on('message', msg => {
      console.log('客户端发送给服务器端', msg)
      // 当接收到客户端传的参数之后服务器端可以执行某些操作(具体看需求)
      // 小编这里是做了一个数据返回给客户端
      // 是当客户端连接成功之后会发送一条信息告诉服务器,服务器监听到信息之后再返回数据给客户端
      const data = [
        [80, 110, 150, 60, 30, 130, 110],
        [80, 120, 150, 80, 40, 120, 112],
        [80, 130, 150, 40, 70, 133, 115],
        [80, 140, 150, 30, 80, 110, 110],
        [80, 130, 150, 70, 100, 140, 115],
        [80, 120, 180, 90, 90, 150, 120],
        [80, 100, 120, 90, 80, 120, 160]
      ]
      let i = 0
      setInterval(() => {
        if (i === data.length) {
          i = 0
        }
        // 发送数据给客户端
        ws.send(JSON.stringify(data[i]))
        i++
      }, 1000)
      // 由服务端往客户端发送数据
    })
  })
}

2.1.5 服务器发送数据(ws.send()方法)

 /* send(data [,options][,callback])
       data:发送的数据
       options对象(可选):
         (1)compress:指定数据是否需要压缩。默认为true
         (2)binary:指定数据是否通过二进制传送。默认是自动检测
         (3)mask:指定是否应遮罩数据。
         (4)fin:指定数据是否为消息的最后一个片段。默认为true
 */
const WebSocket = require('ws')
// 创建WebSocket服务端的对象,绑定的端口号是9998
const wss = new WebSocket.Server({
  port: 9998
})
const WebSocket = require('ws')
// 创建WebSocket服务端的对象,绑定的端口号是9998
const wss = new WebSocket.Server({
  port: 9998
})
module.exports.listener = () => {
  wss.on('connection', (ws, req) => {
    console.log('有客户端连接成功了', ws, req)
    // 对客户端的连接对象进行message事件的监听
    // 当客户端有消息发送给服务器时,服务器就能够触发该消息
    // msg:由客户端发给服务端的数据
    ws.on('message', msg => {
      console.log('客户端发送给服务器端', msg)
      // 当接收到客户端传的参数之后服务器端可以执行某些操作(具体看需求)
      // 小编这里是做了一个数据返回给客户端
      // 是当客户端连接成功之后会发送一条信息告诉服务器,服务器监听到信息之后再返回数据给客户端
      const data = [
        [80, 110, 150, 60, 30, 130, 110],
        [80, 120, 150, 80, 40, 120, 112],
        [80, 130, 150, 40, 70, 133, 115],
        [80, 140, 150, 30, 80, 110, 110],
        [80, 130, 150, 70, 100, 140, 115],
        [80, 120, 180, 90, 90, 150, 120],
        [80, 100, 120, 90, 80, 120, 160]
      ]
      let i = 0
      setInterval(() => {
        if (i === data.length) {
          i = 0
        }
        // ========发送数据给客户端========
        ws.send(JSON.stringify(data[i]))
        i++
      }, 1000)
      // 由服务端往客户端发送数据
    })
  })
}

2.1.6 关闭服务器(ws.on的close事件)

const WebSocket = require('ws')
// 创建WebSocket服务端的对象,绑定的端口号是9998
const wss = new WebSocket.Server({
  port: 9998
})
module.exports.listener = () => {
  wss.on('connection', (ws, req) => {
    ws.on('message', msg => {
      console.log('客户端发送给服务器端', msg.toString('utf8'))
      const data = [
        [80, 110, 150, 60, 30, 130, 110],
        [80, 120, 150, 80, 40, 120, 112],
        [80, 130, 150, 40, 70, 133, 115],
        [80, 140, 150, 30, 80, 110, 110],
        [80, 130, 150, 70, 100, 140, 115],
        [80, 120, 180, 90, 90, 150, 120],
        [80, 100, 120, 90, 80, 120, 160]
      ]
      let i = 0
      // 模拟了由服务端往客户端发送数据
      setInterval(() => {
        if (i === data.length) {
          i = 0
        }
        // 服务器向前端发送数据
        ws.send(JSON.stringify(data[i]))
        i++
      }, 1000)
    })

    // 监听要关闭连接的函数
    ws.on('close', function close () {
      // 这里面关闭的逻辑
      console.log('WebSocket连接已关闭')
    })
  })
}

—— 至此,后端的websocket逻辑其实已经完结了

2.2 创建后台服务器(后端部分)

  • 利用express框架在后台开服务器,供我们运行后端代码以及跑websocket
// express具体使用看我提供个文章中有
const express = require('express')
const app = express()
const port = 3001
// app.get('/', (req, res) => res.send('hello express'))
// express提供了一个非常好用的函数,叫做express.static(),能快速托管静态资源的内置中间件
// 如下配置是将public目录下的图片、css文件、JavaScript文件对外开放访问
app.use('/', express.static('public'))
// 解析 JSON 格式的请求体数据
app.use(express.json())
// 监听设置的端口
app.listen(port, () => { console.log('server is running,port is' + port) })

// 监听开启的websocket服务器
const websocketservice = require('./web_socket_service')
websocketservice.listener()

AJAX及其相关知识应用(很详细)_使用ajax需要引入什么-CSDN博客

第三章 前端使用websocket的方法

3.1 常用的使用方法(前端部分)

(注意:小编这里使用的是一个echarts可视化的动态展示,也方便以后大家使用可视化大屏时有一定的参考价值)

3.1.1 代码引入

<template>
  <div>
    <div id='main' style="width:800px;height:600px"></div>
  </div>

</template>

<script>
import * as echarts from 'echarts'
// 引入ws,路径是后端配置好给前端的
const ws = new WebSocket('ws://localhost:9998')
export default {
  data () {
    return {
    }
  },
  mounted () {
    // 要想展示实时效果,需要对series配置下的data数据进行实时的监听
    const options = {
      title: {
        text: '数量统计'
      },
      xAxis: {
        data: ['衣服', '牛奶', '巧克力', '矿泉水', '方便面', '面包', '花生']
      },
      yAxis: {},
      series: [
        {
          name: '销量',
          type: 'line',
          data: [100, 150, 120, 90, 30, 130, 110]
        }
      ]
    }
    const mychart = echarts.init(document.getElementById('main'))

    ws.onopen = () => {
      console.log('连接服务器端成功')
      ws.send('Hello websocket')
    }
    ws.onerror = () => {
      console.log('连接服务器失败')
    }
    ws.onmessage = (msg) => {
      console.log('接收到服务端发送的数据')
      console.log('msg', msg)
      console.log(JSON.parse(msg.data))
      options.series[0].data = JSON.parse(msg.data)
      mychart.setOption(options)
      // ws.send('Hello websocket to message')
    }
  },
  beforeDestroy () {
    ws.onclose = () => {
      console.log('websocket已关闭')
    }
  },
  components: {

  }
}
</script>

3.1.2 引入ws

  • 如果做前后端分分离的项目前端只需要跟后端要配置好的ws的路径即可 
<template>
  <div>
    <div id='main' style="width:800px;height:600px"></div>
  </div>

</template>

<script>
import * as echarts from 'echarts'
// 引入ws,路径是后端配置好给前端的
// 小编在这里引入的目的是除了要展示连接成功,还需要执行关闭连接的逻辑,两者不在相同的生命周期钩子中执行
const ws = new WebSocket('ws://localhost:9998')

export default {
  data () {
    return {
    }
  },
  mounted () {
  },
  beforeDestroy () {
  },
  components: {

  }
}
</script>

3.1.3 打开ws连接(ws.onopen)

// 打开连接
ws.onopen = () => {
    console.log('连接服务器端成功')
    // 如果与ws连接成功,我们发送消息跟服务器说一下已经连接
    ws.send('Hello websocket')
}

3.1.4 连接错误(ws.onerror)

ws.onerror = () => {
    console.log('连接服务器失败')
}

3.1.5 前端接收服务器的数据(ws.onmessage)

// 接收服务器传的数据,msg,每当数据发生变化,后端都会返回数据到前端
// 该方法会一直都在监听ws返回的数据,然后不断的更新我的的图表,从而模拟实现了一个websocket的demo
ws.onmessage = (msg) => {
    console.log('接收到服务端发送的数据')
    console.log('msg', msg)
    // 这里面就可以处理数据的逻辑了
    console.log(JSON.parse(msg.data))
    options.series[0].data = JSON.parse(msg.data)
    mychart.setOption(options)
}

3.1.6 关闭连接(ws.onclose)

beforeDestroy () {
    ws.onclose = () => {
      console.log('websocket已关闭')
    }
},

-- 至此,前端代码结束

第四章 效果展示

  • 启动后端,开启ws服务器

  • 启动前端

  •  运行后前端控制台展示

  •  后端控制台输出

  • 关闭前端页面后台输出

  •  注意代码:msg.toString('utf8')——注意一下websocket支持的数据类型,由于小编看到在控制台输出的是buffer格式,所以进行了一下转换
  • 展示效果 

提示

看到最后了,提示一下,如果还是前后端分开开发时,前端处理其实并没有那么复杂,只需要了解第三章的结构即可。 

源代码

https://gitee.com/shallow-winds/websocket_demo

;