核心设计思路
封装独立客户端类:将单个 WS 客户端的连接、发送、重连、资源清理逻辑封装为 WsTestClient 类,每个实例拥有独立的 socket、定时器、消息索引;
并发实例管理:通过配置 clientCount 指定并发数,批量创建实例并启动;
启动时间错开:配置 startOffset 让实例分批启动(避免瞬间建立大量连接,服务端扛不住);
全局资源管控:维护所有实例的列表,支持统一关闭、错误隔离(单个实例崩溃不影响其他实例)。
const WebSocket = require('ws'); // 全局配置 const config = { url: "ws://localhost:18888", // WS服务端地址 clientCount: 5, // 并发客户端实例数 startOffset: 5, // 实例启动错开时间(毫秒):避免同时连服务端,可设0 messages: [ "CwAzdQoAEgdhbmRyb2lk", "DgA1dQgBEMXGCBiJlQY=", "DgA1dQgBEMXGCBiJlQY=", "DgA1dQgBEMXGCBiJlQY=", "DgA1dQgBEMXGCBiJlQY=", "EgA2dQgBEMXGCBgBIJz5BygB", "EgA2dQgBEMXGCBgBIJz5BygB", "EgA2dQgBEMXGCBgBIJz5BygB", "DAA3dQgBEMXGCBgF", "DAA3dQgBEMXGCBgF", "DAA3dQgBEMXGCBgF", "EQA4dQgBEPXPrjYYtfgHIAE=", "EQA4dQgBEPXPrjYYtfgHIAE=", ], interval: 1*1000, // 单个实例的消息发送间隔 duration: 300*1000, // 单个实例的运行时长(从连接成功开始计时) reconnectMaxTimes: 0, // 单个实例最大重连次数 reconnectInterval: 3000, // 单个实例重连间隔 }; // 全局状态:管理所有客户端实例 const globalState = { clients: [], // 存储所有WS客户端实例 isStopped: false, // 是否全局停止 }; // 通用日志工具(增加实例ID标识) function log(clientId, type, msg) { function coverTimeStr2 (timestamp) { const date = new Date(timestamp * 1000); // 根据时间戳创建Date对象 const year = date.getFullYear(); // 获取年份 let month = date.getMonth() + 1; // 获取月份,需要加1 let day = date.getDate(); // 获取日期 let hour = date.getHours(); // 获取小时 let minute = date.getMinutes(); // 获取分钟 let second = date.getSeconds(); // 获取秒钟 month = month<10 ? ("0"+month) : (month) day = day<10 ? ("0"+day) : (day) hour = hour<10 ? ("0"+hour) : (hour) minute = minute<10 ? ("0"+minute) : (minute) second = second<10 ? ("0"+second) : (second) return `${year}/${month}/${day} ${hour}:${minute}:${second}`; // 拼接成格式化后的日期字符串 } const now = (new Date() ).getTime()/1000 const timestamp1 = coverTimeStr2(now) console.log(`[${timestamp1}] [客户端${clientId}] [${type}] ${msg}`); } /** * WebSocket测试客户端类(单个实例) * @param {number} clientId 实例唯一标识 */ class WsTestClient { constructor(clientId) { this.clientId = clientId; // 实例ID,用于日志区分 this.socket = null; // 单个实例的WS连接 this.intervalId = null; // 发送消息的定时器 this.timeoutId = null; // 运行时长超时定时器 this.reconnectCount = 0; // 重连计数 this.messageIndex = 0; // 已发送消息索引(单个实例独立) } // 清理当前实例的所有资源(定时器+连接) cleanResources() { if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; } if (this.timeoutId) { clearTimeout(this.timeoutId); this.timeoutId = null; } if (this.socket && this.socket.readyState === WebSocket.OPEN) { this.socket.close(1000, `客户端${this.clientId}正常关闭`); } this.socket = null; this.messageIndex = 0; } // 单个实例的重连逻辑 reconnect() { if (globalState.isStopped) return; // 全局停止则不重连 if (this.reconnectCount >= config.reconnectMaxTimes) { log(this.clientId, 'ERROR', `重连${config.reconnectMaxTimes}次失败,停止重连`); this.cleanResources(); return; } this.reconnectCount++; log(this.clientId, 'WARN', `第${this.reconnectCount}次重连中...`); setTimeout(() => this.connect(), config.reconnectInterval); } // 单个实例的消息发送逻辑 startSending() { if (globalState.isStopped) return; this.intervalId = setInterval(() => { // 1. 消息发完:停止发送 if (this.messageIndex >= config.messages.length) { // log(this.clientId, 'INFO', "⏹️ 消息序列发送完成"); // this.cleanResources(); // return; this.messageIndex=0 } // 2. 连接未打开:跳过发送 if (!this.socket || this.socket.readyState !== WebSocket.OPEN) { log(this.clientId, 'WARN', "❌ 连接未打开,跳过本次发送"); return; } // 3. 发送消息(容错处理) try { const buffer = Buffer.from(config.messages[this.messageIndex], 'base64'); this.socket.send(buffer); log(this.clientId, 'INFO', `📤 发送Protobuf消息 ${this.messageIndex + 1}/${config.messages.length}`); this.messageIndex++; } catch (error) { log(this.clientId, 'ERROR', `❌ 发送消息${this.messageIndex + 1}失败:${error.message}`); this.messageIndex++; // 失败也递增,避免卡死(可改为重试) } }, config.interval); } // 单个实例的连接逻辑 connect() { if (globalState.isStopped) return; this.cleanResources(); // 连接前清理旧资源 this.socket = new WebSocket(config.url); // 连接成功 this.socket.onopen = () => { log(this.clientId, 'INFO', "✅ 连接到WebSocket服务端成功"); this.reconnectCount = 0; // 重连成功,重置计数 this.startSending(); // 启动消息发送 // 重置运行时长超时:从连接成功开始计时 this.timeoutId = setTimeout(() => { log(this.clientId, 'INFO', "⏰ 运行时长超时,关闭连接"); this.cleanResources(); }, config.duration); }; // 接收服务端消息 this.socket.onmessage = (event) => { log(this.clientId, 'INFO', `📥 收到服务端消息:${event.data}`); }; // 连接关闭 this.socket.onclose = (code, reason) => { log(this.clientId, 'WARN', `🔌 连接断开(code: ${code}, reason: ${reason})`); this.reconnect(); // 断开自动重连 }; // 连接错误 this.socket.onerror = (error) => { log(this.clientId, 'ERROR', `❌ 连接错误:${error.message}`); this.cleanResources(); this.reconnect(); // 错误自动重连 }; } // 启动单个实例 start() { log(this.clientId, 'INFO', "🚀 启动客户端实例"); this.connect(); } // 停止单个实例 stop() { log(this.clientId, 'INFO', "🛑 停止客户端实例"); this.cleanResources(); } } /** * 全局启动所有客户端实例 */ function runAllClients() { log('GLOBAL', 'INFO', `🚀 开始启动${config.clientCount}个WebSocket客户端实例`); globalState.isStopped = false; // 批量创建并启动实例(错开时间) for (let i = 0; i < config.clientCount; i++) { const client = new WsTestClient(i + 1); // 实例ID从1开始 globalState.clients.push(client); // 错开启动时间 setTimeout(() => client.start(), i * config.startOffset); } // 全局错误捕获:避免单个实例错误导致进程崩溃 process.on('uncaughtException', (err) => { log('GLOBAL', 'ERROR', `未捕获异常:${err.message}`); stopAllClients(); }); process.on('unhandledRejection', (err) => { log('GLOBAL', 'ERROR', `未处理的Promise拒绝:${err.message}`); stopAllClients(); }); // 监听进程退出,清理所有资源 process.on('SIGINT', () => { log('GLOBAL', 'INFO', "🛑 接收到退出信号,关闭所有客户端"); stopAllClients(); process.exit(0); }); } /** * 全局停止所有客户端实例 */ function stopAllClients() { globalState.isStopped = true; log('GLOBAL', 'INFO', `🛑 开始关闭所有${globalState.clients.length}个客户端实例`); globalState.clients.forEach(client => client.stop()); globalState.clients = []; // 清空实例列表 } // 执行启动 runAllClients();核心配置说明(重点关注)
配置项 作用 clientCount并发客户端实例数(比如设为 5,就会启动 5 个独立的 WS 连接) startOffset实例启动错开时间(毫秒):设为 500 表示第 1 个实例立即启动,第 2 个 500ms 后,以此类推,避免瞬间压垮服务端 其他配置 与单实例一致,但作用于每个独立实例 (比如每个实例都会按 interval发送消息)关键特性
- 实例隔离 :每个客户端实例有独立的
socket、定时器、消息索引,单个实例出错 / 重连不会影响其他实例;- 日志区分 :所有日志都带「客户端 ID」,比如
[客户端3],能清晰区分每个实例的连接 / 发送状态;- 柔性启动 :
startOffset配置让实例分批启动,避免短时间内建立大量连接,服务端更易承受;- 全局管控 :支持
stopAllClients()统一关闭所有实例,也能监听进程退出信号自动清理资源;- 容错保留:单个实例仍保留重连、消息发送容错、超时关闭等逻辑,稳定性不打折。
将单个 WebSocket 客户端封装为实例
chalmers_152025-12-24 14:17
相关推荐
宠..5 小时前
创建单选按钮控件@Mr Wang6 小时前
云服务器之使用jupyter运行ipynb文件2401_865854886 小时前
服务器的windows和Linux系统有什么区别海域云-罗鹏6 小时前
企业服务器防黑客攻击:WAF防火墙部署全攻略云飞云共享云桌面6 小时前
SolidWorks服务器怎么实现研发软件多人共享、数据安全管理ZeroNews内网穿透6 小时前
EasyNode 结合 ZeroNews,实现远程管理服务器晚晶7 小时前
[C++/流媒体/tcp/rtsp]构建一个简单的流媒体转发服务器,用于将rtsp推流转发出去翼龙云_cloud7 小时前
阿里云渠道商:在更换阿里云 GPU 公网 IP 时,如何确保数据的安全性?山川而川-R7 小时前
在香橙派5pro上的ubuntu22.04系统烧录镜像_2_12.23