mediasoup udp 单端口方案设计
mediasoup udp 端口当前现状
mediasoup sfu 每创建一个webrtctransport都会分配一个udp port 来作为通信的端口,并且mediasoup 每个 room client 会创建一个recv webrtctransport 来接受其他client 的数据,创建一个send webrtctransport 来发送自身的数据,也就是一个room client 会占用mediasoup 两个UDP端口。
参会人数过多,就会造成暴露的UDP端口过多,造成运维上的困难。但如果采用UDP 单个端口来服务全部通信用户,这里就会遇到一个问题,如何区分UDP数据包是属于哪个用户的?
我参考了srs 4.0 webrtc 模块的代码,修改mediasoup sfu,设计了一个UDP单端口方案来解决这个问题,设计原则是:
- 功能上要解决UDP 数据包区分的问题
- 修改不破坏mediasoup sfu 整体的代码框架,保持代码的一致性
- 性能上不能有大的下降
mediasoup udp 单端口方案设计
UDP数据包区分原理
单个端口来区分UDP数据包,主要是基于ICE的用户名来做处理:
- 首先每个WebRtcTransport 创建的时候,都分配了 SDP 中的 ICE 用户名和密码,这样就建立了username_WebRtcTransport的映射关系。
- mediasoup room client webrtc 建联的过程,会首先发送STUN包过来,进行ICE candidate 的提名,发送的STUN 包是携带了用户名和密码,这样就知道了该WebRtcTransport的 ip:port,后续就可以通过ip:port来区分WebRtcTransport,建立了ip:port_WebRtcTransport的映射关系
不过这样的设计会导致一个问题,要支持RTP/RTCP/DTLS的地址切换的情况下,只有先发送STUN包,让服务器重新映射ip_port 和 WebRtcTransport的关系。
改造方案
主要的改造过程是对 WebRtcTransport 收UDP数据那块做处理。
WebRtcTransport 原有设计有个成员变量std::unordered_map<RTC::UdpSocket*, std::string> udpSockets,每个网卡创建一个RTC::UdpSocket,WebRtcTransport继承 RTC::UdpSocket::Listener这个类,把自己的this指针传给RTC::UdpSocket,实现OnUdpSocketPacketReceived 这个函数具体处理接收UDP 数据。
考虑到代码最小侵入,我设计了一个全局变量
std::unordered_map<std::string, RTC::SingleUdpSocket*> singleUdpSocket,每个网卡创建一个RTC::SingleUdpSocket,通过RTC::SingleUdpSocket::SetTransportByUserName这个接口,RTC::SingleUdpSocket 绑定对应的ICE username 和WebRtcTransport,RTC::SingleUdpSocket具体处理UDP数据包的区分,把UDP数据包转发给对应的WebRtcTransport。
mediasoup udp 单端口代码实现
区分UDP数据包的具体代码逻辑
void SingleUdpSocket::OnUdpSocketPacketReceived(
RTC::UdpSocket* socket, const uint8_t* data, size_t len, const struct sockaddr* remoteAddr)
{
std::string peer_id;
GetPeerId(remoteAddr, peer_id);
RTC::UdpSocket::Listener *listener = NULL;
auto peer_iter = mapTransportPeerId.find(peer_id);
if (peer_iter != mapTransportPeerId.end())
{
listener = peer_iter->second;
}
if (listener)
{
listener->OnUdpSocketPacketReceived(socket, data, len, remoteAddr);
return;
}
RTC::StunPacket* packet = RTC::StunPacket::Parse(data, len);
if (!packet)
{
MS_WARN_DEV("ignoring wrong STUN packet received");
return;
}
std::string username = packet->GetUsername();
auto size = username.find(":");
if (size != std::string::npos)
{
username = username.substr(0, size);
}
auto name_iter = mapTransportName.find(username);
if (name_iter == mapTransportName.end())
{
// Reply 400.
RTC::StunPacket* response = packet->CreateErrorResponse(400);
response->Serialize(StunBuffer);
socket->Send(response->GetData(),response->GetSize(),remoteAddr,NULL);
delete packet;
delete response;
return;
}
listener = name_iter->second;
SetTransportByPeerId(listener, peer_id);
if (listener)
{
listener->OnUdpSocketPacketReceived(socket, data, len, remoteAddr);
}
delete packet;
}
github 路径
这是我的fork mediasoup 修改后的仓库,有更好解决办法的朋友可以留言讨论一下
https://github.com/UniverseParticle/mediasoup.git