WebRTC源码分析——DataChannel及其相关类

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

1. 引言

我们在文章 WebRTC源码分析-呼叫建立过程之四(下)(创建数据通道DataChannel) 中分析了DataChannel的创建过程,但是也遗留了一些问题留待需要解答:

  1. DataChannelController这个对象是什么时候创建的呢?
  2. SCTP底层传输对象DataChannelTransportInterface到底实体类是哪个?什么时候创建的?
  3. 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
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值