示例说明
裸 WebSocket 只保证字节到达,消息形态全靠双方自觉。协议信封把版本号与类型字段标准化——版本不匹配在信封层就被发现,载荷按类型分发到对应 schema 校验。收到不认识的类型或非法载荷时以协议错误状态码主动关闭,让对端明确知道违反契约,而不是带着脏数据继续跑。
技术要点
- 信封与载荷两级校验分离
- 版本字段为协议演进留出通道
- 协议违规以 1002 状态码关闭
代码解读
Envelope 先校验原始 JSON 的结构性;Messages 表把类型名映射到载荷 schema,未注册类型直接拒绝。parseMessage 返回判别结果,调用方按 ok 分支处理;tick 更新消息打印行情,close 监听输出状态码与原因帮助定位断连性质。
使用建议
服务端也要实现对称的校验闭环。协议演进靠新增类型与版本协商,别改动既有载荷结构。心跳与超时要建立在协议内而不是依赖 TCP 保活。
代码示例
import { z } from 'zod';
import { WebSocket } from 'ws';
const Envelope = z.object({
v: z.literal(1),
type: z.string(),
payload: z.unknown(),
});
const Messages = {
'tick.update': z.object({ symbol: z.string(), price: z.number() }),
'alert.raise': z.object({ level: z.enum(['warn', 'critical']), text: z.string() }),
};
type MessageType = keyof typeof Messages;
function parseMessage(raw: string) {
const envelope = Envelope.safeParse(JSON.parse(raw));
if (!envelope.success) return { ok: false, reason: 'bad envelope' };
const schema = Messages[envelope.data.type as MessageType];
if (!schema) return { ok: false, reason: 'unknown type ' + envelope.data.type };
const payload = schema.safeParse(envelope.data.payload);
return payload.success
? { ok: true, type: envelope.data.type, payload: payload.data }
: { ok: false, reason: 'bad payload for ' + envelope.data.type };
}
const socket = new WebSocket('wss://stream.example.com/v1');
socket.on('message', (data) => {
const parsed = parseMessage(data.toString());
if (!parsed.ok) {
socket.close(1002, 'protocol violation');
return;
}
if (parsed.type === 'tick.update') {
console.log(parsed.payload.symbol, parsed.payload.price);
}
});
socket.on('close', (code, reason) => {
console.log('closed', code, reason.toString());
});