WebRTC源码分析-呼叫建立过程之四(下)(创建数据通道DataChannel)

1. 引言

创建完PeerConnectionFactory 和 PeerConnection这两个API层的操盘对象之后,紧接着需要初始化本地的媒体,也即创建本地的音频轨、视频轨、数据通道,并将这些本地的媒体轨道添加到PeerConnection对象中。如图中红色标注所示。

本文将详细描述上述数据通道的创建细节。
在这里插入图片描述

2. 数据通道的创建

应用层通过调用PC的CreateDataChannel方法来创建DataChannel,PC有两个CreateDataChannel方法,其中一个入参是mid值,另外一个如下源码所示。

2.1 PeerConnection::CreateDataChannel方法

rtc::scoped_refptr<DataChannelInterface> PeerConnection::CreateDataChannel(
    const std::string& label,
    const DataChannelInit* config) {
  // 1. 判断是否运行于信令线程,输出日志
  RTC_DCHECK_RUN_ON(signaling_thread());
  TRACE_EVENT0("webrtc", "PeerConnection::CreateDataChannel");
  
  // 2. 是否为第一个数据通道?
  //    PC.data_channel_controller_有三个成员变量记录了DataChannel,分别是
  //    map<std::string, rtc::scoped_refptr<DataChannel>> rtp_data_channels_;
  //    vector<rtc::scoped_refptr<DataChannel>> sctp_data_channels_;
  //    vector<rtc::scoped_refptr<DataChannel>> sctp_data_channels_to_free_;
  //    第一个记录的是rtp作为datachannel底层传输的数据通道,并且记录label->DataChannel的映射
  //    第二个记录的是sctp作为datachannel底层传输的数据通道
  //    第三个记录的是已经需要进行释放的sctp作为datachannel底层传输的数据通道
  //
  //    是否是第一个,取决于rtp_data_channels_和sctp_data_channels_是否为空,为空,则是第一个;
  //    不需要判断第三个记录,因为已经是需要销毁释放的datachannel了。
  bool first_datachannel = !data_channel_controller_.HasDataChannels();

  // 3. 创建DataChannel对象
  // 3.1 创建内部使用的DataChannel初始化参数InternalDataChannelInit
  std::unique_ptr<InternalDataChannelInit> internal_config;
  if (config) {
    internal_config.reset(new InternalDataChannelInit(*config));
  }
  // 3.2 通过InternalCreateDataChannel方法来创建DataChannel
  rtc::scoped_refptr<DataChannelInterface> channel(
      data_channel_controller_.InternalCreateDataChannel(label, internal_config.get()));
  if (!channel.get()) {
    return nullptr;
  }
  // 3.3 如果创建的是RTP DataChannel或者是第一个SCTP DataChannel,需要报告给PC的观察者
  //     进行重新协商
  // Trigger the onRenegotiationNeeded event for every new RTP DataChannel, or
  // the first SCTP DataChannel.
  if (data_channel_type() == cricket::DCT_RTP || first_datachannel) {
    Observer()->OnRenegotiationNeeded();
  }
  // 3.4 记录DATA_ADDED事件到PC的成员usage_event_accumulator_
  NoteUsageEvent(UsageEvent::DATA_ADDED);
  
  // 4. 返回DataChannel的代理对象DataChannelProxy
  return DataChannelProxy::Create(signaling_thread(), channel.get());
}

分四步对CreateDataChannel()方法进行了初步分析,其中一些知识点拎出来再说明下:

  • 从判断是否已经存在DataChannel这么一个判断的方式上看,PC的data_channel_controller_有两个成员存储了DataChannel,分别是成员rtp_data_channels_和成员sctp_data_channels_,从名称上可以看出这两个成员是用来保存不同传输协议实现的DataChannel:基于sctp 和 基于rtp。
  • 创建DataChannel过程中出现了两个特别重要的参数,一个是应用层传入的DataChannelInit,一个是PC的成员data_channel_type_。后续将对这两个主要的参数进行详细的阐述。
  • WebRTC的PC成员usage_event_accumulator_会以位去记录发生的事件,只要事件发生过一次,就会被usage_event_accumulator_标记上,这些事件大致如下:
    在这里插入图片描述
  • 最终返给用户层的是DataChannelProxy,这是WebRTC防止线程乱入的一贯做法,此处不展开详述了。

