一. 前言
mediasoup 支持多种类型的 Transport,有 WebRtcTransport,PlainTransport 以及 PipeTransport,对于 WebRtcTransport 目前 mediasoup 最新版本已经支持多个 WebRtcTransport 共用单个端口的模式了,而在此之前每个 WebRtcTransport 都需要使用一个端口,对于 PlainTransport 和 PipeTransport 现在还是每个通道需要对应一个端口。
二. WebRtcTransport单端口设计
mediasoup-demo-server 的启动配置文件中有 webRtcServerOptions 的配置选项,每个 worker 创建后 nodejs 层会通过 worker.createWebRtcServer 创建一个 WebRtcServer 的实例对象,WebRtcServer 具有 WebRtcTransport 的能力,如下所示。
对于上述配置,如果启动了多个 mediasoup-worker,不同 worker 监听的是不同的端口,第一个 worker 监听 44444,第二个 worker 监听 44445,以此类推。
当 mediasoup-client 调用 createWebRtcTransport 信令请求创建一个 WebRtc 通道时,nodejs 会通过 router.createWebRtcTransportWithServer 方法请求 mediasoup-worker,mediasoup-worker 创建 WebRtcTransport 是先通过 WebRtcServer 进行处理的,candidate 中填充的 port 信息都是 WebRtcServer 监听的 port,所以同一个 mediasoup-worker 上的所有 WebRtcTransport 使用的端口都是相同的。
返回 WebRtcTransport 的 ice candidate 信息后,SDK 会发送 STUN / RTP / RTCP 等不同类型的报文,那么 mediasoup 从固定端口收到报文之后是怎么分发给到具体的 Transport 进行处理呢?
答案是通过四元组信息,即便 server 监听的是固定端口,但是对端的 (ip, port) 是不相同的,当 mediasoup server 收到报文之后查找是哪个对端 (ip, port) 发送过来的包,再找到对应的 transport,将数据塞给对应的 transport 处理,涉及的调用流程如下。
WebRtcServer 启动监听时会创建 UdpSocket,此时 UdpSocket 的 listener 指向 WebRtcServer 对象,当有数据包到达时会回调 UdpSocket::UserOnUdpDatagramReceived,再继续回调到 WebRtcServer::OnUdpSocketPacketReceived。
对于 OnStunDataReceived 和 OnNonStunDataReceived,它们内部都是先首先根据对端地址查找到对应的 WebRtcTransport,然后交给对应的 WebRtcTransport 进行处理,例如 STUN 类型的报文就调用 webRtcTransport->ProcessStunPacketFromWebRtcServer(tuple, packet) 处理,非 STUN 类型的报文就调用 webRtcTransport->ProcessNonStunPacketFromWebRtcServer(tuple, data, len) 处理。
三. PlainTransport/PipeTransport多端口设计
PlainTransport 和 PipeTransport 一个通道使用一个端口,新建 Transport 时会创建 UdpSocket 对象,UdpSocket 需要关联到 (ip, port),对于端口的选取是通过 PortManager::BindUdp 函数实现的,如下。
端口选取的逻辑如下,它从配置的 [rtcMinPort, rtcMaxPort] 中随机选择,如果端口已经被标记使用则更换下一个端口,如果未被标记使用则通过 uv_udp_bind 关联此端口后,如果成功再标记端口被使用,如果失败则更换下一个端口(失败是有可能已经被其他 worker 占用了,该端口有已打开的句柄),之后在 UdpSocket 的析构函数会执行 PortManager::UnbindUdp 函数重新解除使用标记。