一、整体流程概览与架构分层
创建 Router 的流程涉及 Node.js 应用层 与 C++ 媒体处理层 的跨语言协同,其核心路径可抽象为以下四个层次:
- 应用服务层(Node.js / server.js):接收客户端连接请求,管理房间(Room)生命周期。
- 业务逻辑层(Node.js / Room.js) :封装房间业务逻辑,触发
Router创建指令。 - Node.js 原生模块层(Node.js / Worker.ts, Channel.ts):作为 JavaScript 与 C++ 之间的桥梁,通过进程间通信(IPC)管道转发请求与响应。
- C++ 媒体引擎层(C++ / Worker.cpp) :实际执行
Router的实例化,管理底层媒体路由与处理逻辑。
二、流程的详细步骤解析
以下表格梳理了从客户端连接到 Router 实例化的完整调用链:
| 步骤 | 执行层级 | 关键函数/模块 | 核心操作与数据流 | 目的与产出 |
|---|---|---|---|---|
| 1. 连接请求 | 应用服务层 | server.js -> runProtooWebSocketServer() |
WebSocket 服务器监听 connectionrequest 事件,从 URL 查询参数解析 roomId 和 peerId。 |
验证并接收客户端连接意图。 |
| 2. 房间获取/创建 | 应用服务层 | server.js -> getOrCreateRoom() |
检查内存中 rooms Map 是否存在对应 roomId 的房间。若不存在,则调用 Room.create()。 |
确保会话房间存在,为后续媒体操作提供上下文环境 。 |
| 3. 触发Router创建 | 业务逻辑层 | Room.js -> Room.create() |
在房间构造函数中,调用 mediasoupWorker.createRouter({ mediaCodecs }),传入配置的媒体编解码器。 |
向指定的 Worker 进程发起创建 Router 的请求 。 |
| 4. Node.js层转发 | Node.js原生模块层 | Worker.ts -> createRouter() |
1. 使用 ortc.generateRouterRtpCapabilities(mediaCodecs) 生成 RTP 能力集。 2. 生成唯一 routerId。 3. 通过 this._channel.request('worker.createRouter', internal) 向 Channel 发送 IPC 请求。 |
准备创建参数,并将请求序列化后通过 IPC 管道发送至 C++ 层。 |
| 5. IPC通信 | Node.js原生模块层 | Channel.ts -> request() |
1. 为请求生成唯一 ID 并构建 JSON 格式的请求对象。 2. 使用 netstring 格式序列化消息。 3. 通过 this._producerSocket.write(ns) 将数据写入 Unix Domain Socket 管道。 |
实现 Node.js 与 C++ Worker 进程间可靠、有序的指令传输。 |
| 6. C++层接收与处理 | C++媒体引擎层 | Worker.cpp -> OnChannelRequest() |
1. 在 switch-case 中匹配 Channel::Request::MethodId::WORKER_CREATE_ROUTER 方法。 2. 从请求内部数据中提取 routerId。 3. 实例化 RTC::Router 对象。 4. 将新 Router 存入 mapRouters 进行管理。 5. 调用 request->Accept() 确认创建成功。 |
在 C++ 侧完成 Router 核心对象的创建与资源初始化。 |
| 7. Node.js层完成创建 | Node.js原生模块层 | Worker.ts -> createRouter() |
1. 收到 C++ 层的成功响应后,实例化 JavaScript 侧的 Router 对象。 2. 将新 Router 加入 this._routers Set 进行管理。 3. 触发 'newrouter' 观察者事件。 |
完成 JavaScript 侧 Router 对象的封装,建立与 C++ 对象的映射关系,并通知上层应用。 |
| 8. 房间初始化完成 | 业务逻辑层 | Room.js -> Room.create() |
将创建好的 mediasoupRouter 对象与 AudioLevelObserver 等一并作为参数,完成 Room 实例的构造并返回。 |
房间对象准备就绪,可用于处理该房间内后续的 Peer 连接和媒体流路由。 |
三、关键数据结构与代码示例
-
createRouter请求的 IPC 消息结构在
Channel.ts中,发往 C++ 层的请求被序列化为以下 JSON 结构:javascript// Channel.ts 中构建的请求对象 const request = { id: this._nextId, // 自增的唯一请求ID method: 'worker.createRouter', // 方法名 internal: { routerId: uuidv4() }, // 内部参数,包含新生成的routerId data: { rtpCapabilities } // 数据部分,包含生成的RTP能力集 }; // 随后被 netstring.nsWrite(JSON.stringify(request)) 序列化并写入管道 -
C++ 层
Router创建的关键代码段cpp// Worker.cpp 中的处理片段 case Channel::Request::MethodId::WORKER_CREATE_ROUTER: { std::string routerId; // 从请求的internal对象中提取routerId SetNewRouterIdFromInternal(request->internal, routerId); // 实例化核心的 RTC::Router 对象 auto* router = new RTC::Router(routerId); // 存入全局Map进行管理 this->mapRouters[routerId] = router; MS_DEBUG_DEV("Router created [routerId:%s]", routerId.c_str()); // 发送成功响应回Node.js层 request->Accept(); break; }
四、流程的核心设计模式与要点
- 工厂模式与资源管理 :
Worker作为工厂,统一管理其下所有Router的生命周期。Node.js 层通过this._routersSet,C++ 层通过mapRouters进行映射管理,确保资源可追踪和释放 。 - 异步非阻塞 IPC :整个创建请求通过
Channel进行异步通信,Node.js 层在发出channel.request()后不会阻塞,等待 C++ 层处理完成后通过事件循环回调,保证了服务的高并发能力。 - 能力协商前置 :在发起创建请求前,Node.js 层已通过
ortc.generateRouterRtpCapabilities根据配置的mediaCodecs生成了rtpCapabilities。这体现了 SDP 协商中"提供/回答"模型的思想,Router在创建时即明确了其支持的媒体处理能力,为后续的Transport创建和Producer/Consumer的编解码器匹配奠定了基础。 - 唯一标识符生成 :
routerId在 Node.js 层使用uuidv4()生成,并贯穿整个 IPC 调用链,确保了跨进程、跨语言环境下对象标识的一致性,是两端对象关联的关键。
该流程清晰地展示了 mediasoup 如何通过分层架构和进程间通信,将业务逻辑与控制面(Node.js)同高性能媒体处理面(C++)解耦,是实现其高扩展性和高性能的关键设计 。