ws-load-test.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. // WebSocket 并发压测(模拟进入直播间+心跳)
  2. const WebSocket = require('ws');
  3. const crypto = require('crypto');
  4. const WS_HOST = process.env.WS_HOST || 'wss://im.fhhx.runtzh.com/ws/app/webSocket';
  5. const LIVE_ID = process.env.LIVE_ID; // 必填:直播间ID
  6. const USER_TYPE = parseInt(process.env.USER_TYPE || '0', 10); // 与前端一致,默认0
  7. const HEARTBEAT_MS = parseInt(process.env.HB || '15000', 10); // 心跳间隔
  8. const RATE = parseInt(process.env.RATE || '80', 10); // 每秒建链速率
  9. const DURATION_SEC = parseInt(process.env.DURATION || '600', 10);// 运行总时长(秒)
  10. // 新增:可选 Origin 和扫码参数
  11. const ORIGIN = process.env.ORIGIN || 'https://im.fhhx.runtzh.com';
  12. const COMPANY_ID = process.env.COMPANY_ID;
  13. const COMPANY_USER_ID = process.env.COMPANY_USER_ID;
  14. const UID_START = parseInt(process.env.UID_START || '900000000', 10);
  15. const UID_TO = parseInt(process.env.UID_TO || '0', 10);
  16. let COUNT = parseInt(process.env.COUNT || '0', 10);
  17. if (!LIVE_ID) {
  18. console.error('缺少 LIVE_ID。示例:LIVE_ID=359 node scripts/ws-load-test.js');
  19. process.exit(1);
  20. }
  21. if (UID_TO > 0 && UID_TO >= UID_START) {
  22. COUNT = UID_TO - UID_START + 1;
  23. }
  24. if (!COUNT || COUNT <= 0) {
  25. console.error('缺少 COUNT 或 UID_TO,无法确定连接数量');
  26. process.exit(1);
  27. }
  28. function buildUrl(userId) {
  29. const ts = Date.now();
  30. const message = `${LIVE_ID}${userId}${USER_TYPE}${ts}`;
  31. const signature = crypto.createHmac('sha256', String(ts)).update(message).digest('hex');
  32. let url = `${WS_HOST}?userId=${userId}&liveId=${LIVE_ID}&userType=${USER_TYPE}&timestamp=${ts}&signature=${signature}`;
  33. // 新增:可选扫码来源参数
  34. if (COMPANY_ID && COMPANY_USER_ID) {
  35. url += `&companyId=${encodeURIComponent(COMPANY_ID)}&companyUserId=${encodeURIComponent(COMPANY_USER_ID)}`;
  36. }
  37. return url;
  38. }
  39. const stats = { open: 0, close: 0, error: 0, msg: 0 };
  40. function startClient(index) {
  41. const userId = String(UID_START + index);
  42. const url = buildUrl(userId);
  43. // 新增:传入 origin 头,避免被服务端拒绝
  44. const ws = new WebSocket(url, { perMessageDeflate: false, origin: ORIGIN });
  45. let hbTimer = null;
  46. ws.on('open', () => {
  47. stats.open++;
  48. console.log(`[${userId}] connected`);
  49. const sendHb = () => {
  50. const now = Date.now();
  51. const payload = JSON.stringify({
  52. cmd: 'heartbeat',
  53. msg: 'ping',
  54. userId,
  55. liveId: LIVE_ID,
  56. timestamp: now,
  57. networkType: 'wifi',
  58. });
  59. ws.send(payload);
  60. };
  61. // 新增:打开即发送一次心跳, subsequent 定时发送
  62. sendHb();
  63. hbTimer = setInterval(sendHb, HEARTBEAT_MS);
  64. });
  65. // 新增:打印服务端返回的 cmd,观察是否有 heartbeat/entry/out 等
  66. ws.on('message', (msg) => {
  67. stats.msg++;
  68. try {
  69. const parsed = JSON.parse(msg);
  70. if (parsed && parsed.data && parsed.data.cmd) {
  71. console.log(`[${userId}] recv cmd=${parsed.data.cmd}`);
  72. }
  73. } catch (_) {}
  74. });
  75. ws.on('error', (err) => {
  76. stats.error++;
  77. console.error(`[${userId}] error`, err?.message || err);
  78. if (hbTimer) clearInterval(hbTimer);
  79. });
  80. ws.on('close', (code, reason) => {
  81. stats.close++;
  82. console.log(`[${userId}] closed code=${code} reason=${reason || ''}`);
  83. if (hbTimer) clearInterval(hbTimer);
  84. });
  85. }
  86. async function run() {
  87. console.log(`开始压测: liveId=${LIVE_ID}, count=${COUNT}, rate=${RATE}/s, hb=${HEARTBEAT_MS}ms, uid=[${UID_START}..${UID_START + COUNT - 1}]`);
  88. for (let i = 0; i < COUNT; i++) {
  89. startClient(i);
  90. const delayMs = Math.max(1, Math.floor(1000 / RATE));
  91. await new Promise((r) => setTimeout(r, delayMs));
  92. }
  93. const endAt = Date.now() + DURATION_SEC * 1000;
  94. const t = setInterval(() => {
  95. console.log(`[stats] open=${stats.open} close=${stats.close} error=${stats.error} msg=${stats.msg}`);
  96. if (Date.now() >= endAt) {
  97. console.log('压测结束。');
  98. clearInterval(t);
  99. process.exit(0);
  100. }
  101. }, 2000);
  102. }
  103. run().catch((e) => { console.error('运行异常', e); process.exit(1); });