目录
- 1. 引言
- 2. DataChannelController
- 3. DataChannelTransportInterface
-
- 3.1 DataChannelTransportInterface创建时机追踪
-
- 3.1.1 PeerConnection::SetupDataChannelTransport_n
- 3.1.2 PeerConnection::CreateDataChannel
- 3.1.3 PeerConnection::UpdateDataChannel
- 3.1.4 PeerConnection::UpdateTransceiversAndDataChannels
- 3.1.5 PeerConnection::ApplyRemoteDescription
- 3.1.6 PeerConnection::PushdownTransportDescription
- 3.1.6 JsepTransportController::SetLocalDescription
- 3.1.7 JsepTransportController::ApplyDescription_n
- 3.1.8 JsepTransportController::MaybeCreateJsepTransport
- 3.1.8 JsepTransport::JsepTransport
- 3.1.9 JsepTransport::data_channel_transport
- 3.2 作图以做总结
- 4. DataChannel
- 5. 总结
1. 引言
我们在文章 WebRTC源码分析-呼叫建立过程之四(下)(创建数据通道DataChannel) 中分析了DataChannel的创建过程,但是也遗留了一些问题留待需要解答:
- DataChannelController这个对象是什么时候创建的呢?
- SCTP底层传输对象DataChannelTransportInterface到底实体类是哪个?什么时候创建的?
- DataChannelController与DataChannelTransportInterface是如何建立关联,又是在何时建立的关联?
本文将在分析DataChannel相关的几个类的基础上来一一解答上述问题。另外由于SCTP是DataChannel主流的底层传输方式,RTP类别的底层传输是未来要弃用的,因此,本文将只针对SCTP进行分析。
2. DataChannelController
DataChannelController类是WebRTC数据通道的聚合器,保存着所有数据通道的上层对象DataChannel,也保存着数据通道的底层传输——实现DataChannelTransportInterface接口的对象(RTP协议是RtpDataChannel,后续将略过不再提)。同时,它起着桥梁作用,将上层DataChannel要发送的数据,传递给底层通道DataChannelTransportInterface进行网络数据包发送,同时又将底层通道的状态以及接收到的数据回传给上层DataChannel。
PC、DataChannelController、DataChannel、DataChannelTransport的关系如上图所示。如果,在更新的代码中DataChannelController不存在了也不用惊讶,它所有的功能都迁移到PC中了。
2.1 DataChannelController继承结构

上图是DataChannelController的继承结构图,对于DataChannelController类我省略了如下几个部分的东西:
- RTP相关的成员与方法
- 成员的getter/setter方法
- 对接口DataChannelSink && DataChannelProviderInterface的方法实现
2.2 数据和通道状态上行
-
DataChannelTransportInterface——>DataChannelController: DataChannelController实现接口DataChannelSink,通过data_channel_transport_.SetDataSink方法将自已以Sink概念注入到底层的传输通道对象中,以此实现DataChannelController从data_channel_transport_获取对端传来的数据,并观察底层传输通道状态。假如data_channel_transport_的实体类是SctpDataChannelTransport,那么数据的向DataChannelController传递如下图所示:

另DataChannelController实现DataChannelSink接口时,以实现数据接收方法OnDataReceived为例:稍微做了下参数转换后,然后异步方式投递DataChannelController的信号SignalDataChannelTransportReceivedData_s。注意:OnDataReceived是提供给底层的回调,底层的收发包是在网络线程中进行的,因此,OnDataReceived也是在network线程中执行。而更上层的数据处理必须代理到信令线程执行

-
DataChannelController——>DataChannel: 在文章 WebRTC源码分析-呼叫建立过程之四(下)(创建数据通道DataChannel) 中,我们已经知道创建DataChannel时,DataChannel会关联DataChannelController的信号:由此,数据到达信号SignalDataChannelTransportReceivedData_s会触发DataChannel::OnDataReceived方法,完成数据传导到DataChannel。

-
DataChannel——>应用层: DataChannel中会保存它的观察者对象成员DataChannelObserver* observer_,当数据到达时,通过调用observer_->OnMessage()方法传导数据到应用层。

2.3 数据下行
-
应用层——>DataChannel: DataChannel向底层发送数据有两大类:一类是应用数据,具体还分为二进制数据和文本数据;一类是控制信令,用于SCTP的握手状态协商。
发送应用数据的时机 当应用层调用DataChannel::Send方法时,将向投底层投递应用数据,当底层未处于可发送状态时,数据会在DataChannel的PacketQueue queued_send_data_成员中进行排队,如下图调用QueueSendDataMessage方法。底层处于可发送状态时,将向底层发送数据,如下图调用SendDataMessage方法。

发送控制信令的时机 DataChannel保存着该层状态DataState,新创建的DataChannel处于kConnecting状态,处于该状态时,DataChannel不能发送应用数据,只能发送握手控制信令。

