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的帧结构设计得很精巧,我画了张图帮你理解:

WebSocket帧结构 FIN RSV Opcode M Payload Len Masking Key (可选) Payload Data (实际数据) 1位 4位 7/16/64位 关键字段说明: • FIN: 1表示这是最后一帧,0表示还有后续帧 • Opcode: 1=文本帧, 2=二进制帧, 8=关闭帧, 9=Ping, 10=Pong • M(Mask): 客户端发数据必须置1,服务器发数据必须置0 • Payload Len: 数据长度,7位不够用就扩展到16位或64位 • Masking Key: 4字节掩码,用于防止缓存污染攻击

嗯,这里要注意一个坑。客户端发给服务器的数据,必须设置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);
}

注意:有些代理服务器或负载均衡器会主动断开空闲连接。即使你的应用层不需要心跳,也建议每隔30-60秒发一次Ping,防止中间设备把连接掐了。我遇到过某云厂商的SLB,60秒没动静就断连,坑了不少人。

另外,心跳间隔不是越短越好。太频繁会增加网络开销,太慢又可能错过断连检测。我个人习惯:

  • 公网环境:5-10秒一次
  • 内网环境:15-30秒一次
  • 如果交易所文档有明确要求,按他们的来

嗯,关于心跳还有个细节。Ping和Pong帧的Opcode分别是0x90xA。它们可以带应用数据,但一般不带。收到Ping后,协议栈会自动回复Pong,不需要应用层处理。但如果你想自定义心跳逻辑,也可以手动处理。

好了,WebSocket的基础就这些。记住三个核心点:握手靠HTTP升级、数据用帧结构封装、保活靠心跳机制。把这些搞明白,后面接入行情源就顺了。

核心要点回顾:

  • 握手:客户端发Upgrade: websocket,服务器回101
  • 帧结构:FIN+Opcode+Payload Len+Mask+Data
  • 对比HTTP:全双工、低开销、低延迟
  • 心跳:Ping/Pong帧,5-10秒间隔,超时重连

公众号:蓝海数据掘金营,微信deep3321