WebRTC 只管流,不管控——自研信令服务器的状态机设计

视频流是 WebRTC 的事。谁发起、谁接听、谁踢人、谁旁观——这些是信令的事。


一、问题

WebRTC 搞定了音视频传输。两个浏览器之间怎么建 PeerConnection、怎么传递 SDP、怎么走 ICE 打洞——这些都是现成的。

但一个视频帮办系统不只是"两个人能看见对方"。还需要:

  • 坐席发起通话,参保人接听
  • 坐席可以邀请第三方加入旁观
  • 坐席可以踢人
  • 多方加入时,每个人知道房间里有哪些人
  • 有人断线,其他人要知道
  • 通话结束后,知道是谁挂断的、挂了多久

这些 WebRTC 不管。得自己搭一层信令服务器。


二、自研信令协议

设计一套轻量 JSON 信令。WebSocket 传输,每条消息一个类型:

信令方向说明谁来谁收
create坐席创建房间成功服务器坐席
joined有人加入房间后,给此人发的确认(带了所有在房间的人列表)服务器加入者
called有人申请加入,通知坐席是否允许服务器坐席
allow坐席允许加入坐席服务器
deny坐席拒绝加入坐席服务器
refused服务器通知申请者被拒绝服务器申请者
otherjoin有其他人加入房间(通知给已经在房间里的人)服务器其他人
bye有人主动离开离开者服务器
leaved有人离开,服务器通知房间里其他人服务器其他人
kick坐席踢人坐席服务器
kicked被踢的人收到通知服务器被踢者

关键设计决策:所有业务操作不走 P2P,全部经过信令服务器中转。 坐席踢人不是直接发给参保人,是发给服务器 → 服务器发给被踢者 + 通知其他人。单一控制点,状态不会分叉。


三、房间状态机

一个房间的生命周期:

        ┌──────────┐
        │   idle   │  等待坐席创建
        └────┬─────┘
             │ create
             ▼
        ┌──────────┐
        │ created  │  房间已建,等参保人加入
        └────┬─────┘
             │ join
             ▼
        ┌──────────┐
        │  joined  │  参保人已加入,等坐席允许
        └────┬─────┘
             │ allow
             ▼
        ┌──────────┐
        │  called  │  通话中(可能多人)
        └────┬─────┘
             │ bye / kick / timeout
             ▼
        ┌──────────┐
        │   ended  │  所有人离开,房间销毁
        └──────────┘

每个状态允许的操作不同:

状态允许的操作
created参保人加入、坐席取消
joined坐席允许/拒绝、第三方加入
called踢人、邀请第三方、正常挂断

状态机保护了异常路径——比如坐席在 created 状态还没人加入就发踢人指令,服务器直接拒绝,不会崩溃。


四、三种角色的加入和退出逻辑

坐席

  • 建房:发 create → 服务器创建房间,分配 roomId
  • 退出:发 bye → 服务器通知所有人 leaved → 房间销毁

参保人

  • 加入:发 join(带 roomId)→ 服务器暂存 → 发 called 给坐席
  • 被允许:服务器发 joined → 参保人开始建 P2P 连接
  • 被拒绝:服务器发 refused → 参保人收到拒绝原因
  • 被踢:坐席发 kick → 服务器发 kicked 给参保人 → 通知其他人 leaved

第三方(旁观)

  • 加入:同参保人,但坐席收到 called 时能看到是"旁观"角色
  • 退出:发 bye 或坐席踢,逻辑同参保人
  • 差别:不参与音视频流,只收到其他人的存在通知

五、一次完整通话的信令时序

以坐席 → 参保人一对一通话为例:

坐席                    信令服务器                   参保人
 │                         │                          │
 │──────── create ────────▶│                          │
 │◀───── create OK ────────│                          │
 │                                                      │
 │                                 ◀────── join ───────│
 │◀───── called ───────────│                          │
 │                                                      │
 │──────── allow ──────────▶│                          │
 │◀───── allow OK ──────────│                          │
 │                          │────── otherjoin ────────▶│
 │                          │                          │
 │════════════ WebRTC P2P 音视频流 ═══════════════════│
 │                          │                          │
 │                                 ◀─────── bye ───────│
 │◀───── leaved ────────────│                          │

Notes:

  1. 坐席建房后,房间处于 created 状态,等待参保人。
  2. 参保人 join 后,状态变为 joined,坐席决定 allow 还是 deny。
  3. allow 后状态变为 called,视频流开始。
  4. 参保人主动挂断(bye),服务器通知坐席(leaved),房间结束。
  5. 如果是坐席主动 bye,服务器直接销毁房间,通知所有人 leaved。

六、异常处理:断线、超时、重复加入

断线

WebSocket 断连时,服务器检测到连接关闭,等同于该用户主动 bye。处理逻辑:

  • 走正常离开流程,通知其他人 leaved
  • 保留房间 N 分钟,等待重连
  • 超时没重连的人,从房间列表移除

超时

joined 状态等待坐席 allow 超过 N 分钟 → 服务器自动给参保人发 refused,房源回退到 created。

重复加入

同一个 roomId,同一个 userId 重复发 join → 服务器忽略,返回"已在房间中"。


七、总结

WebRTC 搞定了音视频流的传输。但它不负责:

  • 谁有权建房
  • 谁有权加入
  • 谁有权踢人
  • 旁观和发言有什么区别
  • 断线了怎么处理

这些是信令服务器的活。

这套自研协议只有 10 条左右信令,覆盖了远程帮办的全部业务场景。核心设计原则是:所有状态变更必须经过服务器——不是性能最优的方案,但状态一致性最高。政务场景不需要百万人并发,但绝对不能在通话中丢了状态。

信令层的代码不复杂。复杂的是想清楚"每个状态下谁可以做什么"。这个想清楚了,代码是自然推导出来的。

Logo

邀请您加入社区

更多推荐