接下来是时候好好看看那两个重要的参数了:DataChannelInit && data_channel_type_

2.1.1 初始化参数DataChannelInit

当我们在网上搜索SCTP时,会看到相关的描述,将SCTP介绍为与UDP,TCP同一层次的传输层协议。最早STCP是把窄带7号信令的可靠性传输机制引入到IP协议、优化TCP协议的不能分帧传输的局限性提出来的,不过后来应用不是很广泛。在WebRTC中实现数据通道使用的SCTP是基于改良剪切版的,有两个草案描述了该改良版本《draft-ietf-rtcweb-data-channel-13》、《draft-ietf-rtcweb-data-protocol-09》(此处描述来源于webrtc数据通道之SCTP over DTLS简介)。WebRTC要求使用SCTP必须开启DTLS,协议的分层图如下:可以得知WebRTC中的SCTP实际上是基于UDP在应用层提供的相关实现,而非常规意义上的OSI模型中的传输层。

WebRTC根据实际的应用场景,提供了不同可靠程度的传输模式:可靠传输模式、部分可靠传输模式、不可靠传输模式。
在这里插入图片描述
采用哪种模式取决于结构体参数DataChannelInit,该参数包含的字段如下源码:

struct DataChannelInit {
  // Deprecated. Reliability is assumed, and channel will be unreliable if
  // maxRetransmitTime or MaxRetransmits is set.
  bool reliable = false;
  // True if ordered delivery is required.
  bool ordered = true;
  // The max period of time in milliseconds in which retransmissions will be
  // sent. After this time, no more retransmissions will be sent.
  //
  // Cannot be set along with |maxRetransmits|.
  // This is called |maxPacketLifeTime| in the WebRTC JS API.
  absl::optional<int> maxRetransmitTime;
  // The max number of retransmissions.
  //
  // Cannot be set along with |maxRetransmitTime|.
  absl::optional<int> maxRetransmits;
  // This is set by the application and opaque to the WebRTC implementation.
  std::string protocol;
  // True if the channel has been externally negotiated and we do not send an
  // in-band signalling in the form of an "open" message. If this is true, |id|
  // below must be set; otherwise it should be unset and will be negotiated
  // in-band.
  bool negotiated = false;
  // The stream id, or SID, for SCTP data channels. -1 if unset (see above).
  int id = -1;
};
  • reliable、ordered 、maxRetransmitTime、maxRetransmits确定了传输是否是可靠的,不可靠情况下如何重传,包是否要求有序到达。
参数取值传输可靠性
reliable为true可靠传输
reliable为false,maxRetransmitTime 或 maxRetransmits存在且有效部分可靠传输
reliable为false,且maxRetransmitTime && maxRetransmits均无效不可靠传输
  • protocol 字段描述DataChannel中传输数据的子协议,对于WebRTC来说是透明的
  • negotiated 和 id字段确定了通道协商的手段是通过带外数据协商,还是通过DataChannel本身传输数据协商。当negotiated为真时,说明通过SDP进行带外数据协商,此时,id必须提供有效的SID值;当negotiated为假时,需要建立DataChannel时,在底层通道链路建立后发送Open消息进行带内协商。

InternalDataChannelInit结构参数扩展了DataChannelInit ,增加了一个字段OpenHandshakeRole:

struct InternalDataChannelInit : public DataChannelInit {
  enum OpenHandshakeRole { kOpener, kAcker, kNone };
  // The default role is kOpener because the default |negotiated| is false.
  InternalDataChannelInit() : open_handshake_role(kOpener) {}
  explicit InternalDataChannelInit(const DataChannelInit& base);
  OpenHandshakeRole open_handshake_role;
};

在需要带外协商时,open_handshake_role为kNone;带内协商时open_handshake_role默认为kOpener(一方为kOpener、另一方为kAcker),kOpener主动向kAcker发送Open控制消息,进行带内协商。

2.1.2 PeerConnection.data_channel_controller_.data_channel_type_

PeerConnection.data_channel_controller_.data_channel_type_成员是一个枚举类型的变量,该变量影响到在创建DataChannel时,DataChannel底层使用的协议。

