做网站湖州,中国制造网外贸平台,网站开发深圳,淄博学校网站建设公司mediasoup 提供了多种 transport#xff0c;包括 WebRtcTransport、PipeTransport、DirectTransport、PlainTransport 等#xff0c;用来实现不同目的和场景的媒体通信。WebRtcTransport 是 mediasoup 实现与 WebRTC 客户端进行媒体通信的对象#xff0c;是 mediasoup 最重要…mediasoup 提供了多种 transport包括 WebRtcTransport、PipeTransport、DirectTransport、PlainTransport 等用来实现不同目的和场景的媒体通信。WebRtcTransport 是 mediasoup 实现与 WebRTC 客户端进行媒体通信的对象是 mediasoup 最重要也是最复杂的 transport理解了 WebRtcTransport 的设计与实现再去分析其他类型 transport 会简单很多。
1. 静态结构
WebRtcTransport 可以创建独立通信端口也可以共享 WebRtcServer 上的通信端口从安全和运维复杂度来讲大部分场景都应该选择第二种方式本文主要分析第二种实现方式。
1.1. WebRtcTransport 1.1.1. 接口继承
1Transport
这是所有 transport 的基类代表与具体协议无关的通信端口它封装了通信端口参与上层数据交换的公共能力比如如何与 router、producer、consumer 的关联与交互等。 2UdpSocket::Listener
WebRtcTransport 建立独立通信端口时接收 UDP 数据。
class Listener
{
public:virtual void OnUdpSocketPacketReceived(RTC::UdpSocket* socket, const uint8_t* data, size_t len, const struct sockaddr* remoteAddr) 0;
};
3TcpServer::Listener
WebRtcTransport 建立独立通信端口时处理 TCP 连接关闭。
class Listener
{
public:virtual void OnRtcTcpConnectionClosed(RTC::TcpServer* tcpServer, RTC::TcpConnection* connection) 0;
};
4TcpConnection::Listener
WebRtcTransport 建立独立通信端口时接收 TCP 数据。
class Listener
{
public:virtual void OnTcpConnectionPacketReceived(RTC::TcpConnection* connection, const uint8_t* data, size_t len) 0;
};
5IceServer::Listener
ICE 交互相关回调具体见注释。
class Listener
{
public:// 通知发送 stun 报文virtual void OnIceServerSendStunPacket(const RTC::IceServer* iceServer, const RTC::StunPacket* packet, RTC::TransportTuple* tuple) 0;// 通知添加/删除 ufrag主要用于 WebRtcServer stun 报文路由virtual void OnIceServerLocalUsernameFragmentAdded(const RTC::IceServer* iceServer, const std::string usernameFragment) 0;virtual void OnIceServerLocalUsernameFragmentRemoved(const RTC::IceServer* iceServer, const std::string usernameFragment) 0;// 通知添加/删除 tuple用于 WebRtcServer 非 stun 报文路由virtual void OnIceServerTupleAdded(const RTC::IceServer* iceServer, RTC::TransportTuple* tuple) 0;virtual void OnIceServerTupleRemoved(const RTC::IceServer* iceServer, RTC::TransportTuple* tuple) 0;virtual void OnIceServerSelectedTuple(const RTC::IceServer* iceServer, RTC::TransportTuple* tuple) 0;// WebRTC 客户端与服务器通信端口连接状态变化通知virtual void OnIceServerConnected(const RTC::IceServer* iceServer) 0;virtual void OnIceServerCompleted(const RTC::IceServer* iceServer) 0;virtual void OnIceServerDisconnected(const RTC::IceServer* iceServer) 0;
};
6DtlsTransport::Listener
DTLS 相关回调详见注释。
class Listener
{
public:// 接收方向协商成功virtual void OnDtlsTransportConnecting(const RTC::DtlsTransport* dtlsTransport) 0;// 双向协商成功virtual void OnDtlsTransportConnected(const RTC::DtlsTransport* dtlsTransport,RTC::SrtpSession::CryptoSuite srtpCryptoSuite,uint8_t* srtpLocalKey,size_t srtpLocalKeyLen,uint8_t* srtpRemoteKey,size_t srtpRemoteKeyLen,std::string remoteCert) 0;// 连接失败或异常中断virtual void OnDtlsTransportFailed(const RTC::DtlsTransport* dtlsTransport) 0;// 对方关闭 DTLS 连接close_notify alertvirtual void OnDtlsTransportClosed(const RTC::DtlsTransport* dtlsTransport) 0;// 向对端发送 DTLS 数据DTLS协议交互virtual void OnDtlsTransportSendData(const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) 0;// 收到 DTLS 应用层数据virtual void OnDtlsTransportApplicationDataReceived(const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) 0;
};
1.1.2. 重要属性
1webRtcTransportListener
主要用于 WebRtcTransport 通知 WebRtcServer 相关信息用来建立数据路由。
class WebRtcTransportListener
{
public:virtual void OnWebRtcTransportCreated(RTC::WebRtcTransport* webRtcTransport) 0;virtual void OnWebRtcTransportClosed(RTC::WebRtcTransport* webRtcTransport) 0;// ICE 协议交互阶段需要使用 ufrag 来建立路由virtual void OnWebRtcTransportLocalIceUsernameFragmentAdded(RTC::WebRtcTransport* webRtcTransport, const std::string usernameFragment) 0;virtual void OnWebRtcTransportLocalIceUsernameFragmentRemoved(RTC::WebRtcTransport* webRtcTransport, const std::string usernameFragment) 0;// ICE 协议交互完成后需要使用 TransportTuple 来建立路由virtual void OnWebRtcTransportTransportTupleAdded(RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple) 0;virtual void OnWebRtcTransportTransportTupleRemoved(RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple) 0;
};
2iceServer
处理 ICE 协议交互确定用于通信的 TransportTuple。
3udpSockets
WebRtcTransport 建立独立通信端口的 UDP socket。
4tcpServers
WebRtcTransport 建立独立通信端口的 TCP 监听器。
5dtlsTransport
处理 DTLS 协商协商得到 SRTP 所需的加解密参数。
6srtpRecvSession
用于接收方向的 SRTP 解密。
7srtpSendSession
用于发送方向的 SRTP 加密。
1.2. IceServer 1.2.1. 重要属性
1usernameFragment 和 password
本端生成的随机值对应 SDP 中的 ice-ufrag 和 ice-pwd
2oldUsernameFragment 和 oldPassword
用来协助处理 ICE 重协商。
2consentTimeoutMs
客户端需要定时发送 Bind 请求进行保活这是相关超时设置。
4state
ICE 协商状态具体见下面的状态机分析。
enum class IceState
{NEW 1,CONNECTED,COMPLETED,DISCONNECTED,
};
5remoteNomination
正常情况是使用 USE-CANDIDATE 属性来选择用来通信的 candidate当没有 USE-CANDIDATE 属性时但携带了 NOMINATION 属性则可以使用 nomination 来选择通信 candidate值越大地址优先级越高。
6tuples
保存接收到客户端所有 BIND 请求的地址。
7selectedTuple
最终选择使用的通信地址对。
1.2.2. 重要方法
1ProcessStunPacket
WebRtcTransport 调用用来处理 BIND 请求。
2RestartIce
当客户端发起 ICE 重协商时调用此接口此时会重新生成 ufrag 和 pwd。
1.3. DtlsTransport
DtlsTransport 用来处理 DTLS 协议交互完成身份验证和密钥协商。一旦 DTLS 完成握手并协商好密钥后续RTP报文就不再通过 DTLS 处理而是通过 SRTP 进行加密和解密。 1.3.1. 重要属性
1listener
用来通知 DTLS 连接状态DTLS 协议报文需要经过 WebRtcTransport 发送另外 SCTP报文会被封装在DTLS报文中进行传输通过 OnDtlsTransportApplicationDataReceived 回调收到的 SCTP 数据。
2ssl
处于 SSL 协议交互的 OpenSSL 对象。
3state
DTLS 连接状态具体见 DTLS 状态机分析。
enum class DtlsState
{NEW 1,CONNECTING,CONNECTED,FAILED,CLOSED
};
4localRole
DTLS 角色主要用于确定DTLS握手过程中的通信方向和权限通常是 CLIENT 发起握手。
enum class Role
{AUTO 1,CLIENT,SERVER
};
mediasoup 根据客户端 DTLS 角色来设置本端 DTLS 角色调用 connectWebRtcTransport 传递并返回协商结果给客户端。
// Set local DTLS role.
switch (dtlsRemoteRole)
{case RTC::DtlsTransport::Role::CLIENT:{this-dtlsRole RTC::DtlsTransport::Role::SERVER;break;}// If the peer has role auto we become client since we are ICE controlled.case RTC::DtlsTransport::Role::SERVER:case RTC::DtlsTransport::Role::AUTO:{this-dtlsRole RTC::DtlsTransport::Role::CLIENT;break;}
}
5remoteFingerprint
保存客户端的证书指纹用来验证 DTLS 协商时对端证书的合法性。
6remoteCert
保存客户端的证书。
1.3.2. 重要方法
1ProcessDtlsData
WebRtcTransport 调用此接口传入 DTLS 报文。
2SetRemoteFingerprint
在建立 DTLS 连接之前服务器需要调用此接口设置客户端的证书指纹否则无法对客户端证书进行验证。
3SendDtlsData
OpenSSL 回调需要发送协商报文内部会调用回调 WebRtcTransport 进行发送。
4SendApplicationData
发送 SCTP 数据。
2. 数据流
WebRtcTransport 在进行媒体通信前要经历两个阶段第一个阶段是 ICE 协议交互用来确定进行通信的地址对第二个阶段是 DTLS 协议交互用来实现身份认证和密钥协商。这两个阶段完成后才开始进行 RTP/RTCP 传输阶段并使用 DTLS 阶段协商的密钥对淑军进行加解密。 2.1. STUN
WebRtcServer 收到 UDP 数据时会解析报文特征识别报文类型然后调用 ProcessStunPacketFromWebRtcServer 处理 STUN 报文WebRtcTransport 将 STUN 报文丢给 IceServer 处理。IceServer 处理完后如果需要发送响应的则会回调 WebRtcTransportWebRtcTransport 调用 TransportTuple 将报文发送出去。 但这里有个问题WebRtcServer 如何知道 STUN 报文属于哪个 WebRtcTransport方法是WebRtcTransport 在创建 IceServer时会将 ice-ufrag 与 WebRtcTransport 关联起来。WebRtcServer 通过解析 STUN 报文的 ufrag 字段找到所属 WebRtcTransport。 同时WebRtcTransport 还会将 ICE 交互过程中所有地址对设置到 WebRtcServer这样后续从这些地址发送过来的报文都送到关联的 WebRtcTransport 处理不用再依赖 ufrag 字段。当然非 STUN 报文也没有这个字段可用。
2.2. DTLS
DTLS 的数据流与 STUN 数据流类似只是在 WebRtcTransport 会对非 STUN 数据进行解析如果是 DTLS 协议报文会调用 DtlsTransport::ProcessDtlsData 进行处理。如果需要发送 DTLS 协议报文也是回调 WebRtcTransport 发送。 2.3. RTP
RTP 数据流相对复杂一些涉及到转发层的路由如下图所示。
1WebRtcTransport 将收到的 RTP 报文交给 Transport 处理。
2Transport 通过报文的 SSRC 找到对应的 Producer然后将报文交给 Producer 处理。 【注】在 Worker 上创建 Producer 时需要传入 RtpParameters里面包含了 SSRC、MID、RID 等信息。Worker 会将这些信息与 Producer 关联起来当收到报文时可以基于这些信息找到对应的 Producer。 3Producer 需要做拥塞控制、NACK、关键帧请求等相关处理。处理完后会回调 Transport 进行后续的报文转发。
4Transport 直接将报文转发给 Router 处理。 【注】Transport 是在 Router 上创建的Transport 只能属于某个 Router。 5Router 将报文转发给所有连接到 Producer 的 Consumer。
6Consumer 也需要做拥塞控制NACK、关键帧请求等相关处理。处理完后会回调 Consumer 所在 Transport将报文发送出去。 3. 补充分析
3.1. 证书指纹
证书指纹fingerprint用来验证对等端的合法性有效防止中间人攻击。证书中不携带证书指纹需要通过其他方式来传递和交换。WebRTC 在 SDP 中携带证书指纹如下所示。fingergpinrt 分为两段第一段为摘要算法类型第二段为使用此摘要算法计算的证书摘要值。
afingerprint:sha-512 28:8D:69:62:88:27:68:0B:41:FB:BE:28:DE:63:F0:2D:7C:AA:38:72:57:58:37:D4:BD:B9:BE:01:9D:A1:AF:86:1D:BB:9F:36:76:04:A8:0D:24:80:5C:08:D7:70:0D:BA:54:06:CC:48:27:52:DE:00:CD:72:B3:1A:E6:15:F1:7D
mediasoup 的证书指纹通过 connectWebRtcTransport 流程传递到 Worker如下图所示 证书指纹的计算方式可以简单的描述为基于 X509 规范对证书内容使用指定的哈希算法计算摘要。
ret X509_digest(certificate, hashFunction, binaryFingerprint, size);
在 DTLS 握手过程中Worker 会验证对等端的证书与设置的指纹是否匹配 如果匹配则验证通过握手继续进行如果不匹配则握手失败连接终止。
3.2. 密钥协商
mediasoup 预置支持的加密套件如下所示
std::vectorDtlsTransport::SrtpCryptoSuiteMapEntry DtlsTransport::srtpCryptoSuites
{{ RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM, SRTP_AEAD_AES_256_GCM },{ RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM, SRTP_AEAD_AES_128_GCM },{ RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80, SRTP_AES128_CM_SHA1_80 },{ RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32, SRTP_AES128_CM_SHA1_32 }
};
通过下面代码设置将支持的 SRTP 的加密套件设置到 SSL。
// 设置SRTP加密套件
ret SSL_CTX_set_tlsext_use_srtp(DtlsTransport::sslCtx, dtlsSrtpCryptoSuites.c_str());
最终协商结果是两套密钥盐客户端和服务器使用独立的密钥进行加密。
// Create the SRTP local master key.
std::memcpy(srtpLocalMasterKey, srtpLocalKey, srtpKeyLength);
std::memcpy(srtpLocalMasterKey srtpKeyLength, srtpLocalSalt, srtpSaltLength);// Create the SRTP remote master key.
std::memcpy(srtpRemoteMasterKey, srtpRemoteKey, srtpKeyLength);
std::memcpy(srtpRemoteMasterKey srtpKeyLength, srtpRemoteSalt, srtpSaltLength);
3.3. 重启 ICE
如果网络条件发生变化例如网络断开或切换则可能需要重新启动ICE进程以便重新发现和建立有效的网络连接。如下图所示如果在 DEMO 上点击所示图标会触发 ICE 重启。 客户端先调用服务器接口获取服务器更新后的 ICE 参数使用新的 ICE 参数更新 remote SDP再进行 PeerConnection 的 SDP 重协商。
async restartIce(iceParameters: IceParameters): Promisevoid {this.assertNotClosed();// 更新 ICE 参数ufrag/pwdthis._remoteSdp!.updateIceParameters(iceParameters);if (!this._transportReady) {return;}// SDP 重协商if (this._direction send) {const offer await this._pc.createOffer({ iceRestart: true });await this._pc.setLocalDescription(offer);const answer { type: answer, sdp: this._remoteSdp!.getSdp() };await this._pc.setRemoteDescription(answer);} else {const offer { type: offer, sdp: this._remoteSdp!.getSdp() };await this._pc.setRemoteDescription(offer);const answer await this._pc.createAnswer();await this._pc.setLocalDescription(answer);}
}
3.4. ICE 状态机
USE_CANDIDATE 属性用于指示特定的候选对candidate pair被选为用于传输。mediasoup 收到 Binding 请求如果携带 USE_CANDIDATE 属性则进入 COMPLETED 状态否则进入 CONNECTED 状态。客户端要定时向服务器发送 binding 请求来保活如果超时则进入 DISCONNECTED 状态。 3.5. DTLS 状态机
DTLS 状态比较简单在 CONNECTING 状态如果证书验证失败、加密套件协商失败或者协商超时都会进入 FAILED 状态。在 CONNECTED 状态如果收到关闭请求则进入到 CLOSED 状态。DTLS 连接成功后好像没有保活处理看起来像是依赖于 ICE 的保活。 4. 总结
本文重点分析了 WebRtcTransport 的静态结构及重要数据流对于理解 mediasoup 媒体转发框架非常重要。建立 WebRtcTransport 需要经历 ICE 协商和 DTLS 协商两个阶段本文只对其中几个比较重要的逻辑进行了分析不涉及 ICE 协商和 DTLS 协商的协议细节如有需要请参考其他文档。