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);
}
}