Set是管理WebSocket频道订阅关系最轻量高效的方式,因其天然去重、O(1)增删查、语义清晰;应为每个频道维护独立Set,配合Map存储,并在socket关闭时主动清理以避免内存泄漏。

用 Set 管理 WebSocket 频道的订阅关系,是最轻量、最高效的方式之一——它天然去重、支持 O(1) 查找与增删,且语义清晰,特别适合高频变动的实时场景。
为什么选 Set 而不是 Array 或 Map?
Array 容易重复添加、取消时需遍历查找(O(n)),不适合高并发订阅;Map 适合带元数据(如用户身份、权限)的场景,但纯“频道-客户端”映射若无需额外字段,就显得冗余。Set 刚好平衡了简洁性与性能:
- 自动去重:同一客户端反复订阅同一频道不会重复存储
- 增删查都是常数时间:add()、delete()、has() 均为 O(1)
- 内存占用低:只存唯一 channel ID(字符串)或 client ID(如 socket.id)
- 语义直观:“这个频道当前有谁在听?” → Set 就是听众集合
典型结构:按频道维护独立的 Set
不要把所有订阅塞进一个大 Set,而是为每个频道(如 "chat:room-123")维护一个专属 Set。服务端可使用 Map
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
const channelSubscribers = new Map<string, Set<string>>();
// 订阅
function subscribe(channel: string, clientId: string) {
if (!channelSubscribers.has(channel)) {
channelSubscribers.set(channel, new Set());
}
channelSubscribers.get(channel)!.add(clientId);
}
// 取消订阅
function unsubscribe(channel: string, clientId: string) {
const set = channelSubscribers.get(channel);
if (set) set.delete(clientId);
// 可选:空时清理,减少内存占用
if (set && set.size === 0) channelSubscribers.delete(channel);
}
// 广播给该频道所有人
function broadcast(channel: string, message: any) {
const clients = channelSubscribers.get(channel);
if (!clients || clients.size === 0) return;
for (const clientId of clients) {
// 通过 clientId 找到对应 socket 并 send()
const socket = activeSockets.get(clientId);
if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify(message));
}
}
}
配合 WebSocket 生命周期做健壮清理
客户端断连(close/error)时,仅靠前端发 unsubscribe 消息不可靠。必须在服务端监听 socket 关闭事件,并主动从所有相关频道的 Set 中清除它:
- 为每个 socket 记录它当前订阅的频道列表(可用 Set 或 Array)
- socket close 时,遍历它的订阅列表,对每个 channel 调用 unsubscribe(channel, clientId)
- 避免内存泄漏:及时 delete 空 Set,定期检查异常长连接
扩展建议:加一层弱引用或 TTL(按需)
绝大多数场景下原生 Set 已足够。若需支持「断线重连后自动恢复订阅」或「临时频道超时自动退订」,可在 Set 外包一层逻辑:
- 用 Map 存 { channelId → { subscriberSet: Set, createdAt: number } },便于按时间清理
- 结合 setTimeout 或定时任务,清理超过 5 分钟无心跳的订阅者
- 不推荐直接用 WeakSet——它不能遍历,无法广播,也不支持字符串键

