enum DataChannelType {
  // 不允许创建DataChannel;
  DCT_NONE = 0,
  // 基于RTP协议的DataChannel;
  DCT_RTP = 1,
  // 基于SCTP协议的DataChannel;
  DCT_SCTP = 2,
  // 有待解惑
  // Data channel transport over media transport.
  DCT_MEDIA_TRANSPORT = 3,
  // 基于UDP协议的DataChannel,与上一个相比行为一致,但不使用DTLS
  // Data channel transport over datagram transport (with no fallback).  This is
  // the same behavior as data channel transport over media transport, and is
  // usable without DTLS.
  DCT_DATA_CHANNEL_TRANSPORT = 4,
  // 基于UDP传输(使用SCTP协商语法,并可回退到SCTP)。必须使用DTLS。
  // Data channel transport over datagram transport (with SCTP negotiation
  // semantics and a fallback to SCTP).  Only usable with DTLS.
  DCT_DATA_CHANNEL_TRANSPORT_SCTP = 5,
};

PeerConnection.data_channel_controller_.data_channel_type_参数在PC初始化函数中赋值,之前在分析创建PC的文章中粗略地分析过PeerConnection::Initialize()方法,再次把其中与当前议题相关的部分截取出来以供分析:

  if (use_datagram_transport_for_data_channels_) {
    if (configuration.enable_rtp_data_channel) {
      RTC_LOG(LS_ERROR) << "enable_rtp_data_channel and "
                           "use_datagram_transport_for_data_channels are "
                           "incompatible and cannot both be set to true";
      return false;
    }
    if (configuration.enable_dtls_srtp && !*configuration.enable_dtls_srtp) {
      RTC_LOG(LS_INFO) << "Using data channel transport with no fallback";
      data_channel_controller_.set_data_channel_type(
          cricket::DCT_DATA_CHANNEL_TRANSPORT);
    } else {
      RTC_LOG(LS_INFO) << "Using data channel transport with fallback to SCTP";
      data_channel_controller_.set_data_channel_type(
          cricket::DCT_DATA_CHANNEL_TRANSPORT_SCTP);
      config.sctp_factory = sctp_factory_.get();
    }
  } else if (configuration.enable_rtp_data_channel) {
    // Enable creation of RTP data channels if the kEnableRtpDataChannels is
    // set. It takes precendence over the disable_sctp_data_channels
    // PeerConnectionFactoryInterface::Options.
    data_channel_controller_.set_data_channel_type(cricket::DCT_RTP);
  } else {
    // DTLS has to be enabled to use SCTP.
    if (!options.disable_sctp_data_channels && dtls_enabled_) {
      data_channel_controller_.set_data_channel_type(cricket::DCT_SCTP);
      config.sctp_factory = sctp_factory_.get();
    }
  }
  • 首先,use_datagram_transport_for_data_channels_参数是否为真,决定了是否使用DatagramTrasnport接口来收发DataChannel数据(该套接口是后引入的,早期版本应该只有rtp和sctp-dtls)。该参数是否为真一方面取决于应用层给PC传入的RTCConfiguration配置参数中同名字段取值,另一方面还需PeerConnectionFactory中提供MediaTransportFactory用于创建DatagramTrasnport。当二者都具备时,use_datagram_transport_for_data_channels_为真,此时,将创建基于DatagramTrasnport接口的底层收发包对象。如上代码所示,实质上只会创建DCT_DATA_CHANNEL_TRANSPORT 或者 DCT_DATA_CHANNEL_TRANSPORT_SCTP类别的DataChannel,分别是直接基于UDP的DatagramTrasnport接口 和 基于SCTP的DatagramTrasnport接口。没有 DCT_MEDIA_TRANSPORT 这个类别的实现。
  • 其次,当决定不使用DatagramTrasnport接口的实现时,判断是否使用RTP实现,即DCT_RTP类别。取决于应用层给PC传入的RTCConfiguration配置参数是否开启enable_rtp_data_channel。
  • 最后,当既不使用DatagramTrasnport接口,又不使用RTP传输时,启用SCTP-DTLS实现。

总之:
DataChannel最终的类别取决于PC的RTCConfiguration配置参数中的两个取值:use_datagram_transport_for_data_channels_ 和 enable_rtp_data_channel;以及PC工厂类是否提供了MediaTransportFactory。

