一、架构图
webrtc的ICE符合rfc5245标准协议。该协议的链接为:https://tools.ietf.org/html/rfc5245
每个终端都有一系列传输地址(包括传输协议、IP地址和端口)的候选,可以用来和其他端点进行通信。其中可能包括:
1、直接和网络接口联系的传输地址(host address)
2、经过NAT转换的传输地址,即反射地址(server reflective address)
3、TURN服务器分配的中继地址(relay address)
虽然潜在要求任意一个主叫Client的候选地址都能用来和被叫Client的候选地址进行通信。但是实际中发现有许多组合是无法工作的。举例来说,如果主叫Client和被叫Client都在NAT之后而且不处于同一内网,他们的直接地址就无法进行通信。ICE的目的就是为了发现哪一对候选地址的组合可以工作,并且通过系统的方法对所有组合进行测试(用一种精心挑选的顺序)。
在rfc5245定义里面,实现ICE一共有六个步骤:收集候选地址、连通性测试、候选排序、冻结候选、检查的安全性、ICE结束。webrtc结合实际应用,优化这六步为如下五步:收集候选地址、在信令通道中交换候选选项、执行连接检查、选定并启动媒体。
1、实现收集候选地址核心函数:BasicPortAllocatorSession::DoAllocate
2、在信令通道中交换候选选项:发送本端的候选选项核心函数:AllocationSequence::EnableProtocol
接收远端的候选选项核心函数:PeerConnection::AddIceCandidate
3、执行连接检查核心函数: P2PTransportChannel::SortConnectionsAndUpdateState
4、选定并启动媒体核心函数:P2PTransportChannel::OnReadyToSend
过程中检测有变化时:P2PTransportChannel::SwitchSelectedConnection
二、处理流程
一、收集候选地址
1、获取turn、stun服务器信息处理流程
Conductor::InitializePeerConnection
->Conductor::CreatePeerConnection
->PeerConnectionFactory::CreatePeerConnection
->PeerConnection::Initialize
->PeerConnection::InitializePortAllocator_n//这个函数解析配置信息,确定探测类型及参数。
->PortAllocator::SetConfiguration//这里配置服务器信息。
2、生效turn、stun服务器处理流程
WebRtcSessionDescriptionFactory::OnMessage
->Conductor::OnSuccess
->PeerConnection::SetLocalDescription (session_->MaybeStartGathering())
->WebRtcSession::MaybeStartGathering
->TransportController::MaybeStartGathering
->TransportController::MaybeStartGathering_n
->P2PTransportChannel::MaybeStartGathering
AddAllocatorSession(allocator_->CreateSession());//注册信号量
allocator_sessions_.back()->StartGettingPorts(); //启动Port资源申请消息
当P2PTransportChannel::MaybeStartGathering函数调用BasicPortAllocatorSession::StartGettingPorts后,就会启动MSG_CONFIG_START消息处理流程。开始收集本端的candidates信息。
3、Port资源申请状态机
enum {
MSG_CONFIG_START,
MSG_CONFIG_READY,
MSG_ALLOCATE,
MSG_ALLOCATION_PHASE,
MSG_SEQUENCEOBJECTS_CREATED,
MSG_CONFIG_STOP,
};
4、Port状态机接口函数
void BasicPortAllocatorSession::OnMessage(rtc::Message *message) {
switch (message->message_id) {
case MSG_CONFIG_START:
GetPortConfigurations();
break;
case MSG_CONFIG_READY:
OnConfigReady(static_cast<PortConfiguration*>(message->pdata));
break;
case MSG_ALLOCATE:
OnAllocate();
break;
case MSG_SEQUENCEOBJECTS_CREATED:
OnAllocationSequenceObjectsCreated();
break;
case MSG_CONFIG_STOP:
OnConfigStop();
break;
default:
RTC_NOTREACHED();
}
}
1、MSG_CONFIG_START:申请stun配置文件信息,在配置文件中增加turn信息。配置OK后 ,向消息队列发送ready消息。
2、MSG_CONFIG_READY:push在START里面创建的配置信息到configs_,向消息队列发送MSG_ALLOCATE消息。
3、MSG_ALLOCATE:在这里会创建Port资源,收集candidates信息。
AllocationSequence* sequence =new AllocationSequence();//申请AllocationSequence类
sequence->SignalPortAllocationComplete.connect();//注册信号量
sequence->Init();//启动给自己发送MSG_ALLOCATION_PHASE消息队列
sequence->Start();
sequences_.push_back(sequence);
4、MSG_ALLOCATION_PHASE:Port申请处理流程
void AllocationSequence::OnMessage(rtc::Message* msg) {
RTC_DCHECK(msg->message_id == MSG_ALLOCATION_PHASE);
// Perform all of the phases in the current step.
switch (phase_) {
case PHASE_UDP:
CreateUDPPorts();//申请udp port资源
CreateStunPorts();//申请 stun port资源
EnableProtocol(PROTO_UDP);
break;
case PHASE_RELAY:
CreateRelayPorts();//申请GTURN或TURN port资源
break;
case PHASE_TCP:
CreateTCPPorts();//申请TCP Port资源
EnableProtocol(PROTO_TCP);
break;
case PHASE_SSLTCP:
state_ = kCompleted;//不需要继续探测,停止给自己发MSG_ALLOCATION_PHASE消息
EnableProtocol(PROTO_SSLTCP);
break;
}
if (state() == kRunning) {
++phase_;//更新探测阶段
session_->network_thread()->PostDelayed(RTC_FROM_HERE,MSG_ALLOCATION_PHASE);//给自己发消息。
} else {
session_->network_thread()->Clear(this, MSG_ALLOCATION_PHASE);//清除给自己发消息资源
SignalPortAllocationComplete(this);//发送信号量,知会需要探测的Port资源都已经申请完毕。
}
}
5、发送candidates信息给对端:
每次调用EnableProtocol函数,就是将本地的Candidates信息,发送给对端的Client
AllocationSequence::EnableProtocol
->BasicPortAllocatorSession::OnProtocolEnabled
->SignalCandidatesReady
->P2PTransportChannel::OnCandidatesReady
->SignalCandidateGathered
->TransportController::OnChannelCandidateGathered_n
->SignalCandidatesGathered
->WebRtcSession::OnTransportControllerCandidatesGathered
->Conductor::OnIceCandidate
->Conductor::SendMessage
->PeerConnectionClient::SendToPeer
Port资源申请结束后,通过信号量传递该信息:
SignalPortAllocationComplete信号量触发调用
BasicPortAllocatorSession::OnPortAllocationComplete
二、接收对端候选选项
当主叫Client获得了它全部的候选地址,把它们按优先级从高到低排序后通过信令通道(SDP offer)发送给被叫Client。被叫Client收到后做同样的事,然后通过响应发给主叫Client。最后,每个agent都拥有了自己和对方的候选地址。它们共同组成CANDIDATE PAIRS(候选地址对)。client通过CANDIDATE PAIRS发送给对端STUN request来检查它们。
1、处理HTTP的POST/message里面的SDP信令
Conductor::OnMessageFromPeer
->PeerConnection::SetRemoteDescription
->WebRtcSession::SetRemoteDescription
->WebRtcSession::UpdateSessionState
->WebRtcSession::PushdownTransportDescription
->WebRtcSession::PushdownRemoteTransportDescription
->TransportController::SetRemoteTransportDescription
->TransportController::SetRemoteTransportDescription_n
->JsepTransport::SetRemoteTransportDescription
->JsepTransport::ApplyRemoteTransportDescription
->P2PTransportChannel::SetRemoteIceParameters
->P2PTransportChannel::RequestSortAndStateUpdate
->P2PTransportChannel::SortConnectionsAndUpdateState
2、处理HTTP的POST/message里面的Candidate信令
Conductor::OnMessageFromPeer
->PeerConnection::AddIceCandidate
->WebRtcSession::ProcessIceMessage
->WebRtcSession::UseCandidate
->TransportController::AddRemoteCandidates
->TransportController::AddRemoteCandidates_n
->P2PTransportChannel::AddRemoteCandidate
->P2PTransportChannel::SortConnectionsAndUpdateState
->P2PTransportChannel::MaybeSwitchSelectedConnection
->P2PTransportChannel::SwitchSelectedConnection
SignalReadyToSend SignalSelectedCandidatePairChanged
三、执行连接检查
可以参考该状态机。当连接检查失败,会启动重新收集candidate信息。
// messages for queuing up work for ourselves
enum {
MSG_SORT_AND_UPDATE_STATE = 1,
MSG_CHECK_AND_PING,
MSG_REGATHER_ON_FAILED_NETWORKS,
MSG_REGATHER_ON_ALL_NETWORKS
};
四、选择选定的对并启动媒体
进行探测,收到对端的报文的时候,就会发送该信号量,选定传输方式。
AsyncUDPSocket::OnReadEvent发送信号量SignalReadPacket
->Thread::Start
->Thread::PreRun
->Thread::Run
->Thread::ProcessMessages
->MessageQueue::Get
->PhysicalSocketServer::Wait
->SocketDispatcher::OnEvent
->AsyncUDPSocket::OnReadEvent
->AllocationSequence::OnReadPacket
->UDPPort::HandleIncomingPacket
->UDPPort::OnReadPacket
->Connection::OnReadPacket
->StunRequestManager::CheckResponse
->ConnectionRequest::OnResponse
->Connection::OnConnectionRequestResponse
::Connection::ReceivedPingResponse[set_write_state(STATE_WRITABLE)]
::Connection::MaybeUpdateLocalCandidate[SignalStateChange]
UpdateNetworkCost->SignalStateChange:
在多网卡时,对wifi、4G、vpn等类型设置不同权重值。当检测出权重高的网络可以通的时候,进行切换。
set_connected->SignalStateChange TCP链接成功后,会发送这个信号。
UpdateReceiving->SignalStateChange
HandleBindingRequest->SignalStateChange
五、心跳检测
webrtc进行ICE探测时,会在不稳定链接时,持续发送ping心跳报文,当检测链接已经稳定后,就不再发心跳报文,也不会探测切换传输路径。
链接是否稳定是在weak函数里面判断的。要是服务器资源宽裕的话,可以把weak修改问false。整个通话期间持续探测。可以做到主备倒换的目的
三、附加说明
webrtc会每隔两秒,检查一下网络状态是否有变化,若是有变化,就重新探测网络,选择其他的传输方式。所以会概率出现一会P2P一会中转的现象。
函数调用关系如下:
Network::Network
BasicNetworkManager::CreateNetworks
BasicNetworkManager::UpdateNetworksOnce
BasicNetworkManager::UpdateNetworksContinually
BasicNetworkManager::OnMessage
MessageQueue::Dispatch
Thread::ProcessMessages
Thread::Run
Thread::PreRun
来源:https://www.jianshu.com/p/c51fccf853cb
四、ICE配置参数
enum {
// Disable local UDP ports. This doesn't impact how we connect to relay
// servers.
PORTALLOCATOR_DISABLE_UDP = 0x01,
PORTALLOCATOR_DISABLE_STUN = 0x02,
PORTALLOCATOR_DISABLE_RELAY = 0x04,
// Disable local TCP ports. This doesn't impact how we connect to relay
// servers.
PORTALLOCATOR_DISABLE_TCP = 0x08,
PORTALLOCATOR_ENABLE_IPV6 = 0x40,
// TODO(pthatcher): Remove this once it's no longer used in:
// remoting/client/plugin/pepper_port_allocator.cc
// remoting/protocol/chromium_port_allocator.cc
// remoting/test/fake_port_allocator.cc
// It's a no-op and is no longer needed.
PORTALLOCATOR_ENABLE_SHARED_UFRAG = 0x80,
PORTALLOCATOR_ENABLE_SHARED_SOCKET = 0x100,
PORTALLOCATOR_ENABLE_STUN_RETRANSMIT_ATTRIBUTE = 0x200,
// When specified, we'll only allocate the STUN candidate for the public
// interface as seen by regular http traffic and the HOST candidate associated
// with the default local interface.
PORTALLOCATOR_DISABLE_ADAPTER_ENUMERATION = 0x400,
// When specified along with PORTALLOCATOR_DISABLE_ADAPTER_ENUMERATION, the
// default local candidate mentioned above will not be allocated. Only the
// STUN candidate will be.
PORTALLOCATOR_DISABLE_DEFAULT_LOCAL_CANDIDATE = 0x800,
// Disallow use of UDP when connecting to a relay server. Since proxy servers
// usually don't handle UDP, using UDP will leak the IP address.
PORTALLOCATOR_DISABLE_UDP_RELAY = 0x1000,
// When multiple networks exist, do not gather candidates on the ones with
// high cost. So if both Wi-Fi and cellular networks exist, gather only on the
// Wi-Fi network. If a network type is "unknown", it has a cost lower than
// cellular but higher than Wi-Fi/Ethernet. So if an unknown network exists,
// cellular networks will not be used to gather candidates and if a Wi-Fi
// network is present, "unknown" networks will not be usd to gather
// candidates. Doing so ensures that even if a cellular network type was not
// detected initially, it would not be used if a Wi-Fi network is present.
PORTALLOCATOR_DISABLE_COSTLY_NETWORKS = 0x2000,
// When specified, do not collect IPv6 ICE candidates on Wi-Fi.
PORTALLOCATOR_ENABLE_IPV6_ON_WIFI = 0x4000,
// When this flag is set, ports not bound to any specific network interface
// will be used, in addition to normal ports bound to the enumerated
// interfaces. Without this flag, these "any address" ports would only be
// used when network enumeration fails or is disabled. But under certain
// conditions, these ports may succeed where others fail, so they may allow
// the application to work in a wider variety of environments, at the expense
// of having to allocate additional candidates.
PORTALLOCATOR_ENABLE_ANY_ADDRESS_PORTS = 0x8000,
};