后端:koa2+sequelize中使用websocket,可以使用的库有ws
和koa-websocket
,此处使用ws
。
安装ws
npm i --save ws
在koa2中使用ws
const Koa = require('koa')
const app = new Koa()
const WebSocket = require('ws')
const server = app.listen(8081)
wss = new WebSocket.Server({ server }) // 这样可以实现websocket连接和http请求连接使用同一个端口
wss.on('connection', (ws) => {
ws.on('message', async message => {
console.log(`服务端接收到客户端的消息:${message}`)
ws.send(`服务端说:${message}`) // 发送的消息需要是一个字符串
})
ws.on('close', () => {
console.log('客户端关闭连接')
})
})
在Vue中使用WebSocket
const ws = new WebSocket('ws://localhost:8081')
ws.onopen = () => {
// 客户端主动向服务端请求数据
console.log('连接成功')
ws.send('我是客户端发送的消息')
}
ws.onmessage = (event) => {
// 这是从服务器接收到的消息
console.log(event.data) // 这是一个字符串
}
如何在ws连接中获取信息,比如登录用户id
在客户端建立websocket
连接时将查询参数包含用户id
const uid = 'ourworwo3292lmflkp'
const ws = new WebSocket(`ws://localhost:8081?uid=${uid}`)
在服务端的websocket
连接时处理请求url,拿到uid
const WebSocket = require('ws') // 需要安装
const querystring = require('querystring') // 需要安装
const url = require('url') // 不需要安装
wss.on('connection', (ws, req) => {
const urlObj = url.parse(req.url)
const query = querystring.parse(urlObj.query) // 解析URL参数
const uid = query.uid
ws.on('message', async message => {
// ...
})
ws.on('close', () => {
// ...
})
})
如何实现ws连接通道独立
方式1:比如可以使用uid为每个用户建立单独的连接通道
let cache = {}
wss.on('connection', (ws, req) => {
const urlObj = url.parse(req.url)
const query = querystring.parse(urlObj.query)
const uid = query.uid
cache[uid] = ws
cache[uid].on('message', async message => {
console.log(`服务端接收到客户端的消息:${message}`)
cache[uid].send(`服务端说:${message}`)
})
cache[uid].on('close', () => {
console.log('客户端关闭连接')
})
})
客户端主动向服务端推送数据
const ws = new WebSocket(`ws://${base_url}`)
ws.onopen = () => {
// 客户端主动向服务端请求数据
const msg = {
cmd: 'init',
type: 'log'
}
ws.send(JSON.stringify(msg))
}
服务端接收客户端推送的数据
wss.on('connection', async (ws, req) => {
ws.on('message', async message => {
// console.log(`接收消息:${message}`)
try {
const clientMsg = JSON.parse(message)
if (clientMsg.type === 'log' && clientMsg.cmd === 'init') {
const msg = {
type: 'log',
data: ''
}
// 服务端主动向客户端发送数据
ws.send(JSON.stringify(msg))
}
} catch (error) {
}
})
ws.on('close', () => {
console.log('客户端关闭连接')
})
})
服务端的主动向客户端推送数据
wss.on('connection', async (ws, req) => {
const msg = {
type: 'log',
data: ''
}
// 服务端主动向客户端发送数据
ws.send(JSON.stringify(msg))
ws.on('close', () => {
console.log('客户端关闭连接')
})
})
示例:定义一个日志表,记录用户登录情况,使用ws连接后保证在日志表数据变化后服务端主动向客户端推送数据
sequelize
中监听表的数据变化可以使用hook,比如afterCreate
、afterDestroy
、afterUpdate
、beforeCreate
、beforeDestroy
等
后端代码示例:
const Log = require('../models/Log')
wss.on('connection', (ws) => {
ws.on('message', async message => {
try {
const clientMsg = JSON.parse(message)
if (clientMsg.type === 'log' && clientMsg.cmd === 'init') {
const msg = {
type: 'log', // 避免多个地方使用ws连接,定义一个类型作区别
data: '我是服务端定义的数据' // 这个地方可以处理需要向客户端发送的ws数据
}
cache[uid].send(JSON.stringify(msg))
}
} catch (error) {
}
})
// 监听日志表新增数据后的生命周期
Log.addHook('afterCreate', async (log, options) => {
const msg = {
type: 'log',
data: '我是服务端定义的数据'
}
ws.send(JSON.stringify(msg))
})
// 监听日志表删除数据后的生命周期
Log.addHook('afterDestroy', async (log, options) => {
const msg = {
type: 'log',
data: '我是服务端定义的数据'
}
ws.send(JSON.stringify(msg))
})
})
前端代码示例:
const ws = new WebSocket('ws://localhost:8081')
ws.onopen = () => {
const msg = {
cmd: 'init',
type: 'log'
}
// 客户端主动向服务端请求数据
ws.send(JSON.stringify(msg))
}
ws.onmessage = (event) => {
try {
// 客户端接收服务端数据
const { data } = JSON.parse(event.data)
} catch (error) {
}
}
注意:后端代码示例中的
afterDestroy
方法如果不能正确触发的原因,需要检查是否正确触发destroy
方法(确保在数据库中正确查询到了要删除的数据,并且在查询记录的基础上调用了destroy
方法)
示例:
const log= await Log.findByPk(logId)
if (log) {
await log.destroy()
}
如果使用Log.destroy()
不能正确触发afterDestroy
方法
await Log.destroy({
where: {
id
}
})
websocket连接失败和重连次数限制处理
连接次数是客户端请求服务端ws连接的次数,所以需要在客户端即前端处理
let MAX_CONNECTION = 5 // 最大重连次数
let CONNECTED = 0 // 连接次数
let ws = null
getWebscoket() // 初始化websocket连接
function getWebscoket () {
ws = new WebSocket(`ws://${base_url}`) // 这里new之后指向了新的内存地址,所以后续的open等事件都会重新监听,连接失败后判断重连次数,需要保证ws的内存地址指向一致
ws.onopen = () => {
// 客户端主动向服务端请求数据
const msg = {
cmd: 'init',
type: 'log'
}
ws.send(JSON.stringify(msg))
}
ws.onmessage = (event) => {
try {
const { data } = JSON.parse(event.data)
weekData.value = data
} catch (error) {
ElMessage.error(error)
}
}
ws.onerror = (error) => {
// 连接错误操作
}
ws.onclose = () => {
// 连接关闭操作
reWsConnect()
if (CONNECTED >= MAX_CONNECTION) {
ElMessage.error('连接失败:可能后台未启动')
}
}
}
// ws连接失败后重新连接处理,连接次数限制处理
function reWsConnect () {
setTimeout(() => {
if (CONNECTED < MAX_CONNECTION) {
getWebscoket()
CONNECTED++
}
}, 2000)
}