mirror of
https://github.com/sdewis/dg-lab-opensource-english.git
synced 2026-10-19 02:09:03 +00:00
socket document push
This commit is contained in:
parent
fbaabe289b
commit
5698ce0239
24 changed files with 4483 additions and 0 deletions
24
socket/BackEnd(Node)/document.txt
Normal file
24
socket/BackEnd(Node)/document.txt
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
0. 以下代码适用于Linux服务器,使用Node官网的linux包管理下载
|
||||
https://nodejs.org/en/download/package-manager
|
||||
|
||||
推荐使用NVM安装
|
||||
|
||||
根据官网的命令行提示选择并安装Node18及以上版本(推荐安装后缀LTS的版本)
|
||||
|
||||
逐行复制页面中的Linux命令到服务器中执行,等待提示完成之后,查看安装的版本是否正确,那之后请重启命令窗口或远程窗口。
|
||||
|
||||
接下来使用npm 安装必要的后台运行环境:
|
||||
|
||||
1. npm i ws -g
|
||||
全局安装websocket for node 插件,让node支持websocket协议,若后续第6步使用pm2运行程序失败,则使用cd命令进入第6步的文件夹中使用npm i ws来将插件单独配置到此文件夹中。
|
||||
|
||||
2. npm i pm2 -g
|
||||
全局安装PM2,由于node程序不支持后台运行,而且使用linux自带后台运行是不可靠的,推荐使用PM2来托管你的程序在后台运行(支持程序崩溃时自动重启)
|
||||
|
||||
3. 在目录中创建存放服务端代码的文件夹(例如:www/myws) 并将websocketNode.js文件放进去
|
||||
|
||||
4. 使用cd命令进入你的文件夹(例如:cd www/myws)
|
||||
|
||||
5. 执行命令 npx pm2 start websocketNode.js 即可看到运行提示
|
||||
如果你需要查看运行过程中打印的log日志,可以在运行代码之后,在此目录下命令行中输入npx pm2 log 0 并回车来查看
|
||||
(通常情况下你只有一个程序的情况id就是0,如果你运行了多个程序,在执行start命令之后,命令行会显示你本次任务的id,将命令最后的0修改成对应id即可)
|
||||
307
socket/BackEnd(Node)/websocketNode.js
Normal file
307
socket/BackEnd(Node)/websocketNode.js
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
const WebSocket = require('ws');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
// 储存已连接的用户及其标识
|
||||
const clients = new Map();
|
||||
|
||||
// 存储消息关系
|
||||
const relations = new Map();
|
||||
|
||||
const punishmentDuration = 5; //默认发送时间1秒
|
||||
|
||||
const punishmentTime = 1; // 默认一秒发送1次
|
||||
|
||||
// 存储客户端和发送计时器关系
|
||||
const clientTimers = new Map();
|
||||
|
||||
// 定义心跳消息
|
||||
const heartbeatMsg = {
|
||||
type: "heartbeat",
|
||||
clientId: "",
|
||||
targetId: "",
|
||||
message: "200"
|
||||
};
|
||||
|
||||
// 定义定时器
|
||||
let heartbeatInterval;
|
||||
|
||||
const wss = new WebSocket.Server({ port: 9999 });
|
||||
|
||||
wss.on('connection', function connection(ws) {
|
||||
// 生成唯一的标识符
|
||||
const clientId = uuidv4();
|
||||
|
||||
console.log('新的 WebSocket 连接已建立,标识符为:', clientId);
|
||||
|
||||
//存储
|
||||
clients.set(clientId, ws);
|
||||
|
||||
// 发送标识符给客户端(格式固定,双方都必须获取才可以进行后续通信:比如浏览器和APP)
|
||||
ws.send(JSON.stringify({ type: 'bind', clientId, message: 'targetId', targetId: '' }));
|
||||
|
||||
// 监听发信
|
||||
ws.on('message', function incoming(message) {
|
||||
console.log("收到消息:" + message)
|
||||
let data = null;
|
||||
try {
|
||||
data = JSON.parse(message);
|
||||
}
|
||||
catch (e) {
|
||||
// 非JSON数据处理
|
||||
ws.send(JSON.stringify({ type: 'msg', clientId: "", targetId: "", message: '403' }))
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type && data.clientId && data.message && data.targetId) {
|
||||
// 优先处理绑定关系
|
||||
const { clientId, targetId, message, type } = data;
|
||||
switch (data.type) {
|
||||
case "bind":
|
||||
// 服务器下发绑定关系
|
||||
if (clients.has(clientId) && clients.has(targetId)) {
|
||||
// relations的双方都不存在这俩id
|
||||
if (![clientId, targetId].some(id => relations.has(id) || [...relations.values()].includes(id))) {
|
||||
relations.set(clientId, targetId)
|
||||
const client = clients.get(clientId);
|
||||
const sendData = { clientId, targetId, message: "200", type: "bind" }
|
||||
ws.send(JSON.stringify(sendData));
|
||||
client.send(JSON.stringify(sendData));
|
||||
}
|
||||
else {
|
||||
const data = { type: "bind", clientId, targetId, message: "400" }
|
||||
ws.send(JSON.stringify(data))
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const sendData = { clientId, targetId, message: "401", type: "bind" }
|
||||
ws.send(JSON.stringify(sendData));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
// 服务器下发APP强度调节
|
||||
if (relations.get(clientId) !== targetId) {
|
||||
const data = { type: "bind", clientId, targetId, message: "402" }
|
||||
ws.send(JSON.stringify(data))
|
||||
return;
|
||||
}
|
||||
if (clients.has(targetId)) {
|
||||
const client = clients.get(targetId);
|
||||
const sendType = data.type - 1;
|
||||
const sendChannel = data.channel ? data.channel : 1;
|
||||
const sendStrength = data.type >= 3 ? data.strength : 1 //增加模式强度改成1
|
||||
const msg = "strength-" + sendChannel + "+" + sendType + "+" + sendStrength;
|
||||
const sendData = { type: "msg", clientId, targetId, message: msg }
|
||||
client.send(JSON.stringify(sendData));
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
// 服务器下发指定APP强度
|
||||
if (relations.get(clientId) !== targetId) {
|
||||
const data = { type: "bind", clientId, targetId, message: "402" }
|
||||
ws.send(JSON.stringify(data))
|
||||
return;
|
||||
}
|
||||
if (clients.has(targetId)) {
|
||||
const client = clients.get(targetId);
|
||||
const sendData = { type: "msg", clientId, targetId, message }
|
||||
client.send(JSON.stringify(sendData));
|
||||
}
|
||||
break;
|
||||
case "clientMsg":
|
||||
// 服务端下发给客户端的消息
|
||||
if (relations.get(clientId) !== targetId) {
|
||||
const data = { type: "bind", clientId, targetId, message: "402" }
|
||||
ws.send(JSON.stringify(data))
|
||||
return;
|
||||
}
|
||||
if (!data.message2) {
|
||||
const data = { type: "error", clientId, targetId, message: "501" }
|
||||
ws.send(JSON.stringify(data))
|
||||
return;
|
||||
}
|
||||
if (clients.has(targetId)) {
|
||||
let sendtimeA = data.time1 ? data.time1 : punishmentDuration; // AB通道的执行时间可以独立
|
||||
let sendtimeB = data.time2 ? data.time2 : punishmentDuration;
|
||||
const target = clients.get(targetId); //发送目标
|
||||
const sendDataA = { type: "msg", clientId, targetId, message: "pulse-" + data.message }
|
||||
const sendDataB = { type: "msg", clientId, targetId, message: "pulse-" + data.message2 }
|
||||
let totalSendsA = punishmentTime * sendtimeA;
|
||||
let totalSendsB = punishmentTime * sendtimeB;
|
||||
const timeSpace = 1000 / punishmentTime;
|
||||
|
||||
console.log("消息发送中,总消息数A:" + totalSendsA + "总消息数B:" + totalSendsB + "持续时间A:" + sendtimeA + "持续时间B:" + sendtimeB)
|
||||
if (clientTimers.has(clientId)) {
|
||||
// 计时器尚未工作完毕, 清除计时器且发送清除APP队列消息,延迟150ms重新发送新数据
|
||||
// 新消息覆盖旧消息逻辑
|
||||
ws.send("当前有正在发送的消息,覆盖之前的消息")
|
||||
|
||||
const timerId = clientTimers.get(clientId);
|
||||
clearInterval(timerId); // 清除定时器
|
||||
clientTimers.delete(clientId); // 清除 Map 中的对应项
|
||||
|
||||
// 发送APP波形队列清除指令
|
||||
const clearDataA = { clientId, targetId, message: "clear-1", type: "msg" }
|
||||
const clearDataB = { clientId, targetId, message: "clear-2", type: "msg" }
|
||||
target.send(JSON.stringify(clearDataA));
|
||||
target.send(JSON.stringify(clearDataB));
|
||||
setTimeout(() => {
|
||||
delaySendMsg(clientId, ws, target, sendDataA, sendDataB, totalSendsA, totalSendsB, timeSpace);
|
||||
}, 150);
|
||||
} else {
|
||||
// 不存在未发完的消息 直接发送
|
||||
delaySendMsg(clientId, ws, target, sendDataA, sendDataB, totalSendsA, totalSendsB, timeSpace);
|
||||
}
|
||||
} else {
|
||||
console.log(`未找到匹配的客户端,clientId: ${clientId}`);
|
||||
const sendData = { clientId, targetId, message: "404", type: "msg" }
|
||||
ws.send(JSON.stringify(sendData));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// 未定义的普通消息
|
||||
if (relations.get(clientId) !== targetId) {
|
||||
const data = { type: "bind", clientId, targetId, message: "402" }
|
||||
ws.send(JSON.stringify(data))
|
||||
return;
|
||||
}
|
||||
if (clients.has(clientId)) {
|
||||
const client = clients.get(clientId);
|
||||
const sendData = { type, clientId, targetId, message }
|
||||
client.send(JSON.stringify(sendData));
|
||||
} else {
|
||||
// 未找到匹配的客户端
|
||||
const sendData = { clientId, targetId, message: "404", type: "msg" }
|
||||
ws.send(JSON.stringify(sendData));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('close', function close() {
|
||||
// 连接关闭时,清除对应的 clientId 和 WebSocket 实例
|
||||
console.log('WebSocket 连接已关闭');
|
||||
// 遍历 clients Map,找到并删除对应的 clientId 条目
|
||||
let clientId = '';
|
||||
clients.forEach((value, key) => {
|
||||
if (value === ws) {
|
||||
// 拿到断开的客户端id
|
||||
clientId = key;
|
||||
}
|
||||
});
|
||||
console.log("断开的client id:" + clientId)
|
||||
relations.forEach((value, key) => {
|
||||
if (key === clientId) {
|
||||
//网页断开 通知app
|
||||
let appid = relations.get(key)
|
||||
let appClient = clients.get(appid)
|
||||
const data = { type: "break", clientId, targetId: appid, message: "209" }
|
||||
appClient.send(JSON.stringify(data))
|
||||
appClient.close(); // 关闭当前 WebSocket 连接
|
||||
relations.delete(key); // 清除关系
|
||||
console.log("对方掉线,关闭" + appid);
|
||||
}
|
||||
else if (value === clientId) {
|
||||
// app断开 通知网页
|
||||
let webClient = clients.get(key)
|
||||
const data = { type: "break", clientId: key, targetId: clientId, message: "209" }
|
||||
webClient.send(JSON.stringify(data))
|
||||
webClient.close(); // 关闭当前 WebSocket 连接
|
||||
relations.delete(key); // 清除关系
|
||||
console.log("对方掉线,关闭" + clientId);
|
||||
}
|
||||
})
|
||||
clients.delete(clientId); //清除ws客户端
|
||||
console.log("已清除" + clientId + " ,当前size: " + clients.size)
|
||||
});
|
||||
|
||||
ws.on('error', function (error) {
|
||||
// 错误处理
|
||||
console.error('WebSocket 异常:', error.message);
|
||||
// 在此通知用户异常,通过 WebSocket 发送消息给双方
|
||||
let clientId = '';
|
||||
// 查找当前 WebSocket 实例对应的 clientId
|
||||
for (const [key, value] of clients.entries()) {
|
||||
if (value === ws) {
|
||||
clientId = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!clientId) {
|
||||
console.error('无法找到对应的 clientId');
|
||||
return;
|
||||
}
|
||||
// 构造错误消息
|
||||
const errorMessage = 'WebSocket 异常: ' + error.message;
|
||||
|
||||
relations.forEach((value, key) => {
|
||||
// 遍历关系 Map,找到并通知没掉线的那一方
|
||||
if (key === clientId) {
|
||||
// 通知app
|
||||
let appid = relations.get(key)
|
||||
let appClient = clients.get(appid)
|
||||
const data = { type: "error", clientId: clientId, targetId: appid, message: "500" }
|
||||
appClient.send(JSON.stringify(data))
|
||||
}
|
||||
if (value === clientId) {
|
||||
// 通知网页
|
||||
let webClient = clients.get(key)
|
||||
const data = { type: "error", clientId: key, targetId: clientId, message: errorMessage }
|
||||
webClient.send(JSON.stringify(data))
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// 启动心跳定时器(如果尚未启动)
|
||||
if (!heartbeatInterval) {
|
||||
heartbeatInterval = setInterval(() => {
|
||||
// 遍历 clients Map(大于0个链接),向每个客户端发送心跳消息
|
||||
if (clients.size > 0) {
|
||||
console.log(relations.size, clients.size, '发送心跳消息:' + new Date().toLocaleString());
|
||||
clients.forEach((client, clientId) => {
|
||||
heartbeatMsg.clientId = clientId;
|
||||
heartbeatMsg.targetId = relations.get(clientId) || '';
|
||||
client.send(JSON.stringify(heartbeatMsg));
|
||||
});
|
||||
}
|
||||
}, 60 * 1000); // 每分钟发送一次心跳消息
|
||||
}
|
||||
});
|
||||
|
||||
function delaySendMsg(clientId, client, target, sendDataA, sendDataB, totalSendsA, totalSendsB, timeSpace) {
|
||||
// 发信计时器 AB通道会分别发送不同的消息和不同的数量 必须等全部发送完才会取消这个消息 新消息可以覆盖
|
||||
target.send(JSON.stringify(sendDataA)); //立即发送一次AB通道的消息
|
||||
target.send(JSON.stringify(sendDataB));
|
||||
totalSendsA--;
|
||||
totalSendsB--;
|
||||
if (totalSendsA > 0 || totalSendsB > 0) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 按频率发送消息给特定的客户端
|
||||
const timerId = setInterval(() => {
|
||||
if (totalSendsA > 0) {
|
||||
target.send(JSON.stringify(sendDataA));
|
||||
totalSendsA--;
|
||||
}
|
||||
if (totalSendsB > 0) {
|
||||
target.send(JSON.stringify(sendDataB));
|
||||
totalSendsB--;
|
||||
}
|
||||
// 如果达到发送次数上限,则停止定时器
|
||||
if (totalSendsA <= 0 && totalSendsB <= 0) {
|
||||
clearInterval(timerId);
|
||||
client.send("发送完毕")
|
||||
clientTimers.delete(clientId); // 删除对应的定时器
|
||||
resolve();
|
||||
}
|
||||
}, timeSpace); // 每隔频率倒数触发一次定时器
|
||||
|
||||
// 存储clientId与其对应的timerId
|
||||
clientTimers.set(clientId, timerId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue