使用 k6 对 WebSocket 测试

WebSocket 测试

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,广泛应用于实时通信场景,如在线聊天、实时数据推送、游戏、协作工具等。k6 提供了完整的 WebSocket 支持,可以模拟大量并发连接,测试服务器的实时通信能力。

WebSocket 连接

基本连接

在 k6 中,使用 k6/ws 模块来建立 WebSocket 连接。连接成功后,服务器会返回状态码 101(Switching Protocols),表示协议升级成功。

javascript 复制代码
import ws from 'k6/ws';
import { check } from 'k6';

export default function () {
  const url = 'wss://echo.websocket.org';
  const params = { tags: { name: 'WebSocket' } };
  
  const response = ws.connect(url, params, function (socket) {
    socket.on('open', function () {
      console.log('WebSocket 连接已建立');
    });
    
    socket.on('close', function () {
      console.log('WebSocket 连接已关闭');
    });
  });
  
  check(response, {
    'WebSocket 连接成功': (r) => r && r.status === 101,
  });
}

关键点说明:

  • ws.connect() 方法接受三个参数:URL、连接参数对象和回调函数
  • 回调函数中的 socket 对象用于处理连接的各种事件
  • open 事件在连接成功建立时触发
  • close 事件在连接关闭时触发
  • 连接参数可以包含标签、超时设置等配置

连接参数配置

javascript 复制代码
import ws from 'k6/ws';

export default function () {
  const url = 'wss://api.example.com/ws';
  const params = {
    tags: { 
      name: 'WebSocket连接',
      environment: 'production'
    },
    headers: {
      'Authorization': 'Bearer token123',
      'X-Client-Version': '1.0.0'
    }
  };
  
  ws.connect(url, params, function (socket) {
    socket.on('open', function () {
      console.log('带认证的连接已建立');
    });
  });
}

消息发送与接收

WebSocket 的核心功能是双向消息传递。客户端可以随时向服务器发送消息,也可以接收服务器推送的消息。

发送消息

WebSocket 支持发送文本和二进制消息。在实际应用中,通常使用 JSON 格式进行结构化数据传输。

javascript 复制代码
import ws from 'k6/ws';

export default function () {
  const url = 'wss://echo.websocket.org';
  
  ws.connect(url, {}, function (socket) {
    socket.on('open', function () {
      // 发送简单文本消息
      socket.send('Hello from k6!');
      
      // 发送 JSON 格式的结构化消息
      socket.send(JSON.stringify({
        type: 'message',
        content: 'Hello',
        timestamp: Date.now(),
        userId: __VU
      }));
      
      // 发送二进制数据(如果需要)
      // socket.sendBinary(new Uint8Array([1, 2, 3, 4]));
    });
  });
}

接收消息

接收消息通过监听 message 事件实现。消息数据可能是文本或二进制格式,需要根据实际情况进行解析。

javascript 复制代码
import ws from 'k6/ws';
import { check } from 'k6';

export default function () {
  const url = 'wss://echo.websocket.org';
  
  ws.connect(url, {}, function (socket) {
    socket.on('open', function () {
      socket.send('Hello');
    });
    
    socket.on('message', function (data) {
      console.log('收到消息:', data);
      
      // 如果是 JSON 格式,进行解析和验证
      try {
        const message = JSON.parse(data);
        check(message, {
          '消息包含必要字段': (m) => m.type !== undefined,
        });
      } catch (e) {
        // 非 JSON 消息,直接处理文本
        console.log('收到文本消息:', data);
      }
      
      socket.close();
    });
  });
}

事件处理

WebSocket 连接生命周期中的各种事件都需要正确处理,以确保测试的稳定性和准确性。

完整事件类型

k6 的 WebSocket 实现支持以下事件类型:

javascript 复制代码
import ws from 'k6/ws';
import { check } from 'k6';

export default function () {
  const url = 'wss://echo.websocket.org';
  
  ws.connect(url, {}, function (socket) {
    // 连接成功建立时触发
    socket.on('open', function () {
      console.log('连接打开');
      socket.send('test message');
    });
    
    // 收到服务器消息时触发
    socket.on('message', function (data) {
      console.log('收到消息:', data);
    });
    
    // 连接关闭时触发
    socket.on('close', function () {
      console.log('连接关闭');
    });
    
    // 发生错误时触发
    socket.on('error', function (e) {
      console.error('WebSocket 错误:', e);
    });
    
    // 收到 ping 帧时触发(服务器发送的心跳)
    socket.on('ping', function () {
      console.log('收到 ping,自动回复 pong');
    });
    
    // 收到 pong 帧时触发(服务器对 ping 的响应)
    socket.on('pong', function () {
      console.log('收到 pong,连接正常');
    });
  });
}

事件说明:

  • open: 连接建立成功,此时可以开始发送消息
  • message: 收到服务器消息,需要根据业务逻辑处理
  • close: 连接关闭,可能是正常关闭或异常断开
  • error: 连接或通信过程中发生错误
  • ping/pong: WebSocket 协议层面的心跳机制,用于保持连接活跃

连接管理

良好的连接管理是 WebSocket 测试的关键,包括超时控制、优雅关闭、重连机制等。

超时设置

设置连接超时可以防止测试脚本长时间挂起,确保测试能够正常结束。

javascript 复制代码
import ws from 'k6/ws';

export default function () {
  const url = 'wss://echo.websocket.org';
  
  ws.connect(url, {}, function (socket) {
    // 设置连接超时,超时后自动关闭
    socket.setTimeout(function () {
      console.log('连接超时,关闭连接');
      socket.close();
    }, 5000); // 5秒超时
    
    socket.on('open', function () {
      console.log('连接已建立');
    });
  });
}

手动关闭连接

在完成测试后,应该主动关闭连接,释放服务器资源。

javascript 复制代码
import ws from 'k6/ws';

export default function () {
  const url = 'wss://echo.websocket.org';
  
  ws.connect(url, {}, function (socket) {
    let messageCount = 0;
    
    socket.on('open', function () {
      socket.send('Hello');
    });
    
    socket.on('message', function (data) {
      console.log('收到:', data);
      messageCount++;
      
      // 收到足够消息后关闭连接
      if (messageCount >= 5) {
        socket.close();
      }
    });
    
    socket.on('close', function () {
      console.log('连接已关闭');
    });
  });
}

连接状态检查

javascript 复制代码
import ws from 'k6/ws';

export default function () {
  const url = 'wss://api.example.com/ws';
  
  ws.connect(url, {}, function (socket) {
    socket.on('open', function () {
      // 检查连接状态
      if (socket.readyState === ws.OPEN) {
        console.log('连接状态:已打开');
        socket.send('test');
      }
    });
  });
}

实时通信测试场景

WebSocket 常用于实时通信场景,下面介绍几个典型的测试场景。

聊天应用测试

模拟多用户同时在线聊天的场景,测试服务器的并发处理能力。

javascript 复制代码
import ws from 'k6/ws';
import { check } from 'k6';

export const options = {
  vus: 10,
  duration: '30s',
};

export default function () {
  const url = 'wss://chat.example.com/ws';
  const username = `user${__VU}`;
  
  const response = ws.connect(url, {}, function (socket) {
    socket.on('open', function () {
      // 发送登录消息
      socket.send(JSON.stringify({
        type: 'login',
        username: username,
        timestamp: Date.now()
      }));
    });
    
    socket.on('message', function (data) {
      const message = JSON.parse(data);
      
      if (message.type === 'login_success') {
        // 登录成功后,发送聊天消息
        socket.send(JSON.stringify({
          type: 'chat',
          message: `Hello from ${username}`,
          room: 'general'
        }));
      }
      
      if (message.type === 'chat') {
        check(message, {
          '消息包含用户名': (m) => m.username !== undefined,
          '消息包含内容': (m) => m.message !== undefined,
        });
        
        console.log(`[${message.username}]: ${message.message}`);
        
        // 收到消息后关闭连接
        socket.close();
      }
      
      if (message.type === 'error') {
        console.error('服务器错误:', message.error);
        socket.close();
      }
    });
    
    socket.on('error', function (e) {
      console.error('连接错误:', e);
    });
  });
  
  check(response, {
    'WebSocket 连接成功': (r) => r && r.status === 101,
  });
}

实时数据推送测试

测试服务器向客户端推送实时数据的能力,如股票价格、监控指标等。

javascript 复制代码
import ws from 'k6/ws';
import { check } from 'k6';

export const options = {
  vus: 5,
  duration: '1m',
};

export default function () {
  const url = 'wss://api.example.com/realtime';
  
  ws.connect(url, {}, function (socket) {
    let messageCount = 0;
    let lastTimestamp = 0;
    
    socket.on('open', function () {
      // 订阅数据频道
      socket.send(JSON.stringify({
        action: 'subscribe',
        channel: 'updates',
        filters: {
          type: 'price'
        }
      }));
    });
    
    socket.on('message', function (data) {
      messageCount++;
      const update = JSON.parse(data);
      
      check(update, {
        '数据包含时间戳': (u) => u.timestamp !== undefined,
        '数据包含内容': (u) => u.data !== undefined,
        '时间戳递增': (u) => u.timestamp >= lastTimestamp,
      });
      
      lastTimestamp = update.timestamp;
      console.log(`收到更新 #${messageCount}:`, update.data);
      
      // 收到足够消息后关闭连接
      if (messageCount >= 10) {
        socket.close();
      }
    });
    
    // 设置超时,防止连接长时间挂起
    socket.setTimeout(function () {
      console.log('测试超时,关闭连接');
      socket.close();
    }, 30000); // 30秒超时
  });
}

心跳检测机制

心跳检测用于保持连接活跃,检测连接是否正常。这对于长时间保持连接的场景非常重要。

javascript 复制代码
import ws from 'k6/ws';
import { check } from 'k6';

export default function () {
  const url = 'wss://api.example.com/ws';
  
  ws.connect(url, {}, function (socket) {
    let pongReceived = false;
    let heartbeatInterval;
    
    socket.on('open', function () {
      console.log('连接已建立,开始心跳检测');
      
      // 定期发送心跳 ping
      heartbeatInterval = setInterval(function () {
        pongReceived = false;
        socket.ping();
        console.log('发送 ping');
      }, 5000); // 每5秒发送一次心跳
    });
    
    socket.on('pong', function () {
      pongReceived = true;
      console.log('收到 pong,连接正常');
      
      check(pongReceived, {
        '心跳响应正常': () => pongReceived === true,
      });
    });
    
    socket.on('close', function () {
      console.log('连接关闭,停止心跳');
      if (heartbeatInterval) {
        clearInterval(heartbeatInterval);
      }
    });
    
    socket.on('error', function (e) {
      console.error('连接错误:', e);
      if (heartbeatInterval) {
        clearInterval(heartbeatInterval);
      }
    });
    
    // 设置总超时时间
    socket.setTimeout(function () {
      console.log('测试完成,关闭连接');
      if (heartbeatInterval) {
        clearInterval(heartbeatInterval);
      }
      socket.close();
    }, 60000); // 60秒后关闭
  });
}

性能测试配置

WebSocket 性能测试主要关注并发连接数、消息吞吐量、延迟等指标。

并发连接测试

测试服务器能够同时处理多少个 WebSocket 连接。

javascript 复制代码
import ws from 'k6/ws';
import { check } from 'k6';

export const options = {
  stages: [
    { duration: '30s', target: 100 },  // 30秒内增加到100个并发连接
    { duration: '1m', target: 100 },   // 保持100个连接1分钟
    { duration: '30s', target: 200 },  // 增加到200个连接
    { duration: '1m', target: 200 },   // 保持200个连接1分钟
    { duration: '30s', target: 0 },   // 逐步减少到0
  ],
  thresholds: {
    'ws_connecting': ['rate<0.01'],      // 连接失败率小于1%
    'ws_session_duration': ['p(95)<2000'], // 95%的连接在2秒内建立
  },
};

export default function () {
  const url = 'wss://api.example.com/ws';
  
  const response = ws.connect(url, {}, function (socket) {
    socket.on('open', function () {
      socket.send(JSON.stringify({
        type: 'test',
        message: 'concurrent connection test'
      }));
    });
    
    socket.on('message', function (data) {
      const message = JSON.parse(data);
      check(message, {
        '收到响应': (m) => m !== null,
      });
      socket.close();
    });
    
    socket.on('error', function (e) {
      console.error('连接错误:', e);
    });
  });
  
  check(response, {
    '连接成功': (r) => r && r.status === 101,
  });
}

消息吞吐量测试

测试服务器处理消息的能力,包括每秒发送和接收的消息数量。

javascript 复制代码
import ws from 'k6/ws';
import { check, Counter, Rate } from 'k6';

const messagesSent = new Counter('ws_messages_sent');
const messagesReceived = new Counter('ws_messages_received');
const messageRate = new Rate('ws_message_rate');

export const options = {
  vus: 10,
  duration: '30s',
};

export default function () {
  const url = 'wss://api.example.com/ws';
  
  ws.connect(url, {}, function (socket) {
    let sentCount = 0;
    let receivedCount = 0;
    const totalMessages = 100;
    
    socket.on('open', function () {
      // 快速发送多条消息
      for (let i = 0; i < totalMessages; i++) {
        socket.send(JSON.stringify({
          id: i,
          message: `test message ${i}`,
          timestamp: Date.now()
        }));
        sentCount++;
        messagesSent.add(1);
      }
    });
    
    socket.on('message', function (data) {
      receivedCount++;
      messagesReceived.add(1);
      
      try {
        const message = JSON.parse(data);
        check(message, {
          '消息ID有效': (m) => m.id !== undefined,
        });
        messageRate.add(1);
      } catch (e) {
        messageRate.add(0);
      }
      
      // 收到所有消息后关闭连接
      if (receivedCount >= totalMessages) {
        console.log(`发送: ${sentCount}, 接收: ${receivedCount}`);
        socket.close();
      }
    });
    
    socket.setTimeout(function () {
      socket.close();
    }, 10000); // 10秒超时
  });
}

消息延迟测试

测试消息从发送到接收的延迟时间。

javascript 复制代码
import ws from 'k6/ws';
import { Trend } from 'k6';

const messageLatency = new Trend('ws_message_latency');

export default function () {
  const url = 'wss://api.example.com/ws';
  
  ws.connect(url, {}, function (socket) {
    socket.on('open', function () {
      // 发送带时间戳的消息
      const sendTime = Date.now();
      socket.send(JSON.stringify({
        type: 'latency_test',
        sendTime: sendTime
      }));
    });
    
    socket.on('message', function (data) {
      const receiveTime = Date.now();
      const message = JSON.parse(data);
      
      if (message.sendTime) {
        const latency = receiveTime - message.sendTime;
        messageLatency.add(latency);
        console.log(`消息延迟: ${latency}ms`);
      }
      
      socket.close();
    });
  });
}

最佳实践

错误处理

完善的错误处理机制可以确保测试的稳定性和可靠性。

javascript 复制代码
import ws from 'k6/ws';
import { check } from 'k6';

export default function () {
  const url = 'wss://api.example.com/ws';
  
  const response = ws.connect(url, {}, function (socket) {
    // 始终监听错误事件
    socket.on('error', function (e) {
      console.error('WebSocket 错误:', e);
      // 记录错误指标
      check(null, {
        '连接无错误': () => false,
      }, { error: e.toString() });
    });
    
    socket.on('open', function () {
      try {
        socket.send('test');
      } catch (e) {
        console.error('发送消息失败:', e);
      }
    });
    
    socket.on('message', function (data) {
      try {
        const message = JSON.parse(data);
        // 处理消息
      } catch (e) {
        console.error('消息解析失败:', e);
      }
    });
  });
  
  // 检查连接响应
  check(response, {
    '连接成功': (r) => r && r.status === 101,
  }, {
    url: url
  });
}

超时处理

合理设置超时时间,避免测试脚本无限期等待。

javascript 复制代码
import ws from 'k6/ws';

export default function () {
  const url = 'wss://api.example.com/ws';
  
  ws.connect(url, {}, function (socket) {
    let messageReceived = false;
    
    socket.on('open', function () {
      socket.send('test');
    });
    
    socket.on('message', function (data) {
      messageReceived = true;
      socket.close();
    });
    
    // 设置超时,如果超时仍未收到消息则关闭连接
    socket.setTimeout(function () {
      if (!messageReceived) {
        console.log('等待消息超时,关闭连接');
      }
      socket.close();
    }, 10000); // 10秒超时
  });
}

消息验证

对接收到的消息进行格式和内容验证,确保服务器返回的数据符合预期。

javascript 复制代码
import ws from 'k6/ws';
import { check } from 'k6';

export default function () {
  const url = 'wss://api.example.com/ws';
  
  ws.connect(url, {}, function (socket) {
    socket.on('open', function () {
      socket.send(JSON.stringify({
        type: 'request',
        data: 'test'
      }));
    });
    
    socket.on('message', function (data) {
      try {
        const message = JSON.parse(data);
        
        // 验证消息格式和内容
        check(message, {
          '消息是对象': (m) => typeof m === 'object',
          '包含 type 字段': (m) => m.type !== undefined,
          '包含 data 字段': (m) => m.data !== undefined,
          'type 字段有效': (m) => ['response', 'error'].includes(m.type),
          'data 不为空': (m) => m.data !== null && m.data !== '',
        });
        
        if (message.type === 'error') {
          console.error('服务器返回错误:', message.error);
        }
        
      } catch (e) {
        console.error('消息解析失败:', e);
        check(null, {
          '消息格式正确': () => false,
        });
      }
      
      socket.close();
    });
  });
}

资源清理

确保在测试结束时正确清理资源,避免资源泄漏。

javascript 复制代码
import ws from 'k6/ws';

export default function () {
  const url = 'wss://api.example.com/ws';
  
  ws.connect(url, {}, function (socket) {
    let heartbeatInterval;
    let timeoutId;
    
    socket.on('open', function () {
      // 设置心跳
      heartbeatInterval = setInterval(function () {
        socket.ping();
      }, 5000);
      
      // 设置超时
      timeoutId = socket.setTimeout(function () {
        cleanup();
        socket.close();
      }, 30000);
      
      socket.send('test');
    });
    
    socket.on('message', function (data) {
      cleanup();
      socket.close();
    });
    
    socket.on('close', function () {
      cleanup();
    });
    
    socket.on('error', function (e) {
      cleanup();
    });
    
    function cleanup() {
      if (heartbeatInterval) {
        clearInterval(heartbeatInterval);
        heartbeatInterval = null;
      }
      if (timeoutId) {
        clearTimeout(timeoutId);
        timeoutId = null;
      }
    }
  });
}

使用指标和阈值

利用 k6 的指标和阈值功能,自动判断测试是否通过。

javascript 复制代码
import ws from 'k6/ws';
import { check, Counter, Rate, Trend } from 'k6';

const wsConnections = new Counter('ws_connections_total');
const wsErrors = new Counter('ws_errors_total');
const wsSuccessRate = new Rate('ws_success_rate');
const wsLatency = new Trend('ws_message_latency');

export const options = {
  vus: 50,
  duration: '1m',
  thresholds: {
    'ws_success_rate': ['rate>0.95'],           // 成功率大于95%
    'ws_errors_total': ['count<10'],            // 错误总数小于10
    'ws_message_latency': ['p(95)<100'],       // 95%的消息延迟小于100ms
    'ws_connecting': ['rate<0.01'],            // 连接失败率小于1%
  },
};

export default function () {
  const url = 'wss://api.example.com/ws';
  
  const response = ws.connect(url, {}, function (socket) {
    wsConnections.add(1);
    
    socket.on('open', function () {
      const sendTime = Date.now();
      socket.send(JSON.stringify({ test: true }));
    });
    
    socket.on('message', function (data) {
      const receiveTime = Date.now();
      wsLatency.add(receiveTime - Date.now());
      wsSuccessRate.add(1);
      socket.close();
    });
    
    socket.on('error', function (e) {
      wsErrors.add(1);
      wsSuccessRate.add(0);
      console.error('连接错误:', e);
    });
  });
  
  const success = check(response, {
    '连接成功': (r) => r && r.status === 101,
  });
  
  if (!success) {
    wsErrors.add(1);
    wsSuccessRate.add(0);
  }
}
相关推荐
福尔摩斯张1 小时前
从Select到Epoll:深度解析Linux I/O多路复用演进之路(超详细)
linux·运维·服务器·c语言·网络
robur1 小时前
H3C V7路由器升级软件时提示无足够存储空间
网络·路由器·升级·h3c
云飞云共享云桌面2 小时前
研发部门使用SolidWorks,三维设计云桌面应该怎么选?
运维·服务器·前端·网络·自动化·电脑
MicroTech20252 小时前
微算法科技(NASDAQ:MLGO)优化区块链身份证明(PoI)技术:构建可信网络的基石
网络·科技·区块链
honsor2 小时前
一种采用POE供电的RJ45网络型温湿度传感器
运维·服务器·网络
Tandy12356_2 小时前
手写TCP/IP协议栈——数据包结构定义
c语言·网络·c++·计算机网络
繁华似锦respect2 小时前
HTTPS 中 TLS 协议详细过程 + 数字证书/签名深度解析
开发语言·c++·网络协议·http·单例模式·设计模式·https
Tandy12356_2 小时前
手写TCP/IP协议栈——环境配置
服务器·网络·网络协议·tcp/ip
老蒋新思维3 小时前
创客匠人洞察:创始人 IP 变现的长期主义,文化根基与 AI 杠杆的双重赋能
大数据·网络·人工智能·tcp/ip·重构·创始人ip·创客匠人