WebSocket_ohana!的博客-CSDN博客
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据
。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输
。
WebSocket有以下特点:
npm install websocket // "websocket": "^1.0.32",
新建 websocket.js 文件
node
安装:npm install ws // "ws": "^7.3.0",
vue项目中使用WebSocket_vue websocket服务端_weixin_43964779的博客-CSDN博客
开发者API
WebSocket() - Web API 接口参考 | MDN
client:
Server:
package.json:
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "nodemon src/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"bluebird": "^3.7.2",
"jsonwebtoken": "^8.5.1",
"redis": "^2.8.0",
"ws": "^7.2.1"
},
"devDependencies": {
"nodemon": "^2.0.2"
}
}
index.js:
const WebSocket = require('ws')
const wss = new WebSocket.Server({ port: 3000 })
// const jwt = require('jsonwebtoken')
const {getValue, setValue, existKey} = require('./config/RedisConfig')
const timeInterval = 30000
// 多聊天室的功能
// roomid -> 对应相同的roomid进行广播消息
let group = {}
// const run = async () =>{
// setValue('imooc', 'hello')
// const result = await getValue('imooc')
// console.log('TCL: run -> result', result)
// }
// run()
const prefix = 'imooc-'
wss.on('connection', function connection (ws) {
// 初始的心跳连接状态
ws.isAlive = true
console.log('one client is connected');
// 接收客户端的消息
ws.on('message', async function(msg) {
const msgObj = JSON.parse(msg)
const roomid = prefix + (msgObj.roomid ? msgObj.roomid : ws.roomid)
if (msgObj.event === 'enter') {
// 当用户进入之后,需要判断用户的房间是否存在
// 如果用户的房间不存在,则在redis中创建房间号,用户保存用户信息
// 主要是用于统计房间里的人数,用于后面进行消息发送
ws.name = msgObj.message
ws.roomid = msgObj.roomid
ws.uid = msgObj.uid
console.log('TCL: connection -> ws.uid', ws.uid)
// 判断redis中是否有对应的roomid的键值
const result = await existKey(roomid)
if (result === 0) {
// 初始化一个房间数据
setValue(roomid, ws.uid)
} else {
// 已经存在该房间缓存数据
const arrStr = await getValue(roomid)
let arr = arrStr.split(',')
if (arr.indexOf(ws.uid) === -1) {
setValue(roomid, arrStr + ',' + ws.uid)
}
}
if (typeof group[ws.roomid] === 'undefined') {
group[ws.roomid] = 1
} else {
group[ws.roomid] ++
}
}
// // 鉴权
// if (msgObj.event === 'auth') {
// jwt.verify(msgObj.message, 'secret', (err, decode) => {
// if (err) {
// // websocket返回前台鉴权失败消息
// ws.send(JSON.stringify({
// event: 'noauth',
// message: 'please auth again'
// }))
// console.log('auth error');
// return
// } else {
// // 鉴权通过
// console.log(decode);
// ws.isAuth = true
// return
// }
// })
// return
// }
// // 拦截非鉴权的请求
// if (!ws.isAuth) {
// return
// }
// 心跳检测
if (msgObj.event === 'heartbeat' && msgObj.message === 'pong') {
ws.isAlive = true
return
}
// 广播消息
// 获取房间里所有的用户信息
const arrStr = await getValue(roomid)
let users = arrStr.split(',')
wss.clients.forEach(async (client) => {
// 判断非自己的客户端
if (client.readyState === WebSocket.OPEN && client.roomid === ws.roomid) {
msgObj.name = ws.name
msgObj.num = group[ws.roomid]
client.send(JSON.stringify(msgObj))
// 排队已经发送了消息了客户端 -> 在线
if (users.indexOf(client.uid) !== -1) {
users.splice(users.indexOf(client.uid), 1)
}
// 消息缓存信息:取redis中的uid数据
let result = await existKey(ws.uid)
if (result !== 0) {
// 存在未发送的离线消息数据
let tmpArr = await getValue(ws.uid)
let tmpObj = JSON.parse(tmpArr)
let uid = ws.uid
if (tmpObj.length > 0) {
let i = []
// 遍历该用户的离线缓存数据
// 判断用户的房间id是否与当前一致
tmpObj.forEach((item) => {
if (item.roomid === client.roomid && uid === client.uid) {
client.send(JSON.stringify(item))
i.push(item)
}
})
// 删除已经发送的缓存消息数据
if (i.length > 0) {
i.forEach((item) => {
tmpObj.splice(item, 1)
})
}
setValue(ws.uid, JSON.stringify(tmpObj))
}
}
}
})
// 断开了与服务端连接的用户的id,并且其他的客户端发送了消息
if (users.length> 0 && msgObj.event === 'message') {
users.forEach(async function(item) {
const result = await existKey(item)
if (result !== 0) {
// 说明已经存在其他房间该用户的离线消息数据
let userData = await getValue(item)
let msgs = JSON.parse(userData)
msgs.push({
roomid: ws.roomid,
...msgObj
})
setValue(item, JSON.stringify(msgs))
} else {
// 说明先前这个用户一直在线,并且无离线消息数据
setValue(item, JSON.stringify([{
roomid: ws.roomid,
...msgObj
}]))
}
})
}
})
// 当ws客户端断开链接的时候
ws.on('close', function() {
if (ws.name) {
group[ws.roomid] --
}
let msgObj = {}
// 广播消息
wss.clients.forEach((client) => {
// 判断非自己的客户端
if (client.readyState === WebSocket.OPEN && ws.roomid === client.roomid) {
msgObj.name = ws.name
msgObj.num = group[ws.roomid]
msgObj.event = 'out'
client.send(JSON.stringify(msgObj))
}
})
})
})
// setInterval(()=> {
// wss.clients.forEach((ws) => {
// if (!ws.isAlive && ws.roomid) {
// group[ws.roomid] --
// delete ws['roomid']
// return ws.terminate()
// }
// // 主动发送心跳检测请求
// // 当客户端返回了消息之后,主动设置flag为在线
// ws.isAlive = false
// ws.send(JSON.stringify({
// event: 'heartbeat',
// message: 'ping',
// num: group[ws.roomid]
// }))
// })
// }, timeInterval)
服务器先发->客户端->服务器 ∞
心跳检测:
服务ping->客户端 服务器端有定时器 如果没有收到 会在下次遍历中关闭与该客户的服务 ws.terminate() //终止发送 退出了本次连接
客户段Clinet:
客户段在定时器中加入 断开重连的代码,在下次服务器发送过来的PIng 的代码中 清除掉上次的定时器,同时就清除了上次心跳检查的断开代码,然后发送pong->服务器,服务器收到后继续大发送ping->客户端,当本次请求一直未收到ping时 心跳检查的定时器没有被清除 ,就会执行close方法,关闭本次连接,并重新Init新的链接,这就是断线重连。
服务器端: 定时器
setInterval(()=> { //定时器
wss.clients.forEach((ws) => {
if (!ws.isAlive && ws.roomid) { //客户段终止
group[ws.roomid] --
delete ws['roomid']
return ws.terminate() //终止发送 退出了本次连接
}
// 主动发送心跳检测请求
// 当客户端返回了消息之后,主动设置flag为在线
ws.isAlive = false
ws.send(JSON.stringify({
event: 'heartbeat',
message: 'ping',
num: group[ws.roomid]
}))
})
}, timeInterval)
客户端 心跳检查与 断线重连:
//心跳检查
case 'heartbeat':
//this.checkServer() // timeInterval + t 如果一直接收到ping 那么这次的请求就会删除上次的定时器 定时器不会被执行
// 可以注释掉以下心跳状态,主动测试服务端是否会断开客户端的连接
this.ws.send(JSON.stringify({
event: 'heartbeat',
message: 'pong'
}))
break
//检查心跳
checkServer: function () {
var _this = this
clearTimeout(this.handle) //清除计时器
this.handle = setTimeout(function () {
_this.onClose()
setTimeout(function () {
_this.init()
}, 1000)
// 设置1ms的时延,调试在服务器测未及时响应时,客户端的反应
}, 30000 + 1000)
}
springboot整合webSocket(看完即入门)_springboot websocket_hmb↑的博客-CSDN博客
webSocket前端开发实现+心跳检测机制_前端心跳检测_mayuan2011的博客-CSDN博客
在使用webSocket的过程中,如果遇到网络断开,服务端并没有触发onclose事件,就会出现此状况:服务端会继续向客户端发送多余的连接,并且这些数据会丢失。
因此就需要一种机制来检测客户端和服务端是否处于正常的连接状态,因此就有了webSocket的心跳检测机制,即如果有心跳则说客户端和服务端的连接还存在,无心跳相应则说明链接已经断开,需要采取重新连接等措施。
WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。