2、WebSocket基础:WebSocket协议原理、与HTTP的区别、心跳机制

各位同学,今天我们来聊聊WebSocket。这东西在量化交易里有多重要呢?我这么说吧,没有WebSocket,你的行情数据延迟可能比别人慢几百毫秒。在抢单的世界里,这几百毫秒足够让一笔交易从盈利变成亏损。

我个人习惯把WebSocket理解为「HTTP的升级版」。但严格来说,它俩根本不是一回事。咱们先看看协议原理。

2.1 WebSocket协议原理

WebSocket的设计初衷很简单:让客户端和服务器之间建立一个全双工的通信通道。什么叫全双工?就是两边可以同时说话,不用等对方说完。

我记得第一次接触WebSocket时,脑子里全是问号:「这不就是长连接吗?」后来踩了坑才明白,长连接和全双工是两码事。HTTP的长连接只是复用TCP连接,但通信模式还是「一问一答」。

WebSocket的握手过程很有意思。它借用了HTTP的升级机制:

  1. 客户端发一个HTTP GET请求,带上 Upgrade: websocket
  2. 服务器返回101状态码,表示「协议切换」
  3. 之后双方直接走WebSocket帧,不再走HTTP协议

来看一个典型的握手请求:

GET /ws HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

服务器返回:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

嗯,这里要注意:Sec-WebSocket-Key 不是用来鉴权的,它只是一个随机数,用来证明服务器确实支持WebSocket。我见过有人在这上面加业务逻辑,结果把自己坑了。

核心要点:WebSocket握手只有一次HTTP请求,之后的数据传输不再走HTTP。这意味着你省去了每次请求的头部开销——对于高频行情来说,这能省下30%-50%的带宽。

2.2 与HTTP的区别

说白了,HTTP是「你问我答」模式,WebSocket是「随时开聊」模式。咱们用表格对比一下:

对比维度 HTTP WebSocket
通信模式 半双工(请求-响应) 全双工(双向实时)
连接开销 每次请求都有头部(几百字节) 握手后头部仅2-14字节
实时性 依赖轮询或长轮询 服务器主动推送
协议开销 高(Cookie、认证等) 低(二进制帧)
适用场景 REST API、网页加载 行情推送、实时聊天

你想想看,如果用HTTP轮询行情,每秒请求一次,光头部开销就够你受的。我在做币安行情聚合时测试过,HTTP轮询的延迟在200-500ms,而WebSocket推送的延迟只有10-50ms。差了整整一个数量级。

避坑指南:我曾经在项目中用HTTP长轮询模拟实时推送,结果服务器连接数暴涨,最后把Nginx搞崩了。后来换成WebSocket,连接数从10万降到1万(因为复用),CPU占用率直接腰斩。

2.3 心跳机制

这是WebSocket里最容易出问题的地方。很多同学以为连接建立后就万事大吉了,结果半夜行情断了都不知道。

心跳机制说白了就是「定期确认对方还活着」。WebSocket协议本身提供了 PingPong 帧:

  • 客户端发 Ping
  • 服务器必须回复 Pong
  • 如果超时没收到,就认为连接断了

但实际项目中,很多交易所并不按规范来。比如币安的WebSocket,它要求客户端每3分钟发一次Ping,否则服务器会主动断开。而OKX则是服务器每15秒发一次Ping,客户端只需要回复Pong。

来看一个通用的心跳实现(Python伪代码):

import asyncio
import websockets

async def heartbeat(ws, interval=30):
    """每30秒发送一次Ping"""
    while True:
        try:
            await ws.ping()
            print(f"[{time.time()}] 发送Ping")
            await asyncio.sleep(interval)
        except websockets.ConnectionClosed:
            print("连接已断开,停止心跳")
            break

async def main():
    async with websockets.connect("wss://stream.binance.com/ws") as ws:
        # 启动心跳任务
        asyncio.create_task(heartbeat(ws))
        # 处理行情数据
        async for msg in ws:
            process_trade(msg)

重要警告:不要依赖WebSocket协议自带的Ping/Pong!很多代理服务器(比如Nginx、HAProxy)会吃掉这些帧。我建议在应用层自己实现心跳——也就是在业务消息里夹带一个「心跳消息」类型。这样即使协议层被代理干扰,你的应用层还能感知到连接状态。

我曾经踩过一个坑:用Kubernetes部署行情服务,WebSocket连接经常莫名其妙断开。查了两天才发现,是K8s的负载均衡器把空闲连接给回收了。解决方案就是在应用层加心跳,让连接一直处于「活跃」状态。

2.4 知识体系总览

下面这张图总结了WebSocket的核心知识结构,我建议你保存下来:

WebSocket核心知识体系 协议原理 • 全双工通信 • HTTP升级握手 • 101状态码 • 帧结构(2-14字节) • 掩码机制 • 数据分片 与HTTP对比 • 半双工 vs 全双工 • 头部开销大 vs 小 • 轮询 vs 推送 • 无状态 vs 有状态 • 延迟高 vs 低 • 连接复用差 vs 好 心跳机制 • Ping/Pong帧 • 应用层心跳 • 超时重连 • 间隔策略 • 代理穿透 • 连接保活 三者共同构成WebSocket的完整知识体系,缺一不可

这张图里三个模块是环环相扣的。协议原理是基础,与HTTP的对比帮你理解「为什么选WebSocket」,心跳机制则是实战中的保命技能。我建议你按这个顺序学习,别跳着看。

个人经验:我在做多交易所行情聚合时,发现不同交易所的心跳策略完全不同。有的30秒,有的3分钟,有的甚至不主动发Ping。我的做法是写一个统一的心跳适配层,针对每个交易所配置不同的心跳参数。这样即使交易所改了策略,我只需要改配置文件,不用动代码。

好了,WebSocket的基础就讲到这里。记住一句话:协议原理决定上限,心跳机制决定下限。原理不懂,你写不出高性能代码;心跳不做,你的系统随时可能掉线。


公众号:蓝海资料掘金营,微信deep3321