由于使用DatagramTrasnport接口来收发DataChannel数据的方式是后引入的,是一个实验特性,需要特意设置use_datagram_transport_for_data_channels_ 以及 提供MediaTransportFactory来开启;

由于WebRTC中使用RTP来实现DataChannel是一个计划要淘汰的方式,因此,也需要外部设置RTCConfiguration.enable_rtp_data_channel来启用

由于SCTP是实现DataChannel最正式的方式,因此,在外界不提供额外设置的情况下,默认使用该方式。

2.2 DataChannelController::InternalCreateDataChannel方法

rtc::scoped_refptr<DataChannel> DataChannelController::InternalCreateDataChannel(
    const std::string& label,
    const InternalDataChannelInit* config) {
  // 1.判断PC状态是否已经关闭了
  if (IsClosed()) {
    return nullptr;
  }
  
  // 2. 若data_channel_type_为DCT_NONE,表示禁用DataChannel
  if (data_channel_type() == cricket::DCT_NONE) {
    RTC_LOG(LS_ERROR)
        << "InternalCreateDataChannel: Data is not supported in this call.";
    return nullptr;
  }
 
  // 3. 判断外部是否提供了InternalDataChannelInit,否则提供默认的
  InternalDataChannelInit new_config =
      config ? (*config) : InternalDataChannelInit();

  // 4. 如果DataChannel是类sctp类型,我们需要对sid进行处理
  //    类sctp已经在上文描述过
  if (DataChannel::IsSctpLike(data_channel_type_)) {
    // 4.1 如果new_config.id < 0,是无效的sid值,根据SSLRole是服务器还是客户端
    // 分配有效的sid
    if (new_config.id < 0) {
      rtc::SSLRole role;
      if ((GetSctpSslRole(&role)) &&
          !sid_allocator_.AllocateSid(role, &new_config.id)) {
        RTC_LOG(LS_ERROR)
            << "No id can be allocated for the SCTP data channel.";
        return nullptr;
      }
    // 4.2 如果new_config.id > 0,判断外部提供的new_config.id是否有效
    //     也即new_config.id是否超界或者已经被使用
    } else if (!sid_allocator_.ReserveSid(new_config.id)) {
      RTC_LOG(LS_ERROR) << "Failed to create a SCTP data channel "
                           "because the id is already in use or out of range.";
      return nullptr;
    }
  }

  // 5. DataChannel::Create根据datachannel类别,标签,参数来创建DataChannel
  rtc::scoped_refptr<DataChannel> channel(
      DataChannel::Create(this, data_channel_type(), label, new_config));
  if (!channel) {
    sid_allocator_.ReleaseSid(new_config.id);
    return nullptr;
  }

  // 6. 存储创建的DataChannel
  // 6.1 如果创建的是cricket::DCT_RTP类别的DataChannel,则放入成员rtp_data_channels_中
  if (channel->data_channel_type() == cricket::DCT_RTP) {
    if (rtp_data_channels_.find(channel->label()) != rtp_data_channels_.end()) {
      RTC_LOG(LS_ERROR) << "DataChannel with label " << channel->label()
                        << " already exists.";
      return nullptr;
    }
    rtp_data_channels_[channel->label()] = channel;
  // 6.2 如果创建的是类sctp的DataChannel,则放入成员sctp_data_channels_中
  } else {
    RTC_DCHECK(DataChannel::IsSctpLike(data_channel_type_));
    sctp_data_channels_.push_back(channel);
    // 绑定通道的关闭信号和PC对应的槽,让PC知道SCTP通道的关闭事件
    channel->SignalClosed.connect(this,
                                  &PeerConnection::OnSctpDataChannelClosed);
  }

  // 7. 发送通道创建的信号,一方面PC封装了DataChannelController的SignalDataChannelCreated_
  //    信号,PC肯定能获知该信号进行响应;另一方面RTCStatsCollector等对象通过关联PC封装的 
  //    SignalDataChannelCreated信号也可以处理数据通道被创建的消息。
  SignalDataChannelCreated_(channel.get());
  return channel;
}

该函数就不展开分析了,最终调用了DataChannel::Create()方法来创建DataChannel。后续来看下DataChannel::Create()方法的内容。

2.3 DataChannel::Create方法

分两步:构造DataChannel + 初始化DataChannel

