第 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 延伸阅读
- xrt 参考 · websocket — close 状态码与帧层缓冲语义
- xrt 参考 · sync — 注册表用锁的选型