WebSocket 实时通信:从轮询到长连接 | 一方天地
为什么需要 WebSocket
Web 最初是为"请求-响应"设计的:浏览器发一个请求,服务器回一个响应,然后连接就结束了。这在浏览网页、提交表单这类场景下毫无问题,但遇到"服务器要主动推送消息"的场景就尴尬了——服务器没法主动开口,只能等浏览器来问。
早期大家用 HTTP 轮询 解决:浏览器每隔几秒发一次请求,问"有没有新消息?"。这是最朴素的方案,代价也很明显:
- 浪费带宽:大部分轮询请求换来的都是"没有新消息"。
- 实时性差:消息产生到被发现,平均要等半个轮询周期。
- 服务器压力大:大量无效请求占着连接资源,用户一多就扛不住。
后来演化出长轮询、SSE(服务端推送,单向)等折中方案,但都有各自的局限。

WebSocket:一条真正的长连接
WebSocket 解决的正是"服务器主动推送"这个问题。它用一条持久化的全双工连接,让浏览器和服务器可以随时互发消息,不再是一问一答。
关键特点:
- 握手即升级:客户端先发一个普通 HTTP 请求,带
Upgrade: websocket头,服务器同意后,连接就从 HTTP"升级"成 WebSocket 长连接——之后双方直接在这条连接上收发二进制帧,不再走 HTTP 报文。 - 全双工:连接建立后,两端都能主动发消息,服务器可以"先开口"。
- 长连接:连接会一直保持,直到某一端主动关闭或网络异常,中间靠心跳维持。
- 低开销:没有 HTTP 头和请求/响应配对,消息体就是业务数据本身。

WebSocket 的典型应用场景
凡是"实时性 + 服务器主动推送"的需求,几乎都能看到 WebSocket 的身影:
- 股票行情:价格实时跳动,服务器一有变动就推给所有盯盘的客户端。
- 实时聊天 / 即时通讯:消息要毫秒级送达,双向收发。
- 协同编辑:多人同时编辑文档,光标、内容变更实时同步。
- 在线游戏:玩家操作、状态同步需要低延迟双向通信。
- 通知推送:新的待办、新的评论、系统告警,主动推到前端。
- 物联网监控:设备状态实时上报、远程控制下发。
我们系统的接入过程
起点:一段段 HTTP 轮询
我们的后台有两处典型的"等待服务器状态"场景:
- 2FA 手机确认登录:用户在浏览器登录,密码验证通过后,需要等手机 App 确认。浏览器这边怎么做?轮询——每 3 秒问一次"手机确认了吗"。
- 手机扫码上传:PC 端弹出一个二维码,等手机扫码上传图片。同样轮询,每 2 秒问一次状态。
功能能用,但总觉得别扭:绝大多数轮询都是空转,服务器白忙一场,用户那边还有"确认了但界面没跟上"的延迟。
踩坑:CDN 不支持 WebSocket
我们决定把这两处改成 WebSocket 长连接。前端技术选型时也调研过:STOMP 协议、SockJS 回退、断线重连……方案都想好了,结果第一个拦路虎是部署层的 CDN。
我们的静态资源和 API 都挂在 CDN 后面,CDN 只认 HTTP 转发。WebSocket 的握手升级请求到了 CDN 这一层就被拦下了——CDN 不理解 Upgrade 头,不会把这条连接转给源站,连接直接失败。
排查了半天,确认是 CDN 的锅,不是代码问题。
打通:一条独立的通道
CDN 不支持,就绕开它。我们的做法是给 WebSocket 单独开一条不经过 CDN 的独立通道:
- 用一个独立的子域,DNS 直接指向源站服务器,绕开 CDN。
- 在 Nginx 上为这个子域配置 WebSocket 代理,关键是两个升级头:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
- 配好超时(空闲超时不能太短,否则会掐断长连接,配合心跳保活)和升级相关的转发头。
WebSocket 长连接本身和 HTTP 是不同的协议,但入口还是 HTTP 握手——Nginx 正是抓住这一点,把带 Upgrade 头的握手请求转给后端,握手成功后这条连接就归 WebSocket 管了。
落地:两处长连接
通道打通后,我们把那两处轮询都改成了 WebSocket:
- 2FA 登录确认:浏览器发起确认后,订阅一个"等待确认"主题;手机 App 确认/拒绝的瞬间,服务器推消息给浏览器,界面立即响应——不再有 3 秒轮询的空转。
- 手机扫码上传:PC 端订阅"上传状态"主题,手机扫码上传完成,服务器立刻推给 PC 端,秒级反馈。
两处都保留了降级策略:WebSocket 连不上时自动回退到原来的 HTTP 轮询,保证功能不因连接失败而中断。
过程中的一些体会
- 断线重连:长连接总会断(网络切换、空闲超时、服务器重启),客户端必须做重连,且重连后要恢复订阅——否则连接还在,但消息收不到了。这是最容易踩的坑。
- 心跳保活:靠心跳探测连接是否还活着,及时清理"僵尸连接",也给代理层一个判断依据。
- 协议选型:我们选了 STOMP over WebSocket,利用它的发布/订阅模型,订阅主题、推送消息都很顺手,比裸 WebSocket 自己管理订阅简单得多。
- 降级兜底:WebSocket 不是银弹,CDN、代理、弱网都可能让连接失败,保留 HTTP 轮询作为降级路径是稳妥的做法。
结语
从一段段 HTTP 轮询,到一条条真正的 WebSocket 长连接,改的不只是传输方式,更是对"实时性"的理解——服务器能主动开口了,产品上很多体验就打开了。WebSocket 不复杂,真正复杂的是长连接世界里那些需要细心经营的东西:握手、心跳、重连、订阅恢复。把这些料理好,实时体验就是水到渠成的事。