概述
上节说过WebRtcTransport实例创建成功后,客户端通过服务端的nodejs主进程发送"transport.connect" 指令到c++子进程,进行建立连接的。连接的建立过程主要是完全ICE,DTLS协议等规定的相关内容,下面简单说明一下相关概念。
ICE是一种标准穿透协议,利用STUN和TURN服务器来帮助端点建立连接 ,客户端通过ice-ufrags, ice-passwords与服务器进行身份验证并建立连接。并互换fingerprint,用于双方证书的合法性。
DTLS用于保障传输数据的安全,是对TLS协议进行了扩展。
SRTP为通过IP网络交付音频和视频定义了标准的分组格式。SRTP本身并不对传输数据的及时性、可靠性或数据恢复提供任何保证机制,它只负责把数字化的音频采样和视频帧用一些元数据封装起来,以辅助接收方处理这些流。
SCTP是一种面向消息的可靠传输协议,与 TCP 一样,SCTP 提供可靠的、面向连接的数据传输和拥塞控制。 与 TCP 不同,SCTP 还提供消息边界保留、有序和无序消息传递、多流和多归属。 数据损坏、数据丢失和数据重复的检测是通过使用校验和和序列号来实现的。 应用选择性重传机制来纠正数据的丢失或损坏。对于WebRTC,SCTP是在一个安全的DTLS信道中运行,而这个信道又运行在UDP之上。由于WebRTC支持通过DataChannel API在端与端之间传输任意应用数据,而DataChannel就依赖于SCTP
首先在main.cpp中对相关的库进行初始化:
DepOpenSSL::ClassInit();//初始化一些加密相关的算法
DepLibSRTP::ClassInit(); //libSRTP库初始化,libSRTP提供了保护 RTP 和 RTCP 的功能
DepUsrSCTP::ClassInit();//一个用户级的sctp的初始化
DepLibWebRTC::ClassInit();//只是初始化webrtc试用特性 “WebRTC-Bwe-AlrLimitedBackoff/Enabled/” 暂不做了解
Utils::Crypto::ClassInit();//创建一个HMAC SHA1的HMAC_CTX上下文
RTC::DtlsTransport::ClassInit();//创建加密上下文,证书,私钥,证书指纹(摘要),与srtp绑定
RTC::SrtpSession::ClassInit();//监听会话事件,打印相关信息
我们重点看DtlsTransport的初始化代码
void DtlsTransport::ClassInit()
{
MS_TRACE();
// Generate a X509 certificate and private key (unless PEM files are provided).
// 先生成x509的证书和密钥
if (
Settings::configuration.dtlsCertificateFile.empty() ||
Settings::configuration.dtlsPrivateKeyFile.empty())
{
// 没有证书配置信息,就通过代码创建,流程与openssl命令创建过程一样
GenerateCertificateAndPrivateKey();
}
else
{
// 已经有证书配置信息,直接从配置文件指定的路径读取
ReadCertificateAndPrivateKeyFromFiles();
}
// Create a global SSL_CTX.
// 创建全局的ssl 上下文 主要使用的是一些openssl的api,初始化证书、私钥并绑定上下文.
// 与srtp绑定,设置采用srtp做为数据的传输协议
CreateSslCtx();
// Generate certificate fingerprints.
// 创建证书指纹,不同的算法可以产生不同的指纹
GenerateFingerprints();
}
相关内容初始化后,看一下Router在收到ROUTER_CREATE_WEBRTC_TRANSPORT时,创建 WebRtcTransport实例代码:
WebRtcTransport::WebRtcTransport(const std::string& id, RTC::Transport::Listener* listener, json& data)
: RTC::Transport::Transport(id, listener, data)
{
............省略............
try
{
uint16_t iceLocalPreferenceDecrement{ 0 };
if (enableUdp && enableTcp)
this->iceCandidates.reserve(2 * jsonListenIpsIt->size());
else
this->iceCandidates.reserve(jsonListenIpsIt->size());
for (auto& listenIp : listenIps)
{
if (enableUdp)//启动upd传输
{
uint16_t iceLocalPreference =
IceCandidateDefaultLocalPriority - iceLocalPreferenceDecrement;
if (preferUdp)
iceLocalPreference += 1000;
uint32_t icePriority = generateIceCandidatePriority(iceLocalPreference);
// This may throw.
RTC::UdpSocket* udpSocket;
if (port != 0)
udpSocket = new RTC::UdpSocket(this, listenIp.ip, port);
else
udpSocket = new RTC::UdpSocket(this, listenIp.ip);
this->udpSockets[udpSocket] = listenIp.announcedIp;
if (listenIp.announcedIp.empty())
this->iceCandidates.emplace_back(udpSocket, icePriority);
else
this->iceCandidates.emplace_back(udpSocket, icePriority, listenIp.announcedIp);
}
if (enableTcp)//启动tcp传输
{
uint16_t iceLocalPreference =
IceCandidateDefaultLocalPriority - iceLocalPreferenceDecrement;
if (preferTcp)
iceLocalPreference += 1000;
uint32_t icePriority = generateIceCandidatePriority(iceLocalPreference);
// This may throw.
RTC::TcpServer* tcpServer;
if (port != 0)
tcpServer = new RTC::TcpServer(this, this, listenIp.ip, port);
else
tcpServer = new RTC::TcpServer(this, this, listenIp.ip);
this->tcpServers[tcpServer] = listenIp.announcedIp;
if (listenIp.announcedIp.empty())
this->iceCandidates.emplace_back(tcpServer, icePriority);
else
this->iceCandidates.emplace_back(tcpServer, icePriority, listenIp.announcedIp);
}
// Decrement initial ICE local preference for next IP.
iceLocalPreferenceDecrement += 100;
}
// Create a ICE server.
this->iceServer = new RTC::IceServer(
this, Utils::Crypto::GetRandomString(16), Utils::Crypto::GetRandomString(32));
// Create a DTLS transport.
this->dtlsTransport = new RTC::DtlsTransport(this);
}
...........省略............
}
WebRtcTransport实例创建成功后,会通过WebRtcTransport::FillJson填充要返回给客户端的数据,包括iceRole(默认是controlled),用户名usernameFragment,密码password,iceLite说明不是完全的遵循ice标准,iceCandidates,dtlsParameters(包含fingerprints,role)。
void WebRtcTransport::FillJson(json& jsonObject) const
{
MS_TRACE();
// Call the parent method.
RTC::Transport::FillJson(jsonObject);
// Add iceRole (we are always "controlled").
jsonObject["iceRole"] = "controlled";
// Add iceParameters.
jsonObject["iceParameters"] = json::object();
auto jsonIceParametersIt = jsonObject.find("iceParameters");
(*jsonIceParametersIt)["usernameFragment"] = this->iceServer->GetUsernameFragment();
(*jsonIceParametersIt)["password"] = this->iceServer->GetPassword();
(*jsonIceParametersIt)["iceLite"] = true;
// Add iceCandidates.
jsonObject["iceCandidates"] = json::array();
auto jsonIceCandidatesIt = jsonObject.find("iceCandidates");
for (size_t i{ 0 }; i < this->iceCandidates.size(); ++i)
{
jsonIceCandidatesIt->emplace_back(json::value_t::object);
auto& jsonEntry = (*jsonIceCandidatesIt)[i];
auto& iceCandidate = this->iceCandidates[i];
iceCandidate.FillJson(jsonEntry);
}
// Add iceState.
switch (this->iceServer->GetState())
{
case RTC::IceServer::IceState::NEW:
jsonObject["iceState"] = "new";
break;
case RTC::IceServer::IceState::CONNECTED:
jsonObject["iceState"] = "connected";
break;
case RTC::IceServer::IceState::COMPLETED:
jsonObject["iceState"] = "completed";
break;
case RTC::IceServer::IceState::DISCONNECTED:
jsonObject["iceState"] = "disconnected";
break;
}
// Add iceSelectedTuple.
if (this->iceServer->GetSelectedTuple())
this->iceServer->GetSelectedTuple()->FillJson(jsonObject["iceSelectedTuple"]);
// Add dtlsParameters.
jsonObject["dtlsParameters"] = json::object();
auto jsonDtlsParametersIt = jsonObject.find("dtlsParameters");
// Add dtlsParameters.fingerprints.
(*jsonDtlsParametersIt)["fingerprints"] = json::array();
auto jsonDtlsParametersFingerprintsIt = jsonDtlsParametersIt->find("fingerprints");
auto& fingerprints = this->dtlsTransport->GetLocalFingerprints();
for (size_t i{ 0 }; i < fingerprints.size(); ++i)
{
jsonDtlsParametersFingerprintsIt->emplace_back(json::value_t::object);
auto& jsonEntry = (*jsonDtlsParametersFingerprintsIt)[i];
auto& fingerprint = fingerprints[i];
jsonEntry["algorithm"] =
RTC::DtlsTransport::GetFingerprintAlgorithmString(fingerprint.algorithm);
jsonEntry["value"] = fingerprint.value;
}
// Add dtlsParameters.role.
switch (this->dtlsRole)
{
case RTC::DtlsTransport::Role::NONE:
(*jsonDtlsParametersIt)["role"] = "none";
break;
case RTC::DtlsTransport::Role::AUTO:
(*jsonDtlsParametersIt)["role"] = "auto";
break;
case RTC::DtlsTransport::Role::CLIENT:
(*jsonDtlsParametersIt)["role"] = "client";
break;
case RTC::DtlsTransport::Role::SERVER:
(*jsonDtlsParametersIt)["role"] = "server";
break;
}
// Add dtlsState.
switch (this->dtlsTransport->GetState())
{
case RTC::DtlsTransport::DtlsState::NEW:
jsonObject["dtlsState"] = "new";
break;
case RTC::DtlsTransport::DtlsState::CONNECTING:
jsonObject["dtlsState"] = "connecting";
break;
case RTC::DtlsTransport::DtlsState::CONNECTED:
jsonObject["dtlsState"] = "connected";
break;
case RTC::DtlsTransport::DtlsState::FAILED:
jsonObject["dtlsState"] = "failed";
break;
case RTC::DtlsTransport::DtlsState::CLOSED:
jsonObject["dtlsState"] = "closed";
break;
}
}
客户端收到回应后,接下来就是客户端发送connect指令给服务端了,connect指令主要是包括dtlsParameters的信息(fingerprints和role)。
另外,WebRtcTransport实例的创建过程,通过generateIceCandidatePriority,会生成相应的服务端的ice candidate并传给客户端(双方进行通信需要交换candidate), 在客户端收到服务端的candidate,并将candidate加入到peerconnection连接对象中去后,底层就会发送一个stun binding request消息(包含ice-ufrags, ice-passwords),服务端收到消息后取出ice-ufrags, ice-passwords,然后进行验证是不是由之前WebRtcTransport为客户端生成的用户名和密码。如果是就返回OK,否则返回错误。如果返回的是OK,接下来就是进行DTLS握手消息处理,DTLS握手成功后,客户端与服务端就确认的加密算法key。然后dtls把拿到的key交给SRTP,再创建一个SRTP Session,后面数据传输都采用SRTP进行加密解密。
inline void WebRtcTransport::OnPacketReceived(
RTC::TransportTuple* tuple, const uint8_t* data, size_t len)
{
MS_TRACE();
// Increase receive transmission.
RTC::Transport::DataReceived(len);
// Check if it's STUN.
if (RTC::StunPacket::IsStun(data, len))
{
OnStunDataReceived(tuple, data, len);
}
// Check if it's RTCP.
else if (RTC::RTCP::Packet::IsRtcp(data, len))
{
OnRtcpDataReceived(tuple, data, len);
}
// Check if it's RTP.
else if (RTC::RtpPacket::IsRtp(data, len))
{
OnRtpDataReceived(tuple, data, len);
}
// Check if it's DTLS.
else if (RTC::DtlsTransport::IsDtls(data, len))
{
OnDtlsDataReceived(tuple, data, len);
}
else
{
MS_WARN_DEV("ignoring received packet of unknown type");
}
}
从 WebRtcTransport::OnPacketReceived中可以看出针对不同类型的消息进行了分别的处理。WebRtcTransport实例创建时,同时创建了iceServer,dtlsTransport。
stun消息就是通过iceServer进行处理的:
void IceServer::ProcessStunPacket(RTC::StunPacket* packet, RTC::TransportTuple* tuple)
{
MS_TRACE();
// Must be a Binding method.
if (packet->GetMethod() != RTC::StunPacket::Method::BINDING)
{
.............这边就是对 stun binding消息进行处理
}
}
然后dtls握手过程就是通过 dtlsTransport进行处理的:
void DtlsTransport::ProcessDtlsData(const uint8_t* data, size_t len)
{
MS_TRACE();
int written;
int read;
if (!IsRunning())
{
MS_ERROR("cannot process data while not running");
return;
}
// Write the received DTLS data into the sslBioFromNetwork.
written =
BIO_write(this->sslBioFromNetwork, static_cast<const void*>(data), static_cast<int>(len));
if (written != static_cast<int>(len))
{
MS_WARN_TAG(
dtls,
"OpenSSL BIO_write() wrote less (%zu bytes) than given data (%zu bytes)",
static_cast<size_t>(written),
len);
}
// Must call SSL_read() to process received DTLS data.
read = SSL_read(this->ssl, static_cast<void*>(DtlsTransport::sslReadBuffer), SslReadBufferSize);
// Send data if it's ready.
SendPendingOutgoingDtlsData();
// Check SSL status and return if it is bad/closed.
// 真正处理握手的地方
if (!CheckStatus(read))
return;
// Set/update the DTLS timeout.
if (!SetTimeout())
return;
// Application data received. Notify to the listener.
if (read > 0)
{
// It is allowed to receive DTLS data even before validating remote fingerprint.
if (!this->handshakeDone)
{
MS_WARN_TAG(dtls, "ignoring application data received while DTLS handshake not done");
return;
}
// Notify the listener.
this->listener->OnDtlsTransportApplicationDataReceived(
this, (uint8_t*)DtlsTransport::sslReadBuffer, static_cast<size_t>(read));
}
}
另外对于RTCP,RTP类型的数据就是实际的音视频数据。
每个类型的具体的处理细节都比较多,后续会慢慢逐个分析。
最后
以上就是隐形中心为你收集整理的三、mediasoup之WebRtcTransport 创建流程(2)的全部内容,希望文章能够帮你解决三、mediasoup之WebRtcTransport 创建流程(2)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复