创建完PeerConnectionFactory 和 PeerConnection这两个API层的操盘对象之后,紧接着需要初始化本地的媒体,也即创建本地的音频轨、视频轨、数据通道,并将这些本地的媒体轨道添加到PeerConnection对象中。如图中红色标注所示。
应用层通过调用PC的CreateDataChannel方法来创建DataChannel,PC有两个CreateDataChannel方法,其中一个入参是mid值,另外一个如下源码所示。
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> rtp_data_channels_;
// vector> sctp_data_channels_;
// vector> 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()方法进行了初步分析,其中一些知识点拎出来再说明下:
接下来是时候好好看看那两个重要的参数了:DataChannelInit && data_channel_type_
当我们在网上搜索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为true | 可靠传输 |
reliable为false,maxRetransmitTime 或 maxRetransmits存在且有效 | 部分可靠传输 |
reliable为false,且maxRetransmitTime && maxRetransmits均无效 | 不可靠传输 |
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控制消息,进行带内协商。
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();
}
}
总之:
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最正式的方式,因此,在外界不提供额外设置的情况下,默认使用该方式。
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()方法的内容。
分两步:构造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;
}
初始化成员,各成员的用途
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) {}
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对象给联系起来。我们来重点看下该方法。
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勾搭上的呢?还有几个问题:
由于本篇文章已经很长了,打算另起一篇文章来说明下DataChannel相关的这几个类,并回答上述几个问题。WebRTC源码分析——DataChannel及其相关类
回顾下上述所说内容,捡要点做下总结: