WebSocket 实时通信:从轮询到长连接 | 一方天地

2026-08-01 探索 Vue3, Nginx, Java, Flutter

为什么需要 WebSocket

Web 最初是为"请求-响应"设计的:浏览器发一个请求,服务器回一个响应,然后连接就结束了。这在浏览网页、提交表单这类场景下毫无问题,但遇到"服务器要主动推送消息"的场景就尴尬了——服务器没法主动开口,只能等浏览器来问。

早期大家用 HTTP 轮询 解决:浏览器每隔几秒发一次请求,问"有没有新消息?"。这是最朴素的方案,代价也很明显:

后来演化出长轮询、SSE(服务端推送,单向)等折中方案,但都有各自的局限。

HTTP 轮询 vs WebSocket 对比

WebSocket:一条真正的长连接

WebSocket 解决的正是"服务器主动推送"这个问题。它用一条持久化的全双工连接,让浏览器和服务器可以随时互发消息,不再是一问一答。

关键特点:

WebSocket 握手升级流程

WebSocket 的典型应用场景

凡是"实时性 + 服务器主动推送"的需求,几乎都能看到 WebSocket 的身影:

我们系统的接入过程

起点:一段段 HTTP 轮询

我们的后台有两处典型的"等待服务器状态"场景:

  1. 2FA 手机确认登录:用户在浏览器登录,密码验证通过后,需要等手机 App 确认。浏览器这边怎么做?轮询——每 3 秒问一次"手机确认了吗"。
  2. 手机扫码上传:PC 端弹出一个二维码,等手机扫码上传图片。同样轮询,每 2 秒问一次状态。

功能能用,但总觉得别扭:绝大多数轮询都是空转,服务器白忙一场,用户那边还有"确认了但界面没跟上"的延迟。

踩坑:CDN 不支持 WebSocket

我们决定把这两处改成 WebSocket 长连接。前端技术选型时也调研过:STOMP 协议、SockJS 回退、断线重连……方案都想好了,结果第一个拦路虎是部署层的 CDN

我们的静态资源和 API 都挂在 CDN 后面,CDN 只认 HTTP 转发。WebSocket 的握手升级请求到了 CDN 这一层就被拦下了——CDN 不理解 Upgrade 头,不会把这条连接转给源站,连接直接失败。

排查了半天,确认是 CDN 的锅,不是代码问题。

打通:一条独立的通道

CDN 不支持,就绕开它。我们的做法是给 WebSocket 单独开一条不经过 CDN 的独立通道

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

WebSocket 长连接本身和 HTTP 是不同的协议,但入口还是 HTTP 握手——Nginx 正是抓住这一点,把带 Upgrade 头的握手请求转给后端,握手成功后这条连接就归 WebSocket 管了。

落地:两处长连接

通道打通后,我们把那两处轮询都改成了 WebSocket:

两处都保留了降级策略:WebSocket 连不上时自动回退到原来的 HTTP 轮询,保证功能不因连接失败而中断。

过程中的一些体会

结语

从一段段 HTTP 轮询,到一条条真正的 WebSocket 长连接,改的不只是传输方式,更是对"实时性"的理解——服务器能主动开口了,产品上很多体验就打开了。WebSocket 不复杂,真正复杂的是长连接世界里那些需要细心经营的东西:握手、心跳、重连、订阅恢复。把这些料理好,实时体验就是水到渠成的事。

本文由一方天地发布 · 查看完整体验