2、WebSocket协议基础:握手过程、帧结构、与HTTP的对比、心跳机制
各位同学,咱们今天聊聊WebSocket。做实时行情接入,这玩意儿是绕不开的坎儿。说白了,它就是让客户端和服务器之间能随时“唠嗑”的通道,不用像HTTP那样每次都得先敲门问好。
我个人习惯把WebSocket理解成“打了鸡血的TCP”。它底层还是TCP那套东西,但加了一层轻量级的封装,让双向通信变得特别丝滑。你想想看,行情数据每秒几十上百次推送,如果用HTTP轮询,服务器得累死,带宽也扛不住。
2.1 握手过程:从HTTP升级到WebSocket
WebSocket的握手,其实是一次特殊的HTTP请求。客户端先发一个GET请求,告诉服务器:“我想升级成WebSocket协议”。服务器如果同意,就回一个101状态码。就这么简单。
我记得第一次抓包看握手过程时,还觉得挺神奇的。咱们来看看具体是怎么玩的:
// 客户端发起的握手请求
GET /ws/stock/real HTTP/1.1
Host: api.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://example.com
这里有个关键字段——Sec-WebSocket-Key。它是一个Base64编码的随机值,服务器收到后会拼接一个固定魔数,然后做SHA-1哈希,再Base64编码,返回给客户端。客户端验证这个值,就能确认服务器确实支持WebSocket,而不是随便回了段数据。
服务器响应长这样:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Upgrade头,导致握手失败。解决方案是让运维把WebSocket的端口单独放行,或者直接用WSS(WebSocket Secure)走443端口,这样防火墙一般不会拦。2.2 帧结构:数据是怎么包装的
握手完成后,数据就不再是HTTP那种文本格式了,而是二进制的帧。WebSocket的帧结构设计得很精巧,我画了张图帮你理解:
嗯,这里要注意一个坑。客户端发给服务器的数据,必须设置Mask位为1,并且带上4字节的掩码。服务器发给客户端则不需要。为什么?早期设计时担心代理服务器缓存污染,所以强制客户端做掩码。我见过有新手写客户端时忘了加掩码,结果服务器直接断开连接。
Opcode字段也很重要。咱们做行情接入,最常用的是0x1(文本帧)和0x2(二进制帧)。行情数据一般用JSON文本,但有些交易所为了省带宽,会用Protobuf压缩成二进制。这时候就得用二进制帧接收。
2.3 与HTTP的对比:为什么WebSocket更适合行情
咱们直接看对比表,一目了然:
| 特性 | HTTP | WebSocket |
|---|---|---|
| 通信模式 | 请求-响应,客户端主动 | 全双工,双方随时发 |
| 协议开销 | 每次请求都带完整头部(几百字节) | 握手后,帧头仅2-14字节 |
| 实时性 | 轮询延迟高,至少一个RTT | 服务器主动推送,延迟低 |
| 连接复用 | HTTP/1.1需长连接,HTTP/2多路复用 | 单连接双向通信 |
| 数据格式 | 文本为主(HTML/JSON) | 文本+二进制,灵活 |
| 适用场景 | 网页浏览、REST API | 实时行情、聊天、游戏 |
说白了,HTTP就像寄信——你写一封信寄出去,等回信。WebSocket就像打电话——接通后随时说话。做量化交易,行情晚1毫秒可能就是几万块的差距,你想想看该选哪个?
2.4 心跳机制:保活的艺术
连接建立后,怎么知道对方还活着?这就是心跳机制要解决的问题。WebSocket协议内置了Ping/Pong帧,专门干这个。
我曾经踩过一个坑。有个交易所的行情源,网络偶尔会断个几秒钟,但TCP连接没断开。我的程序傻傻等着数据,结果错过了几笔关键行情。后来加了心跳检测,5秒没收到Pong就重连,问题就解决了。
心跳机制一般这么设计:
// 伪代码示例:心跳检测逻辑
let heartbeatInterval = 5000; // 5秒发一次Ping
let timeout = 10000; // 10秒没收到Pong就判定断开
function startHeartbeat(ws) {
let timer = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.ping(); // 发送Ping帧
setTimeout(() => {
if (!pongReceived) {
console.log('心跳超时,准备重连');
ws.close();
reconnect();
}
}, timeout);
}
}, heartbeatInterval);
}
另外,心跳间隔不是越短越好。太频繁会增加网络开销,太慢又可能错过断连检测。我个人习惯:
- 公网环境:5-10秒一次
- 内网环境:15-30秒一次
- 如果交易所文档有明确要求,按他们的来
嗯,关于心跳还有个细节。Ping和Pong帧的Opcode分别是0x9和0xA。它们可以带应用数据,但一般不带。收到Ping后,协议栈会自动回复Pong,不需要应用层处理。但如果你想自定义心跳逻辑,也可以手动处理。
好了,WebSocket的基础就这些。记住三个核心点:握手靠HTTP升级、数据用帧结构封装、保活靠心跳机制。把这些搞明白,后面接入行情源就顺了。
核心要点回顾:
- 握手:客户端发
Upgrade: websocket,服务器回101 - 帧结构:FIN+Opcode+Payload Len+Mask+Data
- 对比HTTP:全双工、低开销、低延迟
- 心跳:Ping/Pong帧,5-10秒间隔,超时重连
公众号:蓝海数据掘金营,微信deep3321