1.WebSocket的基本使用
后端
安装包:npm i -S ws
创建对象
const WebSocket = require('ws')
const wss = new WebSocket.Server({
port: 9998
})
监听事件
连接事件
wss.on('connection', client => {
})
接收数据事件
wss.on('connection', client => {
client.on('message', msg => {
})
})
发送数据 client.send()
前端
创建对象:const ws = new WedSocket('ws://localhost:9998') WebSocket是window对象就提供了的,因此不需要额外的包
监听事件
连接成功事件:ws.onopen
接收数据事件:ws.onmessage
关闭连接事件:ws.onclose
发送数据:ws.send
websocket_demo.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<button id="connect">连接</button>
<button id="send" disabled>发送数据</button><br>
从服务端接收的数据如下:<br>
<span id="recv"></span>
<script>
let connect = document.querySelector('#connect')
let send = document.querySelector('#send')
let 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('hello socket from frontend')
}
</script>
</body>
</html>
app.js
const WebSocket = require('ws')
// 创建WebSocket服务器的对象,绑定9998端口号
const wss = new WebSocket.Server({
port: 9998
})
// 对客户端的连接事件进行监听
// client:代表的是客户端的连接socket对象
wss.on('connection', client => {
console.log('有客户端连接成功了...')
// 对客户端的连接对象进行message事件的监听
// msg: 由客户端发给服务端的数据
client.on('message', msg => {
console.log('客户端发送数据给服务端:' + msg)
// 由服务端往客户端发送数据
client.send('hello socket from backend')
})
})
2.使用WebSocket改造项目
后端工程
创建web_socket_service.js
创建Socket服务端对象,绑定端口
监听事件:connetion、message
将监听事件的代码放到一个函数中,并将这个函数导出
服务端接收数据字段的约定
{
"action": "getData",
"socketType": "trendData",
"chartName": "trend",
"value": "",
}
action: 代表某项行为,可选值有getData(获取图表数据)、fullScreen(全屏事件)、themeChange(主题切换事件)
socketType: 代表业务模块类型、代表前端响应函数的标识,可选值有trendData、sellerData、mapData、rankData、hotData、stockData、fullScreen、themeChange
chartName: 代表图表名称,可选值有trend、seller、map、rank、hot、stock,如果是主题切换事件,可不传此值
服务端发送数据字段的约定
接收到action为getData时:取出数据中的chartName字段、拼接json文件的路径、读取该文件的内容、在接收到数据的基础之上,增加data字段(值是从文件中读出的内容)
接收到action不为getData时:原封不动地将从客户端接收到的数据,并转发给每一个处于连接状态的客户端
app.js
... // 原代码不变
const WebSocketService = require('./service/web_socket_service')
// 开启服务器的监听 ,监听客户端的连接
// 当某一个客户端连接成功之后,就会对这个客户端进行message事件的监听
WebSocketService.listen()
service/web_socket_service.js
const path = require('path')
const fileUtils = require('../utils/file_utils')
const WebSocket = require('ws')
const wss = new WebSocket.Server({
port: 9998
})
// 服务端开启了监听
module.exports.listen = () => {
wss.on('connection', client => {
console.log('有客户端连接成功了...')
client.on('message', async msg => {
console.log('客户端发送数据给服务端:' + msg)
let payload = JSON.parse(msg)
const action = payload.action
if (action === 'getData') {
// payload.chartName可能的值:trend seller map rank hot stock
let filePath = '../data/' + payload.chartName + '.json'
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(JSON.stringify(payload))
})
}
})
})
}
前端工程
创建socket_service.js
定义类SocketService,并定义成单例设计模式
定义连接服务器的方法connect(创建WebSocket对象,对服务器进行连接,在main.js中调用此方法)
监听事件: onopen、onmessage、onclose
存储回调函数:callBackMapping={}、注册回调函数registerCallBack(socketType,callBack)、取消回调函数unRegisterCallBack(socketType)
接收数据的处理:onmessage 调用之前存储的回调函数,传递数据
定义发送数据的方法:send()
挂载SocketService对象到Vue的原型对象上,方便各个组件使用这个对象中的方法
组件的改造
created: 注册回调函数
destoryed: 取消回调函数
在原来获取数据的地方改为发送数据,数据的格式需要满足约定形式
components/trend.vue(其它5个组件修改同理)
... //原代码不变
<script>
export default {
data () {
},
created () {
// 在组件创建完成之后进行回调函数的注册
this.$socket.registerCallBack('trendData', this.getData)
},
mounted () {
this.initChart()
// 发送数据给服务器,告诉服务器我现在需要数据
this.$socket.send({
action: 'getData',
socketType: 'trendData',
chartName: 'trend',
value: ''
})
window.addEventListener('resize', this.screenAdapter)
this.screenAdapter()
},
destoryed () {
window.removeEventListener('resize', this.screenAdapter)
this.$socket.unRegisterCallBack('trendData')
},
computed: {
},
methods: {
... //原代码不变
// ret 就是服务端发送给客户端的图表数据
getData (ret) {
this.allData = ret
this.updateChart()
}
... //原代码不变
}
}
</script>
... //原代码不变
优化
重发数据机制:增加实例属性connected(默认值为false,onopen时设置为true、onclose时设置为false)、发送数据时需要判断connected(true直接发送、false延时发送,延时的时间随着尝试次数的增加而增加)
断开重连机制:onclose时,延时尝试连接服务器,延时的时间随着尝试次数的增加而增加
utils/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
}
// 连接服务器失败
this.ws.onclose = () => {
console.log('连接服务器失败')
this.connected = false
this.connectRetryCount++
setTimeout(() => {
this.connect()
}, this.connectRetryCount * 500)
}
// 得到服务器发送过来的数据
this.ws.onmessage = msg => {
console.log('从服务器获取到了数据')
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') {
} else if (action === 'themeChange') {
}
}
}
}
// 回调函数的注册
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)
}
}
}
main.js
... //原代码不变
import SocketService from './utils/socket_service'
// 对服务端进行websocket的连接
SocketService.Instance.connect()
// 其它组件可通过this.$socket来调用
Vue.prototype.$socket = SocketService.Instance
... //原代码不变