当底层通道准备好时(如上分析,状态会上行到DataChannel),调用DataChannel的UpdateState方法
,通过判断当时的DataChannel状态值 && 握手角色来构造合适的握手控制信令,并向底层投递,如下图调用SendControlMessage()方法

-
DataChannel——>DataChannelController: 当构造DataChannel时,我们知道DataChannelController会被存储到DataChannel中,作为provider存在。而DataChannelController通过实现DataChannelProviderInterface接口,提供了发送数据的方法SendData()。DataChannel不论是调用SendDataMessage()方法发送应用层数据也好,还是调用SendControlMessage()发送握手控制信令也好,最终都是通过调用provider->SendData()完成数据下发,从而数据传到到DataChannelController。

-
DataChannelController——>DataChannelTransportInterface: DataChannelController的SendData方法中会同步调用DataChannelTransportInterface的SendData方法,该方法是在network线程中执行,并返回结果

PS: 通过上述分析,底层传输通道工作在network线程中,上层DataChannel层工作在信令线程中,DataChannelController在数据/状态传递过程中还需要进行线程之间的转换。向上传递时,通过成员std::unique_ptr<rtc::AsyncInvoker> data_channel_transport_invoker_的AsyncInvoker方法进行转换;向下转换时,通过网络线程的Invoke方法同步调用底层通道的方法。
2.4 DataChannelController什么时候创建的?
现在,回答引言中的第一个问题:DataChannelController什么时候创建的?由于DataChannelController作为PC的成员变量,不是以指针形式存在,因此,在创建PC时,DataChannelController就已经被创建

3. DataChannelTransportInterface
DataChannelTransportInterface接口代表了数据通道的底层传输,其声明如下:
class DataChannelTransportInterface {
public:
virtual ~DataChannelTransportInterface() = default;
// Opens a data |channel_id| for sending. May return an error if the
// specified |channel_id| is unusable. Must be called before |SendData|.
virtual RTCError OpenChannel(int channel_id) = 0;
// Sends a data buffer to the remote endpoint using the given send parameters.
// |buffer| may not be larger than 256 KiB. Returns an error if the send
// fails.
virtual RTCError SendData(int channel_id,
const SendDataParams& params,
const rtc::CopyOnWriteBuffer& buffer) = 0;
// Closes |channel_id| gracefully. Returns an error if |channel_id| is not
// open. Data sent after the closing procedure begins will not be
// transmitted. The channel becomes closed after pending data is transmitted.
virtual RTCError CloseChannel(int channel_id) = 0;
// Sets a sink for data messages and channel state callbacks. Before media
// transport is destroyed, the sink must be unregistered by setting it to
// nullptr.
virtual void SetDataSink(DataChannelSink* sink) = 0;
// Returns whether this data channel transport is ready to send.
// Note: the default implementation always returns false (as it assumes no one
// has implemented the interface). This default implementation is temporary.
virtual bool IsReadyToSend() const = 0;
};
- OpenChannel && CloseChannel:由于上层的多个DataChannel是共享同一个底层传输通道的,因此,需要将上层DataChannel以其标识值sid作为channel_id注册进入底层传输通道中;
- SendData:提供了数据向网络层传送的接口;
- SetDataSink: 如前文所述,以回调的形式提供了数据/状态上报;
- IsReadyToSend:提供状态查询接口。
现在,我们回答引言中的第二个问题:SCTP底层传输对象DataChannelTransportInterface到底实体类是哪个?什么时候创建的?
3.1 DataChannelTransportInterface创建时机追踪
搜索整个WebRTC工程,可以看到有四个类实现了DataChannelTransportInterface,那么,什么时候,什么条件下创建哪个实体类?

