如何用 WebSocket 实现后端实时消息推送

chinaz
chinaz 正式会员超兽战士
发布于 2026-10-07 15:34 ·1 浏览 ·0 回复

结论:用 WebSocket 实现后端实时推送,核心是服务端维护「用户 ID → 连接会话」映射表,通过心跳保活长连接,消息到达时按用户或房间定向发送;Java 可选 Spring Boot 的 WebSocketHandler,Node.js 用 ws 库,Nginx 必须转发 Upgrade 和 Connection 头。轮询方案在 1000 在线用户、1 秒一次的场景下会产生 1000 QPS,WebSocket 只在握手时发一次 HTTP 请求,之后延迟可到毫秒级。

WebSocket 和 HTTP 轮询有什么区别?

结论:WebSocket 是一次 HTTP Upgrade 握手后保持的全双工 TCP 长连接,服务端可以主动推消息;HTTP 轮询是客户端每隔固定时间新建请求问服务端要数据。

WebSocket 适合 IM 聊天、系统通知、行情报价、多人协作编辑。轮询适合更新频率低于 30 秒、实现成本优先的场景。SSE(Server-Sent Events,服务端单向推送事件)基于 HTTP,浏览器会自动重连,但只能推文本,HTTP/1.1 下同域名最多 6 个连接。WebSocket 支持二进制和双向通信,代价是需要自己处理心跳、重连和连接鉴权。

后端怎么维护连接和用户映射?

结论:用 ConcurrentHashMap<userId, Set<Session>> 保存会话,连接建立时注册,关闭时移除,支持同一用户多端在线。

具体步骤:

  1. 握手阶段从 token 或 query 参数解析 userId,禁止信任前端直接传的 userId。
  2. onOpen 中执行 sessions.computeIfAbsent(userId, k -> ConcurrentHashMap.newKeySet()).add(session)。
  3. onClose 和 onError 中移除 session,Set 为空时删除 userId。
  4. 推送时遍历 Set,调用 session.getAsyncRemote().sendText(json),不要用同步 sendMessage 阻塞 IO 线程。
  5. 单机存 Session 只能覆盖单实例;多实例部署要用 Redis Pub/Sub、Kafka 或 NATS 广播消息,或者用网关粘性会话把同一用户固定到同一实例。

Spring Boot 怎么实现 WebSocket 推送?

结论:Spring Boot 用 @EnableWebSocket + WebSocketConfigurer 注册原生 WebSocket,生产环境建议用 ConcurrentWebSocketSessionDecorator 包装 session 保证线程安全。

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
  public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
    registry.addHandler(new MyHandler(), "/ws").setAllowedOrigins("*");
  }
}

Handler 中维护静态 Map,afterConnectionEstablished 注册,handleTextMessage 处理客户端消息。推送代码:

sessions.get(uid).forEach(s -> s.sendMessage(new TextMessage(json)));

注意:WebSocketSession.sendMessage 不是线程安全,多线程同时发送会抛 IllegalStateException,要用 new ConcurrentWebSocketSessionDecorator(session, 5000, 512 * 1024) 包装,参数分别表示发送超时 5 秒和缓冲区 512 KB。

Node.js 和 Nginx 怎么配置?

结论:Node.js 用 ws 库,Nginx 必须设置 proxy_http_version 1.1 并转发 Upgrade 和 Connection 头,否则握手返回 400。

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws, req) => {
  const uid = auth(req);
  ws.uid = uid;
  clients.set(uid, ws);
  ws.on('close', () => clients.delete(uid));
});

Nginx 配置:

location /ws {
  proxy_pass http://127.0.0.1:8080;
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";
  proxy_read_timeout 60s;
}

proxy_read_timeout 要大于心跳间隔,心跳 30 秒时设为 60 秒。云负载均衡也要开启 WebSocket 支持,否则连接会在 60 秒后被断开。

消息可靠性和心跳怎么做?

结论:心跳 30 秒、断线重连用指数退避、消息带 msgId 和 ACK,离线消息按 lastMsgId 补拉。

心跳:客户端每 30 秒发 ping,服务端回 pong;服务端 60 秒没收到 pong 就关闭连接。重连间隔按 1s、2s、4s、8s、16s、30s 递增,并加 0 到 1000 毫秒随机抖动,避免同时重连打垮服务端。可靠投递:每条消息生成唯一 msgId,客户端收到后回 ACK;服务端把最近消息写入 Redis List,保留 7 天,客户端重连时带 lastMsgId 拉取缺失消息。顺序性要求高的场景,同一会话内用单线程发送或给消息加 seq 序号。

什么时候不该用 WebSocket?

结论:单向低频通知用 SSE 或 APNs、FCM 等系统推送,请求-响应为主的业务继续用 HTTP。

WebSocket 适合双向高频通信,例如聊天、协同编辑、实时竞价。每天只推几条营销通知,用系统推送更省电,也不用维护长连接。服务端只需单向推、且客户端是浏览器时,SSE 的自动重连特性能省掉重连逻辑。

总结:WebSocket 推送的落地顺序是「握手鉴权 → 连接表注册 → 心跳保活 → 定向发送 → 多实例广播 → ACK 补拉」。先把 ConcurrentHashMap 连接表和 Nginx Upgrade 头配好,再补心跳和离线消息,就能支撑 1 万在线连接级别的实时推送。

版权声明:本文来自 GJ站长论坛《如何用 WebSocket 实现后端实时消息推送》
原文链接:https://www.gj0.com/thread-369.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。

全部回复 0

还没有回复,来抢沙发~