第 22 章 / 共 51 章
卷三 · 协议开发

WebSocket 进阶:广播、限额与慢客户端

echo 只证明管道通;真实业务的问题是:怎么群发、怎么挡大消息、慢客户端会不会拖死大家、僵死连接谁来收。

本章导读

站位:在 ch21 的注册表之上长出运营级能力。广播 + 限额 + 背压 + 空闲回收四件套,是聊天室/推送服务的标准底盘。

22.1 引入

WS 服务的规模问题有个共同根源:一条连接的状态会影响所有连接——一个发大消息的客户端吃内存、一个不收数据的客户端堆积发送缓冲、一个僵死连接占着注册表。宿主提供了限额与回收的机制(ws_message_limit、idle 处理),广播策略与注册表治理是应用的责任——本章就是这两块。

22.2 广播模式

遍历注册表群发(ch21 基础设施之上)
static void BroadcastText(xstrview tMsg)
{
    xmapiter it;
    xrtMutexLock(&G_WsLock);
    /* 遍历注册表:每个连接发送一份拷贝 */
    for ( it = First(G_WsConns); !AtEnd(G_WsConns, it); Next(G_WsConns, &it) ) {
        xwsstream* pWs = KeyOf(it);
        (void)xrtWsStreamText(pWs, tMsg);  /* 拷贝版:消息缓冲可立即复用 */
    }
    xrtMutexUnlock(&G_WsLock);
}

刚才发生了什么——三个工程决策藏在里面:

  • 锁内遍历:防止遍历中连接建立/关闭改表结构(锁粒度按你的注册表操作裁剪)
  • 拷贝发送:广播后消息缓冲立即复用,不必等所有连接的异步发送完成
  • 失败容忍:单个连接发送失败不断广播——失败连接由其 Close 终态自然出表

按频道广播(聊天分组)就是把注册表换成 频道 → 连接集 的两级结构——模式完全同构。

22.3 限额与子协议

机制配置行为
消息上限ws_message_limit(server 级 Custom)累计超限 → close 1009(Message Too Big);溢出检查防绕过
子协议强制ws_protocol握手期客户端子协议不匹配 → 400;启动时校验配置合法性
OOM—服务端分配失败 → close 1011
累积器瘦身—消息累积器 >256KB 时消息结束即释放,不常驻

1009 的语义是"客户端侧应停止发大消息"——限额值 = 你的业务单消息合理上限(如聊天 64KB、行情推送 1MB),不要用默认值猜。

22.4 慢客户端与背压

  • WS 层的发送是异步缓冲:客户端收得慢,数据在发送侧排队——内存防线是消息上限与你的广播频率控制
  • 治理策略由应用定:超过 N 条未发的连接降级(跳过非关键推送)或直接关闭——注册表里记"降级位"
  • 空闲回收:idle_timeout 到点由驱动用跨线程安全的 Abort 关闭(不是 Close——空闲扫描可能发生在任意 worker,Abort 才是线程安全终态)
  • 你的注册表配合:WsClose 终态回调里出表,无论连接因何而死(客户端断、限额、空闲、你主动)都走同一条清理路径
分层记忆:帧层背压 xrt 管(缓冲与重入);策略层背压你管(谁降级、谁踢出);时间层防线 idle_timeout 管。三层各司其职,不要都堆在业务代码里。

22.5 契约

  • 广播线:锁内遍历、拷贝发送、失败容忍;清理统一走 WsClose 终态。
  • 限额线:1009/1011 由宿主发;限额值按业务设定并文档化。
  • 回收线:空闲回收用 Abort(跨线程安全);你的注册表只信任 Close 终态。

常见错误

锁外遍历注册表
症状
并发下偶发遍历崩溃或漏发/重发。
原因
WsOpen/WsClose 在别的 worker 上同时改表——遍历必须在注册表锁内完成。
在非 WS 线程调 Close 回调语义
症状
自建清理线程直接操作连接对象后崩溃。
原因
跨线程的连接操作必须走 Abort 这类线程安全路径;你的清理逻辑放在 WsClose 回调里,而不是自己摸连接。

编程练习

基础聊天室广播

实现多客户端聊天:任一客户端的消息广播给其他人(不含自己)。

验收:3 客户端并发互见;某客户端断开后广播不中断。
进阶限额三态

设 ws_message_limit=1MB,分别发 0.9MB、1MB、1.1MB 消息记录行为;再设 ws_protocol="chat.v1" 验证握手拒绝。

验收:三态行为与 22.3 表一致;子协议不匹配的客户端握手 400。
挑战慢客户端降级

构造一个不读数据的客户端,实现"连续 3 次广播失败即降级 30 秒"的策略。

验收:慢客户端降级期间收不到非关键推送;恢复读取后自动解除。

小结

要点结论
广播锁内遍历 + 拷贝发送 + 失败容忍;频道即两级注册表
限额ws_message_limit → 1009;OOM → 1011;值按业务定
子协议ws_protocol 强制,握手期 400
背压分层帧层 xrt / 策略层应用 / 时间层 idle_timeout
回收Abort 跨线程安全;清理统一在 WsClose 终态
本章涉及的 API 参考
xrt 延伸阅读