1、目标:
上一节分析了SRS4.0中WebRTC模块的总体架构和软件处理流程。接下来分析SRS4.0 WebRTC模块针对客户端推流连接上各种协议报文的软件处理逻辑。
2、内容:
WebRTC模块在启动过程中: 1、创建SrsUdpMuxListener监听对象,监听指定的UDP端口(默认配置8000端口)。
srs_error_t SrsRtcServer::listen_udp() { // 创建监听对象
SrsUdpMuxListener* listener = new SrsUdpMuxListener(this, ip, port);
listener->listen();
}
srs_error_t SrsUdpMuxListener::listen() { // bind监听端口,并启动监听协程
srs_udp_listen(ip, port, &lfd);
trd = new SrsSTCoroutine("udp", this, cid);
trd->start();
}
2、启动此对象内部的SrsUdpMuxListener::cycle()协程,从UDP监听端口接收数据。
srs_error_t SrsUdpMuxListener::cycle() { // 此协程从监听端口读取数据
......
SrsUdpMuxSocket skt(lfd);
while (true) {
// 以阻塞方式从监听端口读取数据,
// 在skt.recvfrom内部使用对端地址的port(16bit)+ipv4(32bit)构成一个fast_id_(64bit)
// 同理,使用对端地址的ipv4+port构成一个字符串类型的peer_id_
skt.recvfrom(SRS_UTIME_NO_TIMEOUT);
handler->on_udp_packet(&skt); // 这里实际是调用SrsRtcServer::on_udp_packet()
}
}
int SrsUdpMuxSocket::recvfrom(srs_utime_t timeout){
nread = srs_recvfrom(lfd, buf, nb_buf, (sockaddr*)&from, &fromlen, timeout);
}
根据上一节的介绍,推流客户端通过API接口完成SDP交换,再从服务器的SDP信息中,获取服务器的IP地址+端口号,并按照WebRTC协议的要求,向服务器端口依次发送各种协议报文,完成客户端与服务器的连接建立、安全认证和RTP报文加密传输。
所以,WebRTC客户端与服务器的连接建立过程中大概涉及四种主要的协议处理
-
客户端和服务端通过STUN协议和ICE机制建立连接
-
客户端和服务端通过DTLS协议报文完成安全认证并生成SRTP加解密所需的密钥
-
客户端和服务端之间通过SRTP算法实现RTP报文的加解密
-
客户端和服务端之间通过RTCP报文完成音视频数据的Qos处理
srs_error_t SrsRtcServer::on_udp_packet(SrsUdpMuxSocket* skt) {
// 查找udp客户端对应的SrsRtcConnection
session = (SrsRtcConnection*)_srs_rtc_manager->find_by_fast_id(fast_id);
session = (SrsRtcConnection*)_srs_rtc_manager->find_by_id(peer_id);
// STUN协议报文处理
if (srs_is_stun((uint8_t*)data, size)) {
// ping.decode()内部检查接收到的必须是合法STUN报文,否则返回错误信息
if ((err = ping.decode(data, size)) != srs_success) {
return srs_error_wrap(err, "decode stun packet failed");
}
return session->on_stun(skt, &ping);
}
// RTP协议报文处理
if (is_rtp_or_rtcp && !is_rtcp) { return session->on_rtp(data, size); }
// RTCP协议报文处理
if (is_rtp_or_rtcp && is_rtcp) { return session->on_rtcp(data, size); }
// DTLS协议报文处理
if (srs_is_dtls((uint8_t*)data, size)) { return session->on_dtls(data, size); }
}
3.1 STUN报文格式与Lite-ICE协商
由于IPv4地址不足以及网络架构的原因,一般用户的电脑或手机总是在一个局域网中,通过连接NAT网关接入公网Internet网络。
一般情况下,同一个局域网的设备之间通过私网IP地址进行通信,局域网设备通过NAT网关获取一个公网IP+端口实现与公网服务器之间的通信。
不同局域网中的设备,因为互相之间不知道对端设备的公网IP,所以一般情况下,无法直接通信。
STUN协议简单说,就是让一种私网设备获取自身公网IP地址的方法,它的运行原理很简单,如下所示:
1、处于局域网的私网设备向公网STUN服务器发送STUN Binding Request请求报文。
2、请求报文经过NAT网关时,请求报文中的源IP和源端口号被NAT网关修改为网关出口的公网IP+端口号。
3、STUN服务器接收到请求报文后,返回一个STUN Binding Response 响应报文,并将服务器所看到的设备公网IP地址+端口信息(这个地址也被称为服务器反射地址server reflex address),放到响应报文的净荷中一起返回给私网设备。
所以,如上过程所示,SRS4.0的WebRTC模块首先要实现一个简单的STUN服务,即接收客户端发送的STUN Binding Request请求报文,并返回一个STUN Binding Response 响应报文。代码如下:
1、SrsStunPacket::decode()函数用于校验客户端发送的Binding Request请求报文是否正确,并得到报文各字段信息
srs_error_t SrsStunPacket::decode(const char* buf, const int nb_buf)
{
SrsBuffer* stream = new SrsBuffer(const_cast<char*>(buf), nb_buf);
if (stream->left() < 20) { // 校验STUN报文长度一定不能少于20个字节
return srs_error_new(ERROR_RTC_STUN, "invalid stun packet, size=%d", stream->size());
}
// 按STUN报文格式依次读取各个字段,用于报文格式校验
message_type = stream->read_2bytes();
uint16_t message_len = stream->read_2bytes(); // STUN报文除去头部以后的净荷长度
string magic_cookie = stream->read_string(4);
transcation_id = stream->read_string(12);
// 如果STUN报文的净荷长度+20字节的STUN报文头不等于UDP数据包长度,则数据包不是STUN报文,直接丢弃
if (nb_buf != 20 + message_len) {
return srs_error_new(ERROR_RTC_STUN, "invalid stun packet, message_len=%d, nb_buf=%d", message_len, nb_buf);
}
// 按照TLV方式,依次解析STUN报文净荷部分的各个Attributes信息
while (stream->left() >= 4) {
uint16_t type = stream->read_2bytes();
uint16_t len = stream->read_2bytes();
...