rtc::scoped_refptr<DataChannel> DataChannel::Create(
    DataChannelProviderInterface* provider,
    cricket::DataChannelType dct,
    const std::string& label,
    const InternalDataChannelInit& config) {
  // 1. 调用DataChannel的构造函数
  rtc::scoped_refptr<DataChannel> channel(
      new rtc::RefCountedObject<DataChannel>(provider, dct, label));
  // 2. 调用初始化方法    
  if (!channel->Init(config)) {
    return NULL;
  }
  return channel;
}

2.3.1 DataChannel构造

初始化成员,各成员的用途

DataChannel::DataChannel(DataChannelProviderInterface* provider,
                         cricket::DataChannelType dct,
                         const std::string& label)
    : internal_id_(GenerateUniqueId()), 
      label_(label),
      observer_(nullptr),
      state_(kConnecting),
      messages_sent_(0),
      bytes_sent_(0),
      messages_received_(0),
      bytes_received_(0),
      buffered_amount_(0),
      data_channel_type_(dct),
      provider_(provider),
      handshake_state_(kHandshakeInit),
      connected_to_provider_(false),
      send_ssrc_set_(false),
      receive_ssrc_set_(false),
      writable_(false),
      send_ssrc_(0),
      receive_ssrc_(0) {}
  • internal_id_:内部使用的自增id,从0开始,每创建一个DataChanel单调递增1;
  • label_:通道的标识,与Track的label一样;
  • observer_:通道的观察者,包含通道状态改变回调、通道获取数据回调、通道缓冲数据大小改变时回调;
  • state_:通道的状态,包含kConnecting(通道连接中状态,无法发送数据)、kOpen(通道已连接状态,可发送数据,send_ssrc_和receive_ssrc_均有值)、kClosing(通道关闭中状态,不允许继续发送数据,但已在buffer中的数据将被发送)、kClosed(通道已关闭状态);
  • messages_sent_ && bytes_sent_:发送消息个数和字节数;
  • messages_received_ && bytes_received_:接收消息个数和字节数;
  • buffered_amount_: DataChanel层发送缓存中缓存的还未发送的数据的字节数,当Transport要发送数据前该值增加,当发送成功后该值相应的减小;
  • data_channel_type_:通道类型RTP?SCTP?还是基于DatagramTransport的那3钟
  • provider_:实际上就DataChannelController,它继承实现了DataChannelProviderInterface,提供了实际上的数据发送功能,将把DataChannel要发送的数据代理到DataChannelTransport去发哦是那个。相当重要;
  • handshake_state_:握手状态,需要进行带内协商时该状态起着作用;
  • connected_to_provider_:是否与provider相关上;
  • send_ssrc_set_ && receive_ssrc_set_:send_ssrc_ && receive_ssrc_是否已经设置, RTP协议类别的使用;
  • writable_:通道是否已经准备ok,可以发送数据;
  • send_ssrc_ && receive_ssrc_:发送端ssrc和接收端ssrc,RTP协议类别的使用,SCTP协议类别使用SID,保存在成员config_.sid中,见后面的DataChannel::Init方法。

2.3.2 DataChannel初始化

bool DataChannel::Init(const InternalDataChannelInit& config) {
  // 根据通道类别进行分类处理
  // 1. RTP类别的通道
  if (data_channel_type_ == cricket::DCT_RTP) {
    // 1.1 入参判断:
    //     RTP通道不能提供可靠传输,因此,reliable不能为真;
    //     RTP通道id必须为-1,因为sid是为sctp准备的,RTP通道不应该设置该值;
    //     RTP通道不提供按重传次数或者最大重传时间这种部分可靠性,因此,maxRetransmits
    //           maxRetransmitTime不可存在。
    if (config.reliable || config.id != -1 || config.maxRetransmits ||
        config.maxRetransmitTime) {
      RTC_LOG(LS_ERROR) << "Failed to initialize the RTP data channel due to "
                           "invalid DataChannelInit.";
      return false;
    }
    //1.2 RTP通道不需要带内协商,因此,握手状态为kHandshakeReady即可
    handshake_state_ = kHandshakeReady;
    
  // 2. 类sctp类别的通道
  } else if (IsSctpLike(data_channel_type_)) {
    // 2.1 判断参数的有效性
    if (config.id < -1 ||
        (config.maxRetransmits && *config.maxRetransmits < 0) ||
        (config.maxRetransmitTime && *config.maxRetransmitTime < 0)) {
      RTC_LOG(LS_ERROR) << "Failed to initialize the SCTP data channel due to "
                           "invalid DataChannelInit.";
      return false;
    }
    // 2.2 按最大重传次数或最大重传时间来确定重传规则,二者不能同时存在
    if (config.maxRetransmits && config.maxRetransmitTime) {
      RTC_LOG(LS_ERROR)
          << "maxRetransmits and maxRetransmitTime should not be both set.";
      return false;
    }
    config_ = config;
    // 2.3 根据握手角色,确定本端握手初始状态
    switch (config_.open_handshake_role) {
      // 2.3.1 KNone表示不在此进行协商,进行带外协商,因此,状态置为已协商完成的
      //       kHandshakeReady状态即可。
      case webrtc::InternalDataChannelInit::kNone:  // pre-negotiated
        handshake_state_ = kHandshakeReady;
        break;
      // 2.3.2 kOpener表示通道打开者,主动发送Open消息方,状态置为kHandshakeShouldSendOpen
      //       表示需要但还未发送Open消息
      case webrtc::InternalDataChannelInit::kOpener:
        handshake_state_ = kHandshakeShouldSendOpen;
        break;
      // 2.3.3 kAcker表示通道的被动打开方,因此状态设置为kHandshakeShouldSendAck
      //       表示下一次要发送Ack,但还未发送的状态
      case webrtc::InternalDataChannelInit::kAcker:
        handshake_state_ = kHandshakeShouldSendAck;
        break;
    }

    // 2.4 尝试关联provider提供的底层transport,以防transport已经创建好了,错过其发出的
    //   ready信号
    // Try to connect to the transport in case the transport channel already
    // exists.
    OnTransportChannelCreated();

    // 2.5 如果底层transport已经是可以发送数据的状态(因为初始化通道ok的信号可能先于
    //    DataChannel创建被发送),以异步的方式来执行OnChannelReady(true)是因为
    //    在当前方法返回前,上层的对象还没建立起连接.
    // Checks if the transport is ready to send because the initial channel
    // ready signal may have been sent before the DataChannel creation.
    // This has to be done async because the upper layer objects (e.g.
    // Chrome glue and WebKit) are not wired up properly until after this
    // function returns.
    if (provider_->ReadyToSendData()) {
      invoker_.AsyncInvoke<void>(RTC_FROM_HERE, rtc::Thread::Current(),
                                 [this] { OnChannelReady(true); });
    }
  }

  return true;
}

之前我们提到RTP是会被淘汰的方式,SCTP是当前主流方式,因此,我们逮着SCTP来说明。DataChannel初始过程中,最重要的莫过于调用OnTransportChannelCreated(),使得provider将上层的DataChannel与底层的Transport对象给联系起来。我们来重点看下该方法。

2.3.3 DataChannel与底层Transport的关联

void DataChannel::OnTransportChannelCreated() {
  // 1. 只有类SCTP才需要进行关联
  RTC_DCHECK(IsSctpLike(data_channel_type_));
  // 2. 进行关联
  if (!connected_to_provider_) {
    connected_to_provider_ = provider_->ConnectDataChannel(this);
  }
  // 3. 关联时,sid会被清掉,因此,再设置一次。
  // The sid may have been unassigned when provider_->ConnectDataChannel was
  // done. So always add the streams even if connected_to_provider_ is true.
  if (config_.id >= 0) {
    provider_->AddSctpDataStream(config_.id);
  }
}

进一步看下真正的关联实现:

