错误处理与异常恢复:连接断开处理、消息格式错误处理、房间异常清理
说实话,信令服务器这东西,平时跑得好好的没人夸你。一旦出问题,那就是连环爆炸。我见过太多项目,信令逻辑写得漂漂亮亮,结果一上线就崩——为什么?因为压根没想过「如果客户端突然断网怎么办」、「如果收到一堆乱码消息怎么办」、「如果房间里的用户全跑了,房间还挂着怎么办」。
这一章,我们就来聊聊这些「没人愿意想,但必须处理」的事。我个人习惯是:先想崩溃场景,再写业务逻辑。顺序反了,后面全是坑。
连接断开处理
WebRTC 的信令连接,说白了就是一条 WebSocket 长连接。客户端一断,服务器得立刻感知,并且做清理。你想想看,用户手机掉进马桶,网络瞬间断了,服务器这边还傻傻地以为他在房间里,给他转发消息——这不就乱套了吗?
我在项目中遇到过一种情况:客户端浏览器标签页被关了,但 WebSocket 的 close 事件没触发(某些老旧浏览器或者代理层搞的鬼)。结果呢?房间里的其他用户一直收不到「有人离开」的消息,还以为对方还在。嗯,这其实是个很隐蔽的 bug。
我的做法是:心跳机制 + 超时踢出。客户端每隔 30 秒发一个 ping,服务器回一个 pong。如果连续 3 次没收到 ping,就认为连接已死,主动清理。
代码实现大概长这样:
// 服务端心跳检测
const HEARTBEAT_TIMEOUT = 90_000; // 90秒无响应视为断开
client.on('pong', () => {
client.lastPong = Date.now();
});
// 每10秒检查一次
setInterval(() => {
const now = Date.now();
for (const [id, ws] of clients) {
if (now - ws.lastPong > HEARTBEAT_TIMEOUT) {
handleDisconnect(id, 'heartbeat timeout');
}
}
}, 10_000);
断开之后要做什么?三步走:
- 从房间成员列表移除——别让他继续占坑
- 通知房间内其他人——广播一条 peer_disconnected 消息
- 检查房间是否为空——如果空了,考虑是否要销毁房间
消息格式错误处理
客户端发过来的消息,你永远不知道是什么鬼样子。我见过有人把 JSON 写成了 Python 字典格式,也见过直接发二进制流的。信令服务器不是垃圾桶,不能什么都往里塞。
我的原则是:严格校验,温柔拒绝。也就是说,格式不对就返回错误码,但不要直接断开连接。除非恶意攻击,否则给客户端一次重试的机会。
常见的消息格式错误有这几类:
| 错误类型 | 示例 | 处理方式 |
|---|---|---|
| JSON 解析失败 | {type: "join", room: "abc"}(单引号) |
返回 {error: "invalid_json"} |
| 缺少必填字段 | {"type": "join"}(没带 room) |
返回 {error: "missing_field", field: "room"} |
| type 值不合法 | {"type": "fly"}(不存在的指令) |
返回 {error: "unknown_type"} |
| 字段类型错误 | {"type": "join", "room": 123}(room 应为字符串) |
返回 {error: "type_mismatch", field: "room"} |
我曾经遇到过最离谱的事:某个客户端 SDK 把信令消息的 type 字段拼写成了 typo,结果服务器一直返回 unknown_type,客户端也不处理错误,就这么死循环了。所以后来我加了一条规则:连续 5 次发送非法消息,直接踢掉。防呆不防傻。
校验代码示例:
function validateMessage(msg) {
if (typeof msg !== 'object' || msg === null) {
return { valid: false, error: 'not_an_object' };
}
if (!msg.type || typeof msg.type !== 'string') {
return { valid: false, error: 'missing_or_invalid_type' };
}
const allowedTypes = ['join', 'leave', 'offer', 'answer', 'ice_candidate'];
if (!allowedTypes.includes(msg.type)) {
return { valid: false, error: 'unknown_type' };
}
return { valid: true };
}
房间异常清理
房间这东西,创建起来容易,清理起来麻烦。我见过不少信令服务器,房间越积越多,内存越吃越高,最后 OOM 挂掉。为什么?因为只创建不销毁。
什么时候该销毁一个房间?我的经验是:
- 房间内所有用户都离开了——这是最直接的场景
- 房间内只剩一个人,且超过 5 分钟没有新用户加入——这个人可能只是忘了关页面
- 房间创建后 24 小时无人使用——定时清理僵尸房间
说白了,房间就是一个临时资源。用完就扔,别留着过年。
清理房间时要做的事:
- 关闭房间内所有残留的 WebSocket 连接(如果有的话)
- 从房间列表中移除该房间
- 释放关联的内存数据(比如 SDP 信息、ICE 候选等)
- 记录日志,方便排查问题
下面这张图,是我个人总结的信令服务器错误处理全流程,你可以参考一下:
最后说一句:错误处理不是锦上添花,是保命用的。你想想看,一个没有错误处理的信令服务器,就像没有安全气囊的车——平时开着没事,撞一次就完了。别偷懒,该写的 try-catch、该做的校验、该跑的定时清理,一个都不能少。