1.webSocket的简介
WebSocket 可以保持着浏览器和客户端之间的长连接, 通WebSocket 可以实现数据由后端推送到前端,保了数据传输的实时性。
2.后端代码的改造(以koa2为例)
-
安装 WebSocket 包
npm i ws -S
-
创建 WebSocket 实例对象
const WebSocket = require("ws")
// 创建出WebSocket实例对象
const wss = new WebSocket.Server({
port: 9998
})
-
监听事件
demo
wss.on("connection", client => {
console.log("有客户端连接...")
client.on("message", msg => {
console.log("客户端发送数据过来了")
// 发送数据给客户端
client.send('hello socket')
})
})
现实使用的
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事件的监听
- 前端测试代码
<!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>
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
}
}
- 监听 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 () {
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 接收到数据')
}
}
-
定义注册函数
记录一下当得到数据时, 应该调用的函数回调
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
}
}
-
连接服务端
- 在 main.js 中连接服务器端
import SocketService from '@/utils/socket_service'
SocketService.Instance.connect()
- 将 SocketService 实例对象挂载到 Vue 的原型对象上
Vue.prototype.$socket = SocketService.Instance
-
发送数据给服务端
- 在 socket_service.js 中定义发送数据的方法
export default class SocketService {
......
send (data) {
console.log('发送数据给服务器:')
this.ws.send(JSON.stringify(data))
}
}
- vue组件注册回调函数
created () {
// 当socket来数据的时候, 会调用getData这个函数
this.$socket.registerCallBack('trendData', this.getData)
}
- destroyed 中取消注册
destroyed () {
this.$socket.unRegisterCallBack('trendData')
}
- mounted 中往 socket 发送数据
mounted () {
this.initChart()
// this.getData() 先将getData的调用注释起来
this.$socket.send({
action: 'getData',
socketType: 'trendData',
chartName: 'trend'
})
......
},
- 运行代码, 发现数据发不出去
因为在刷新界面之后, 客户端和服务端的连接并不会立马连接成功, 在处于连接状态下就调用,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))
}
}
}
- 断开重连机制
如果初始化连接服务端不成功, 或者连接成功了, 后来服务器关闭了, 这两种情况都会触发 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)
}
}
完整前端案例
app.js
import SocketService from '@/utils/socket_service'
// 对服务端进行websocket的连接
SocketService.Instance.connect()
// 其他的组件 this.$socket
Vue.prototype.$socket = SocketService.Instance
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)
}
}
}
组件使用
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')
},