bool DataChannelController::ConnectDataChannel(
    DataChannel* webrtc_data_channel) {
  // 1. 必须运行在信令线程
  RTC_DCHECK_RUN_ON(signaling_thread());
 
  // 2. 如果底层传输通道还不存在,则不需要绑定了
  //    rtp_data_channel是RTP协议的底层Transport
  //    data_channel_transport是sctp协议的底层transport
  if (!rtp_data_channel() && !data_channel_transport()) {
    // Don't log an error here, because DataChannels are expected to call
    // ConnectDataChannel in this state. It's the only way to initially tell
    // whether or not the underlying transport is ready.
    return false;
  }
  
  // 3. 如果sctp协议的底层transport存在,则进行相关信号绑定。请注意:
  //    信号的发射者是DataChannelController,而非transport本身;
  //    信号的接收者是上层的DataChannel对象;
  //    势必.....DataChannelController还得与底层transport进行
  //    对应的关联...如何关联,往后看
  if (data_channel_transport()) {
    // 3.1 底层Transport处于可写状态
    SignalDataChannelTransportWritable_s.connect(webrtc_data_channel,
                                                 &DataChannel::OnChannelReady);
    // 3.2 底层Transport收到data                                             
    SignalDataChannelTransportReceivedData_s.connect(
        webrtc_data_channel, &DataChannel::OnDataReceived);
    // 3.3 底层Transport处于关闭过程中
    SignalDataChannelTransportChannelClosing_s.connect(
        webrtc_data_channel, &DataChannel::OnClosingProcedureStartedRemotely);
    // 3.4 底层Transport处于已关闭状态
    SignalDataChannelTransportChannelClosed_s.connect(
        webrtc_data_channel, &DataChannel::OnClosingProcedureComplete);
  }

  // 4. 如果是rtp协议的底层传输通道存在,则也进行相关信号绑定,状态没有sctp那么多
  //    并且与3应该是不会同时存在的,并且注意:
  //    信号发送者是底层传输通道,不需要provider做二道贩子
  //    信号接收者是上层DataChannel。
  if (rtp_data_channel()) {
    // 4.1 底层通道已处于可发送状态
    rtp_data_channel()->SignalReadyToSendData.connect(
        webrtc_data_channel, &DataChannel::OnChannelReady);
    // 4.2 底层通道有数据到达
    rtp_data_channel()->SignalDataReceived.connect(
        webrtc_data_channel, &DataChannel::OnDataReceived);
  }
  return true;
}

代码分析到这儿,DataChannel创建过程也分析完了,可能还会有懵逼的地方。比如,对于SCTP协议的传输,如上代码所示,DataChannelController做了二道贩子,在底层的Transport与DataChannel之间拉起了皮条。那么DataChannelController是如何与Transport勾搭上的呢?还有几个问题:

  • DataChannelController这个对象是什么时候创建的呢?
  • SCTP底层传输对象DataChannelTransportInterface到底实体类是哪个?什么时候创建的?
  • DataChannelController与DataChannelTransportInterface是如何建立关联,又是在何时建立的关联?

由于本篇文章已经很长了,打算另起一篇文章来说明下DataChannel相关的这几个类,并回答上述几个问题。WebRTC源码分析——DataChannel及其相关类

3. 总结

