做过VOIP的同学都知道,基于UDP实现RTP包收发时需要进行SDP协商或者ICE协商,通常服务器都是用一个端口池来和客户端进行RTP包的转发,而当前的网络环境下,开放端口池给运维带来了维护的风险,也给部分代理场景下带来了实现的复杂度,所以如果使用一个端口用来做媒体数据包的转发,那带来了极大的便利;
以WebRTC的服务器Janus为例,主要需要修改libnice返回的端口配置;以RtpProxy的实现为例,修改SIP协商时,始终返回固定端口给对方,注意需要关闭O_NONBLOCK属性:
1、rtpp_create_listener方法中,原来是通过在端口池中随机选择一个可用的端口,现在只需要返回固定端口就可以了:
代码语言:javascript复制#ifdef USE_SINGLE_PORT
return create_twinlistener(5600, &cta);//modify 2020-03-12
#else
return (CALL_METHOD(rpp, get_port, create_twinlistener,
&cta));
#endif
2、设置端口复用属性:
代码语言:javascript复制 //add for set reuse.
int reuse = 1;
setsockopt(pvt->fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
//add end.
3、收到第一个RTP包时,调用accept方法,在内核中生成对方IP/端口和fd句柄之间的映射关系,注意,调用accept方法后,不能再使用recvfrom 或者 sendto 方法发送数据包,替换为recv/send方法,如使用这两个接口,则目的地址只能为NULL:
代码语言:javascript复制struct rtpp_socket_priv {
struct rtpp_socket pub;
int fd;
struct sockaddr_storage raddr;
int raddr_len ;
};
调用例子,在收到第一个UDP包的时候,得到对方的ip地址和端口,然后使用connect方法连接到对方
struct rtp_packet *packet;
packet = rtp_packet_alloc();
if (packet == NULL) {
return NULL;
}
pvt = PUB2PVT(self);
packet->rlen = sizeof(packet->raddr);
packet->size = recvfrom(pvt->fd, packet->data.buf, sizeof(packet->data.buf), 0,
sstosa(&packet->raddr), &packet->rlen);
if (packet->size > 0 && 0 != check_update_source_addr(pvt, packet)) {
rtp_packet_free(packet);
return (NULL);
}
// 检查和连接函数
static int check_update_source_addr(struct rtpp_socket_priv *pvt, struct rtp_packet *packet){
if (pvt == NULL || packet == NULL){
return -1;
}
if (pvt->raddr_len == 0){
//主要逻辑,就是收到第一个UDP包的时候(判断是否有存储对方的地址,没有则是第一次接收到包),得到对方的ip地址和端口,然后使用connect方法连接到对方
char saddr[MAX_ADDR_STRLEN] = {'