我做了一个小实验:在浏览器中打开同一聊天会话的两个标签页,只在标签页 A 发送消息,完全不操作标签页 B。结果是,B 会自动出现新消息,以及后续的 AI 回复。

第一反应很自然:这是 WebSocket 吗?

简短答案是:它很可能使用了某种实时更新机制,但仅凭这个现象,不能确认具体就是 WebSocket,更不能确认页面一定使用了 BroadcastChannellocalStorage

从现象中能确定什么

两个标签页都能在不刷新的情况下看到同一会话的新增内容,至少说明了两件事:

  1. 服务端保存了会话状态。消息并不只存在于发送消息的那个页面内存中。
  2. 页面具备主动获得增量更新的能力。标签页 B 不需要重新加载页面,就能接收或拉取最新消息。

如果同一个会话还能在另一台电脑或手机 App 上同步,那么服务端的消息分发就是必需的。浏览器内的跨标签通信只能覆盖同一浏览器、同一源下的页面,无法承担跨设备同步。

它可能怎样实现

聊天产品通常由两层能力组成:服务端的会话同步,以及浏览器侧的状态协调。但具体产品采用哪些技术,需要证据确认。

服务端:把会话更新送到每个打开的页面

标签页 A 发送消息后,服务端会写入会话记录,并将新消息和模型生成的增量内容发送给订阅该会话的客户端。常见的传输方式包括:

  • WebSocket:浏览器与服务端维持一条双向长连接,适合消息发送和流式回复。
  • SSE:服务端通过单向事件流持续推送,客户端仍可用普通 HTTP 请求发送消息。
  • HTTP 流式响应:发送消息的请求本身持续返回模型生成的文本片段。
  • 轮询或长轮询:客户端定期或长期等待服务端返回新数据。实时感通常会略弱,但也能实现自动更新。

因此,看到流式输出或另一个标签页自动更新,只能说明存在“增量更新通道”,不能单独证明它是 WebSocket。

浏览器:同源标签页之间可选的本地协调

同一浏览器、同一站点的多个标签页,也可以额外使用浏览器提供的本地通信机制:

  • **BroadcastChannel**:一个标签页向同源的其他标签页发布事件,适合即时同步局部状态。
  • storage 事件:一个页面写入 localStorage 后,其他同源页面会收到变化通知。
  • SharedWorker:多个标签页共享一个 Worker,由它统一管理连接或状态。

这些机制可以减少重复请求、协调未读状态或让 UI 更快反映本地变化。不过,它们不是双标签同步的必要条件:两个标签页各自从服务端接收推送,已经足够让人感觉“实时”。

一个更可靠的流程图

下面是与实验现象一致、但不预设具体协议的最小模型:

标签页 A 发送消息
|
v
服务端写入会话并生成增量更新
|
+-------------------+
| |
v v
标签页 A 的更新通道 标签页 B 的更新通道
| |
v v
两个页面分别更新 UI

如果站点额外使用了浏览器内广播,流程可能多出一条本地路径:

标签页 A 更新本地状态 -- BroadcastChannel / storage --> 标签页 B

但这条路径应当被看作优化假设,而不是从页面现象直接得出的结论。

如何验证,而不是猜测

想确认某个具体网站的实现,可以打开浏览器开发者工具进行只读观察:

  1. Network 面板中筛选 WS,查看是否存在 WebSocket 连接及其 Frames。
  2. 查找类型为 event-stream 的请求,判断是否使用 SSE。
  3. 发送一条消息,观察是否存在长时间保持打开的 fetch/XHR 请求,以及响应是否分块返回。
  4. Sources 中搜索 BroadcastChannellocalStorageSharedWorkerWebSocket,寻找前端代码是否调用这些 API。
  5. 分别在两个标签页打开 Network,观察标签页 B 是否独立收到服务端数据。若是,它本身的服务端连接就足以解释同步现象。

调试时应避免公开会话链接、Cookie、请求头或 WebSocket 帧中的账号和会话数据。

结论

“两个相同聊天标签页实时同步”最稳妥的解释是:服务端维护会话状态,并向打开该会话的客户端提供增量更新。

WebSocket 是常见且合理的候选方案,但并非唯一答案;BroadcastChannelstorage 事件和 SharedWorker 也可能参与同浏览器标签页的状态协调,却不能仅从同步效果推断它们一定存在。要把“可能使用”写成“确定使用”,仍需要开发者工具中的网络或代码证据。