14、房间管理:房间创建与加入、用户列表管理、房间内信令交互

房间管理,说白了就是整个视频会议APP的"门禁系统"。你想想看,用户打开APP,第一件事是什么?肯定是进房间。房间怎么创建?怎么加入?进去之后怎么知道谁在?这些看似基础的功能,其实藏着不少坑。

我个人习惯把房间管理拆成三个核心模块:房间生命周期管理用户列表同步信令交互路由。这三个模块配合好了,整个会议系统才能稳定跑起来。

14.1 房间创建与加入流程

先说说房间创建。我在项目中遇到过一种情况:用户疯狂点击"创建房间"按钮,结果后端收到了N个创建请求,生成了N个房间。嗯,这显然不对。

正确的做法是:前端做防抖,后端做幂等。前端防抖防止用户误操作,后端幂等保证同一个用户在同一时间只能创建一个房间。

房间创建的核心流程是这样的:

// 房间创建请求体
{
  "roomId": "uuid-xxxx",      // 前端生成,保证唯一
  "userId": "user-123",
  "userName": "张三",
  "roomConfig": {
    "maxUsers": 8,
    "enableVideo": true,
    "enableAudio": true
  }
}

// 后端返回
{
  "code": 200,
  "data": {
    "roomId": "uuid-xxxx",
    "createdAt": 1695000000,
    "token": "room-token-xxx"  // 用于后续信令鉴权
  }
}

加入房间的逻辑稍微复杂一点。我记得有一次调试,用户加入房间后一直收不到其他人的视频流,查了半天发现是加入房间的信令没带token,服务端直接拒绝了媒体协商请求。

注意:加入房间时,必须携带有效的房间token。这个token通常在创建房间时由服务端下发,或者在加入房间时由服务端验证用户身份后生成。没有token,信令服务器会直接丢弃你的消息。

加入房间的完整流程我建议这样设计:

  1. 用户输入房间ID(或通过邀请链接进入)
  2. 前端校验房间ID格式(比如UUID格式、长度限制)
  3. 发送加入请求到后端,携带用户信息和房间ID
  4. 后端验证房间是否存在、是否已满、用户是否有权限
  5. 验证通过后,返回房间信息和token
  6. 前端拿到token,初始化WebRTC PeerConnection
  7. 连接信令服务器,开始媒体协商

14.2 用户列表管理

用户列表,说白了就是"房间里都有谁"。这个功能看起来简单,但要做到实时、准确、不丢人,其实挺考验设计的。

我见过很多方案是用轮询:每隔几秒拉一次用户列表。但这样做有两个问题:一是延迟高,二是浪费带宽。你想想看,如果房间里10个人,每个人都在轮询,那信令服务器的压力就大了。

更好的方案是基于信令通道的增量同步。什么意思呢?就是用户加入、离开、更新信息时,服务端主动推送变更事件,客户端只更新变化的部分。

事件类型 触发时机 推送内容
user-joined 用户加入房间 用户ID、用户名、媒体状态
user-left 用户离开房间 用户ID
user-updated 用户切换摄像头/麦克风 用户ID、新的媒体状态
user-muted 用户静音 用户ID、静音类型(audio/video)

我曾经踩过一个坑:用户断网重连后,用户列表全空了。原因是服务端在用户断开WebSocket时直接删除了用户记录,但客户端重连后没有重新拉取全量列表。

我的建议:客户端重连成功后,先请求一次全量用户列表做"基线同步",然后再监听增量事件。这样既保证了实时性,又避免了数据不一致。

用户列表的数据结构,我个人习惯这样设计:

// 用户对象
{
  "userId": "user-123",
  "userName": "张三",
  "avatar": "https://...",  // 头像URL
  "mediaState": {
    "audioEnabled": true,
    "videoEnabled": false,
    "screenSharing": false
  },
  "joinedAt": 1695000000,
  "isHost": true  // 是否是房主
}

// 用户列表(Map结构,key为userId)
{
  "user-123": { ... },
  "user-456": { ... },
  "user-789": { ... }
}

用Map而不是数组,好处是查找和更新都是O(1)复杂度。你想想看,如果用户列表有100人,用数组查找某个用户要遍历100次,用Map直接取就行了。

14.3 房间内信令交互

信令交互,是整个房间管理的"神经系统"。所有的房间操作、媒体协商、状态同步,都通过信令通道来传递。

我习惯把信令消息分为三类:

  • 房间信令:创建房间、加入房间、离开房间、解散房间
  • 媒体信令:offer、answer、ICE candidate、媒体状态变更
  • 数据信令:聊天消息、文件传输、白板数据等

信令消息的格式,我建议统一使用JSON,包含以下几个核心字段:

{
  "type": "signal",           // 消息类型
  "action": "offer",          // 具体动作
  "from": "user-123",         // 发送者
  "to": "user-456",           // 接收者(广播时为空)
  "roomId": "room-xxx",       // 房间ID
  "payload": { ... },         // 具体数据
  "timestamp": 1695000000,    // 时间戳
  "token": "xxx"              // 鉴权token
}
核心要点:信令消息必须包含token,服务端收到消息后先校验token,再处理消息。这样可以防止恶意用户伪造消息。

信令交互的流程,我用一张图来展示:

房间内信令交互流程 客户端A 用户: 张三 信令服务器 消息路由 + 鉴权 客户端B 用户: 李四 ① join-room ② user-joined 创建 PeerConnection 生成 offer 接收 offer 生成 answer ③ offer ④ offer ⑤ answer ⑥ answer ICE Candidate 交换 ICE Candidate 交换 ⑦ ICE Candidate (双向) ✅ P2P 连接建立

这张图展示了一个完整的信令交互流程。注意看,所有的信令都经过服务器中转,服务器负责鉴权、路由和广播。但媒体数据(音视频流)是P2P直连的,不经过服务器。

为什么会这样设计?因为信令数据量小,但要求可靠;媒体数据量大,但可以容忍少量丢包。让服务器处理信令,P2P传输媒体,各司其职。

避坑指南:我曾经遇到过一个问题:客户端A给客户端B发offer,但B还没加入房间,offer就丢了。解决方案是:信令服务器维护一个消息队列,如果目标用户不在线,先把消息缓存起来,等用户上线后再推送。当然,要设置超时时间,避免消息堆积。

14.4 房间状态管理

房间状态管理,说白了就是"房间里现在是什么情况"。我建议客户端维护一个房间状态对象,包含以下信息:

// 房间状态
{
  "roomId": "room-xxx",
  "roomName": "张三的会议室",
  "hostId": "user-123",
  "users": { ... },          // 用户列表
  "mediaState": {
    "isRecording": false,    // 是否在录制
    "isScreenSharing": false // 是否在屏幕共享
  },
  "createdAt": 1695000000,
  "maxUsers": 8
}

这个状态对象要保持与服务器同步。每次收到信令事件,都要更新本地状态。我习惯用状态管理库(比如Redux、MobX)来管理,这样UI层可以自动响应状态变化。

嗯,这里要注意:不要直接修改状态对象,要创建新的副本。否则React等框架无法检测到变化,UI不会更新。

最后说一句,房间管理是整个视频会议APP的基石。如果房间管理做不好,后面的媒体协商、音视频传输都是空中楼阁。我建议你在开发初期就把房间管理的逻辑理清楚,多写单元测试,特别是并发场景下的测试。


公众号:蓝海资料掘金营,微信deep3321