采用倒推法:首先,DataChannelController提供了DataChannelTransportInterface的setter方法set_data_channel_transport,并且DataChannelController是PC的一个成员,因此,很可能是PC的一个方法中调用的。去PC的实现文件中查看,果然如此:就在PC的SetupDataChannelTransport_n中
3.1.1 PeerConnection::SetupDataChannelTransport_n
bool PeerConnection::SetupDataChannelTransport_n(const std::string& mid) {
// 1. 根据DataChannel的mid值获取底层的Transport
DataChannelTransportInterface* transport =
transport_controller_->GetDataChannelTransport(mid);
if (!transport) {
RTC_LOG(LS_ERROR)
<< "Data channel transport is not available for data channels, mid="
<< mid;
return false;
}
RTC_LOG(LS_INFO) << "Setting up data channel transport for mid=" << mid;
// 2. data_channel_controller_设置底层传输通道成员
// 2.1 设置DataChannelController的成员data_channel_transport_
data_channel_controller_.set_data_channel_transport(transport);
// 2.2 设置底层向上层传递数据/状态时,进行network——>signal线程转换的辅助类成员
// std::unique_ptr\<rtc::AsyncInvoker> data_channel_transport_invoker_
data_channel_controller_.SetupDataChannelTransport_n();
// 3. mid为SDP中mLine的mid值,由于sctp_mid_只是单个值,所以反映出使用SCTP
// 传输多路DataChannel时,只有一个Transport,也只对应SDP中一个mLine。
sctp_mid_ = mid;
// 4. 设置DataChannelController为底层Transport的DataSink
// Note: setting the data sink and checking initial state must be done last,
// after setting up the data channel. Setting the data sink may trigger
// callbacks to PeerConnection which require the transport to be completely
// set up (eg. OnReadyToSend()).
transport->SetDataSink(&data_channel_controller_);
return true;
}
划重点: SetupDataChannelTransport_n方法的作用是打通了底层Transport与DataChannelController之间的通路
DataChannelTransportInterface是从PC的std::unique_ptr<JsepTransportController> transport_controller_成员中根据mid获取的,想必transport_controller_中保存了所有Transport(音频、视频、数据),很可能DataChannelTransportInterface就是在transport_controller_中创建的,但暂且先放下transport_controller_的研究,先看看下面这个问题。
问题:SetupDataChannelTransport_n何时被调用?全局搜索源码,只有在PC的CreateDataChannel方法中被调用,注意PC有两个CreateDataChannel方法,此处的CreateDataChannel非上篇文章中的那个
3.1.2 PeerConnection::CreateDataChannel
bool PeerConnection::CreateDataChannel(const std::string& mid) {
switch (data_channel_type()) {
case cricket::DCT_SCTP:
case cricket::DCT_DATA_CHANNEL_TRANSPORT_SCTP:
case cricket::DCT_DATA_CHANNEL_TRANSPORT:
// 1. 在网络线程中执行PeerConnection::SetupDataChannelTransport_n方法
// 该方法使得底层Transport与DataChannelController关联上。
if (!network_thread()->Invoke<bool>(
RTC_FROM_HERE,
rtc::Bind(&PeerConnection::SetupDataChannelTransport_n, this,
mid))) {
return false;
}
// 2. 调用sctp_data_channels_中所有的DataChannel的OnTransportChannelCreated
// 方法,该方法使得DataChannel与DataChannelController关联上,同时将通道sid值
// 注册到底层Transport中。
// All non-RTP data channels must initialize |sctp_data_channels_|.
for (const auto& channel :
*data_channel_controller_.sctp_data_channels()) {
channel->OnTransportChannelCreated();
}
return true;
case cricket::DCT_RTP:
default:
// 此处省略了RTP通道处理代码
return true;
}
return false;
}
划重点: CreateDataChannel通过调用SetupDataChannelTransport_n方法,将底层Transport与DataChannelController关联上;再挨个调用DataChannel::OnTransportChannelCreated,将DataChannel与DataChannelController关联上,并将表征DataChannel的sid值注册到底层的Transport中。至此,Transport——DataChannelController——DataChannel的任督二脉被打通了。
继续跟踪CreateDataChannel的调用,发现其在两个方法中被调用,一个是PeerConnection::UpdateDataChannel;一个是PeerConnection::CreateChannels。
搜索CreateChannels方法调用,发现该方法在PeerConnection::ApplyLocalDescription && PeerConnection::ApplyRemoteDescription中被调用,但是位置都在这样一个分支中,如下图源码所示:表明CreateChannels只有在Plan B这种SDP格式中才会被调用,而该方式将会被淘汰,因此,我们就不再关注该路径了。后续只考虑UpdateDataChannel这条路径。

3.1.3 PeerConnection::UpdateDataChannel
RTCError PeerConnection::UpdateDataChannel(
cricket::ContentSource source,
const cricket::ContentInfo& content,
const cricket::ContentGroup* bundle_group) {
// 1. 如果数据通道类别为DCT_NONE,表示不允许创建DataChanel的,说明DataChannel是不存在的
// 直接返回OK即可
if (data_channel_type() == cricket::DCT_NONE) {
// If data channels are disabled, ignore this media section. CreateAnswer
// will take care of rejecting it.
return RTCError::OK();
}
// 2. 如果数据通道对应的mLine是被拒绝了,则进入销毁程序。
// Transport——DataChannelController——DataChannel关联线路要断开连接,
// 并销毁已创建的底层数据通道以及DataChannel
if (content.rejected) {
RTC_LOG(LS_INFO) << "Rejected data channel, mid=" << content.mid();
DestroyDataChannelTransport();
// 3. content内容是被接受的,我们更新下数据通道相关的内容
} else {
// 3.1 SCTP的数据通道处理,调用CreateDataChannel来实现数据通道上下几个层次
// 的对象的关联。如果底层Transport已经设置过了,那么不重复调用
if (!data_channel_controller_.rtp_data_channel() &&
!data_channel_controller_.data_channel_transport()) {
RTC_LOG(LS_INFO) << "Creating data channel, mid=" << content.mid();
if (!CreateDataChannel(content.name

本文深入探讨WebRTC中的DataChannel机制,解析DataChannelController与DataChannelTransport的创建时机、关联方式及工作原理,揭示数据传输的底层细节。
最低0.47元/天 解锁文章
5422