回顾下上述所说内容,捡要点做下总结:

  • DataChannel的底层传输实际上可以是SCTP传输,也可以是RTP传输。但是我们需要了解到RTP传输方式是将要被淘汰的方式。因此,以后分析只需要关注SCTP是如何做的即可。
  • 创建PC时,应用层传输的RTCConfiguration中的几个字段决定了我们创建DataChannel是采用何种底层传输,细节可以再看看上文。当然,默认情况是采用SCTP。
  • 创建DataChannel时传入的结构体DataChannelInit决定了底层传输数据的可靠级别:可靠、部分可靠、不可靠;也决定了协商方式:是进行带外协商,还是带内协商。
  • 当使用带内协商时,SCTP传输在协商阶段是有角色的,一方是主动打开方,一方是被动打开方。主动打开方在底层通道链接建立后,需要主动发送Open消息,被动打开方需要回复Ack。
  • WebRTC中的SCTP并非是OSI模型中的传输层协议,而是应用层协议,并且使用STCP时,必须使用DLS。如下图所示:
    在这里插入图片描述
  • DataChannel是WebRTC数据通道的顶层对象,底层根据选择的传输方式创建的底层传输对象是不一样的,比如支持SCTP协议的传输对象是实现了接口DataChannelTransportInterface的SctpDataChannelTransport对象;而支持RTP协议的传输对象层对象是RtpDataChannel对象。
    在这里插入图片描述
  • DataChannel需要与底层传输对象SctpDataChannelTransport建立关联以便监控传输状态以及收发数据包。但二者中间隔了DataChannelController对象,DataChannelController起着桥梁作用。
  • 14
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: webrtc-qt-example是一个基于Qt框架开发的WebRTC示例项目。 WebRTC是一种开源的实时通信技术,能够支持音频、视频和数据的实时传输。它通过浏览器之间的端对端连接,实现了高质量的实时通信。 webrtc-qt-example的目的是展示如何使用Qt进行WebRTC开发。Qt是一套跨平台的C++应用程序开发框架,它提供了丰富的工具和库,使开发者能够快速构建可靠的应用程序。 这个示例项目提供了一些基本的功能和界面,使开发者能够了解和学习如何将WebRTC集成到Qt应用程序中。它包含了常见的WebRTC功能,如媒体流捕获、媒体流传输、信令交换等。 通过webrtc-qt-example,开发者可以学习到如何使用Qt的多媒体模块来捕获音频、视频和媒体设备。同时,也可以学习到如何使用Qt的网络模块来进行实时信令交换和流传输。 这个示例项目还提供了一些简单的界面,方便开发者进行测试和调试。开发者可以通过该界面实现与其他WebRTC应用的通信,例如建立视频通话、音频通话等。 总之,webrtc-qt-example是一个非常实用的示例项目,可以帮助开发者快速上手并掌握WebRTC在Qt中的开发。 ### 回答2: webrtc-qt-example是一个基于Qt框架的WebRTC示例应用程序。WebRTC是一种开源项目,它提供了在浏览器之间进行实时通信的能力,包括视频和音频的传输。而webrtc-qt-example则是将这种技术集成到Qt应用程序中的一个示例。 在webrtc-qt-example中,它使用了Qt的多媒体框架和WebRTC提供的API来实现音视频的传输和显示。通过使用WebRTC的API,webrtc-qt-example可以建立点对点的连接,进行音频和视频的实时传输。 webrtc-qt-example中的代码结构清晰,易于理解和扩展。它提供了一些基本的功能,如建立连接、发送和接收音视频流、呼叫取消等。开发者可以根据自己的需求来对这些功能进行定制和扩展。 此外,webrtc-qt-example还支持一些高级特性,如媒体设备的选择、音视频的编码和解码等。开发者可以通过修改代码来选择不同的媒体设备,并且可以使用不同的编码和解码算法来满足自己的需求。 总之,webrtc-qt-example是一个很棒的WebRTC示例应用程序,它可以帮助开发者快速了解和使用WebRTC技术。无论是为了实现实时视频通话、视频会议还是其他需要音视频传输的应用场景,webrtc-qt-example都提供了一个良好的起点,帮助开发者快速上手并实现自己的需求。 ### 回答3: webrtc-qt-example是一个基于Qt框架和WebRTC技术的示例应用。WebRTC是一种用于在Web浏览器上实现实时通信的开源项目,它提供了一套丰富的API和协议,可以实现音视频通话、数据传输以及屏幕共享等功能。 webrtc-qt-example利用Qt框架提供的跨平台能力,结合WebRTC技术,展示了在Qt应用中如何实现实时通信功能。这个示例应用具有以下特点和功能: 1. 界面友好:webrtc-qt-example使用Qt的GUI绘制工具,具有美观、直观的用户界面,便于用户操作和使用。 2. 实时通信:webrtc-qt-example内置了WebRTC的音视频通信功能,可以实现实时的语音和视频通话,支持两个或多个用户之间的通信。 3. 数据传输:除了音视频通话,webrtc-qt-example还支持在通话中传输数据。可以通过编写代码,实现实时文本传输或共享文件等功能。 4. 屏幕共享:webrtc-qt-example还支持屏幕共享功能,可以将自己的屏幕内容分享给其他用户,实现远程协助或在线教育等应用场景。 通过webrtc-qt-example的学习和实践,开发者可以了解并深入理解WebRTC技术的使用方法,以及在Qt框架中的应用。同时,借助webrtc-qt-example提供的示例代码和API文档,开发者可以进一步开发出更加复杂和功能丰富的实时通信应用,满足不同领域的需求。 总之,webrtc-qt-example是一个基于Qt框架和WebRTC技术的示例应用,具备实时音视频通话、数据传输和屏幕共享等功能,适用于开发者学习、实践和开发基于WebRTC的实时通信应用程序。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值