从建立连接到消息推送:WebSocket实现即时通讯功能教程与代码解析

用 WebSocket 实现即时通讯,从连接鉴权、消息帧设计到服务端推送和断线恢复,附可运行代码,并与现成 IM 软件功能对比,帮你判断自建是否划算。

从建立连接到消息推送:WebSocket实现即时通讯功能教程与代码解析

用 WebSocket 做即时通讯,demo 很容易跑通:浏览器连接服务端,双向收发消息。但上线后问题集中在三处:token 鉴权不严谨导致连接被滥用;断线后离线消息丢失;多端在线时消息顺序不一致。本文按连接建立、消息帧设计、服务端推送、断线恢复的顺序给出可运行代码,并在每个环节标注与现成 IM 软件的差距,帮你在动手前判断自建是否划算。

建立WebSocket连接:客户端与服务端的握手与鉴权

客户端代码:

const socket = new WebSocket(
  `wss://example.com/ws?token=${encodeURIComponent(token)}`
);

socket.onopen = () => console.log('connected');
socket.onerror = (e) => console.error('ws error', e);

服务端用 Node.js 的 ws 库处理升级:

const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ noServer: true });

server.on('upgrade', (req, socket, head) => {
  const url = new URL(req.url, 'http://localhost');
  const token = url.searchParams.get('token');
  if (!verifyToken(token)) {
    socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
    socket.destroy();
    return;
  }
  wss.handleUpgrade(req, socket, head, (ws) => {
    wss.emit('connection', ws, req);
  });
});

不要把 token 塞进 Sec-WebSocket-Protocol,部分代理和客户端会丢弃或截断。正式环境可在查询参数中只放一次性 ticket,服务端校验后绑定用户身份并下发短时 session id;连接建立后客户端再通过首个消息携带签名完成二次校验。现成 IM SDK 通常已封装握手、鉴权和重连,开发者不需要处理这些边界。

定义统一消息帧:上行与下行结构

所有消息使用同一 JSON 结构,seq 由客户端生成并单调递增,用于确认与排序。

{
  "type": "chat",
  "seq": 1024,
  "to": "user_42",
  "payload": { "text": "hello" }
}
{
  "type": "chat",
  "seq": 1024,
  "from": "user_7",
  "to": "user_42",
  "ts": 1710000000000,
  "payload": { "text": "hello" }
}

服务端返回 ack 时携带相同 seq;客户端在收到 ack 前保留副本用于重试。群聊把 to 换成 room_id,服务端负责展开接收人。不要依赖 WebSocket 的单连接顺序:同一用户多端在线时,必须由服务端按 seq 排序后推送,否则多端可能显示顺序不一致。

服务端推送:从单连接到房间/用户定向

const clients = new Map(); // userId -> Set<WebSocket>

function addClient(userId, ws) {
  if (!clients.has(userId)) clients.set(userId, new Set());
  clients.get(userId).add(ws);
  ws.on('close', () => {
    const set = clients.get(userId);
    if (!set) return;
    set.delete(ws);
    if (set.size === 0) clients.delete(userId);
  });
}

function sendToUser(userId, message) {
  const sockets = clients.get(userId);
  if (!sockets) return false;
  for (const ws of sockets) {
    if (ws.readyState === 1) {
      ws.send(JSON.stringify(message));
    }
  }
  return true;
}

连接注册表必须处理多端在线和异常断开,否则 Set 会无限增长。单聊同时写入发送方与接收方;群聊先查询房间成员再广播。发送时若连接非 OPEN,应把消息写入离线队列,不能直接丢弃。服务端推送延迟升高时,按网络、服务端、客户端分层的定位方法见即时通讯延迟高怎么排查?从网络、服务端到客户端的分层定位步骤。

断线重连与心跳:消息不丢的边界

  1. 客户端每 30 秒发送 {"type":"heartbeat"};服务端 35 秒内未收到任何帧,判定连接失效并关闭。
  2. 客户端 onclose 后按 1s、2s、4s、8s、封顶 30s 退避重连;重连成功后先拉取本地最后 seq 之后的消息。
  3. 服务端不为长离线用户补发消息,除非你在服务端持久化消息并维护游标;若要求离线必达,必须落盘、补拉、幂等去重。

WebSocket 只解决双向通道,不提供消息必达、离线存储或多端同步。这些能力的具体实现可参考保障消息必达的关键机制:即时通讯消息不丢失怎么实现和离线消息总丢失?即时通讯离线消息收不到的常见原因与修复。

自建WebSocket与现成IM软件功能对比评测

维度自建 WebSocket现成 IM 软件
消息可靠性需自己实现 ack、重试、幂等内置确认、重试与补发策略
离线消息需持久化并维护游标默认提供离线队列与补拉
多端同步需服务端按 seq 排序推送已处理多端游标与已读状态
后台管理需另建控制台提供成员、群组、审计等后台
运维成本长期监控、扩容、修复供应商维护,按订阅付费

如果你的团队只有前端,或需要在两周内上线多端同步与离线消息,自建通常不划算;可先按预算有限的中小企业即时通讯选型,重点核对这5个维度验证需求。已经决定自建时,先算清长期成本,参考成本拆解:自建即时通讯需要多少钱,包含哪些隐性支出。

常见问题

WebSocket 能保证消息必达吗?

不能。WebSocket 只提供全双工通道,断线、服务端故障、客户端未 ack 都可能丢消息。必达需要持久化、seq 补拉、ack 重试和幂等去重。

心跳间隔多少合适?

桌面端通常 30 秒,服务端 35 秒无任何帧判定断线;移动端可放宽到 60 秒,但必须监听网络切换并主动重连。

没有后端经验能快速实现即时通讯吗?

能跑通单页面 demo,但处理离线消息、多端同步和运维需要后端与存储基础。没有后端建议直接使用现成 SDK 或商用 IM。

什么时候该停止自建,直接采购?

当需求包含离线必达、多端同步、后台管理、审计合规中的任意两项以上,或上线时间少于两周时,采购商用 IM 通常比自建更稳、更便宜。

浏览 6