1、类图
pc 相关ICE 类图
几个类的介绍,在看代码过程不理解,有需要大家可以回来在看
- StunRequestManager 请求管理类,为了解决发出去的请求,在接收到响应的时候找到对应的请求进行处理
- UDPPort , 负责主机地址、和反射地址(共享socket 模式下)的候选获取
- AllocationSequence,每一个网络都会有一个sequence, 负责获取 主机地址、反射地址、中继映射、Tcp 映射。里面调用UDPPort 等来获取具体反射地址。
- BasicPortAllocatorSession:负责在一个而会话中,根据配置来获取候选项, session 中创建每个网络的 AllocationSequence,也就是一个Session 中会保存所有网络接口的信息
- PortAllocator: 负责创建BasicPortAllocatorSession
- JsepTransportController : 是整个数据传输的控制类
2、启动收集候选
在调用CreateOffer 的过程中,调用了SetLocalDescription 函数中,开始了收集信息
jsep 细节
JsepTransportController::MaybeStartGathering
遍历所有的Dtls ,然后调用ice transport 的 MaybeStartGathering,通过上图我们知道,底层依赖都是P2PTransportChannel
void JsepTransportController::MaybeStartGathering() {
if (!network_thread_->IsCurrent()) {
network_thread_->Invoke<void>(RTC_FROM_HERE,
[&] { MaybeStartGathering(); });
return;
}
for (auto& dtls : GetDtlsTransports()) {
dtls->ice_transport()->MaybeStartGathering();
}
}
P2PTransportChannel::MaybeStartGathering
由于收集信息是由session 负责,所以需要先创建session 才能收集
- 有可复用的session 可以直接使用,并做一下处理,不需要重新收集
- 如果没有可以使用session, 通过allocator_ 新创建一个session, 链接信号添加到allocator_sessions_ 中,调用BasicPortAllocatorSession::StartGettingPorts开始获取端口
void P2PTransportChannel::MaybeStartGathering() {
// 1. 判断ICE参数
if (ice_parameters_.ufrag.empty() || ice_parameters_.pwd.empty()) {
RTC_LOG(LS_ERROR)
<< "Cannot gather candidates because ICE parameters are empty"
" ufrag: " << ice_parameters_.ufrag
<< " pwd: " << ice_parameters_.pwd;
return;
}
// Start gathering if we never started before, or if an ICE restart occurred.
// 2. 同上方解释,没有启动过或者没有重启过,就直接开始收集
if (allocator_sessions_.empty() ||
IceCredentialsChanged(allocator_sessions_.back()->ice_ufrag(),
allocator_sessions_.back()->ice_pwd(),
ice_parameters_.ufrag, ice_parameters_.pwd)) {
if (gathering_state_ != kIceGatheringGathering) {
gathering_state_ = kIceGatheringGathering;
SignalGatheringState(this);
}
// 2.1 重启的 IceRestartState 的状态
if (!allocator_sessions_.empty()) {
IceRestartState state;
if (writable()) {
state = IceRestartState::CONNECTED;
} else if (IsGettingPorts()) {
state = IceRestartState::CONNECTING;
} else {
state = IceRestartState::DISCONNECTED;
}
RTC_HISTOGRAM_ENUMERATION("WebRTC.PeerConnection.IceRestartState",
static_cast<int>(state),
static_cast<int>(IceRestartState::MAX_VALUE));
}
// Time for a new allocator.
// 2.2 根据参数在session 池中获取一个 session
std::unique_ptr<PortAllocatorSession> pooled_session =
allocator_->TakePooledSession(transport_name(), component(),
ice_parameters_.ufrag,
ice_parameters_.pwd);
if (pooled_session) {
// 2.3.1 如果复用之前的session, 添加session
AddAllocatorSession(std::move(pooled_session));
PortAllocatorSession* raw_pooled_session =
allocator_sessions_.back().get();
// Process the pooled session's existing candidates/ports, if they exist.
OnCandidatesReady(raw_pooled_session,
raw_pooled_session->ReadyCandidates());
for (PortInterface* port : allocator_sessions_.back()->ReadyPorts()) {
OnPortReady(raw_pooled_session, port);
}
if (allocator_sessions_.back()->CandidatesAllocationDone()) {
OnCandidatesAllocationDone(raw_pooled_session);
}
} else {
// 2.4.1 通过allocator_ 新创建一个session, 链接信号添加到allocator_sessions_ 中
AddAllocatorSession(allocator_->CreateSession(
transport_name(), component(), ice_parameters_.ufrag,
ice_parameters_.pwd));
// 2.4.2 最新的session开始获取端口
allocator_sessions_.back()->StartGettingPorts();
}
}
}
BasicPortAllocatorSession::StartGettingPorts
开始了一个请求STUN的流程,在网络线程中执行,发送MSG_CONFIG_START消息,如下图
异步处理机制,请参考webrtc源码学习 - thread 线程实现
webrtc源码学习 - 3种异步处理
void BasicPortAllocatorSession::StartGettingPorts() {
RTC_DCHECK_RUN_ON(network_thread_);
state_ = SessionState::GATHERING;
if (!socket_factory_) {
owned_socket_factory_.reset(
new rtc::BasicPacketSocketFactory(network_thread_));
socket_factory_ = owned_socket_factory_.get();
}
// this ,表示接收方,也就在network_thread_ 线程中异步执行
network_thread_->Post(RTC_FROM_HERE, this, MSG_CONFIG_START);
RTC_LOG(LS_INFO) << "Start getting ports with prune_turn_ports "
<< (prune_turn_ports_ ? "enabled" : "disabled");
}
//接收消息的回调函数
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();
}
}
阶段流转图, 在MSG_ALLOCATE 之前的2个阶段都是配置相关的处理,我从MSG_ALLOCATE 来开始介绍
Session 为每个网络获取端口
获取全部的本地网络network 为每个网络,根据配置过滤掉不需要获取的网络,创建一个sequeue, 开始获取 地址和端口
// For each network, see if we have a sequence that covers it already. If not,
// create a new sequence to create the appropriate ports.
void BasicPortAllocatorSession::DoAllocate(bool disable_equivalent) {
RTC_DCHECK_RUN_ON(network_thread_);
bool done_signal_needed = false;
// 1. 获取所有的网络, 遍历每个网络创建AllocationSequence
std::vector<rtc::Network*> networks = GetNetworks();
if (networks.empty()) {
RTC_LOG(LS_WARNING)
<< "Machine has no networks; no ports will be allocated";
done_signal_needed = true;
} else {
RTC_LOG(LS_INFO) << "Allocate ports on " << networks.size() << " networks";
PortConfiguration* config = configs_.empty() ? nullptr : configs_.back();
// 开始遍历
for (uint32_t i = 0; i < networks.size(); ++i) {
// 设置flag 根据flag 过滤和判断执行逻辑
uint32_t sequence_flags = flags();
if ((sequence_flags & DISABLE_ALL_PHASES) == DISABLE_ALL_PHASES) {
// If all the ports are disabled we should just fire the allocation
// done event and return.
done_signal_needed = true;
break;
}
if (!config || config->relays.empty()) {
// No relay ports specified in this config.
sequence_flags |= PORTALLOCATOR_DISABLE_RELAY;
}
if (!(sequence_flags & PORTALLOCATOR_ENABLE_IPV6) &&
networks[i]->GetBestIP().family() == AF_INET6) {
// Skip IPv6 networks unless the flag's been set.
continue;
}
if (!(sequence_flags & PORTALLOCATOR_ENABLE_IPV6_ON_WIFI) &&
networks[i]->GetBestIP().family() == AF_INET6 &&
networks[i]->type() == rtc::ADAPTER_TYPE_WIFI) {
// Skip IPv6 Wi-Fi networks unless the flag's been set.
continue;
}
if (disable_equivalent) {
// Disable phases that would only create ports equivalent to
// ones that we have already made.
DisableEquivalentPhases(networks[i], config, &sequence_flags);
if ((sequence_flags & DISABLE_ALL_PHASES) == DISABLE_ALL_PHASES) {
// New AllocationSequence would have nothing to do, so don't make it.
continue;
}
}
// 每个network 创建Sequence, 开始发起请求
AllocationSequence* sequence =
new AllocationSequence(this, networks[i], config, sequence_flags);
sequence->SignalPortAllocationComplete.connect(
this, &BasicPortAllocatorSession::OnPortAllocationComplete);
sequence->Init();
sequence->Start(); // 开获取候选
sequences_.push_back(sequence);
done_signal_needed = true;
}
}
if (done_signal_needed) {
network_thread_->Post(RTC_FROM_HERE, this, MSG_SEQUENCEOBJECTS_CREATED);
}
}
void AllocationSequence::Init() {
// 共享socket 的情况下,先创建本地的udp socket 并且绑定端口
if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET)) {
udp_socket_.reset(session_->socket_factory()->CreateUdpSocket(
rtc::SocketAddress(network_->GetBestIP(), 0),
session_->allocator()->min_port(), session_->allocator()->max_port()));
if (udp_socket_) {
udp_socket_->SignalReadPacket.connect(this,
&AllocationSequence::OnReadPacket);
}
// Continuing if |udp_socket_| is NULL, as local TCP and RelayPort using TCP
// are next available options to setup a communication channel.
}
}
获取中继、UDP、反射地址
AllocationSequence::Start ,只是发送一个信号,也就是切换线程执行,在sessiong 的network中执行。发送的消息会到 AllocationSequence::OnMessage 中接收,之前内容已经介绍过线程处理机制。
void AllocationSequence::Start() {
state_ = kRunning;
session_->network_thread()->Post(RTC_FROM_HERE, this, MSG_ALLOCATION_PHASE);
// Take a snapshot of the best IP, so that when DisableEquivalentPhases is
// called next time, we enable all phases if the best IP has since changed.
previous_best_ip_ = network_->GetBestIP();
}
接下来看下,sequence 的OnMessage处理过程,分成3个阶段,分别获取tcp 、 udp 、中继端口,来获取3类候选项 ,udp 阶段,获取了主机地址和反射地址。
- 主机地址(host) : 通过端设备的网络接口获取
- 反射地址 (srflx):通过和工位的STUN服务器交互获取,多重NAT , 最外层的NAT 地址。
- 中继地址(relay): 如果打洞失败情况,需要TURN 服务器来转发,这个地址就TURN 服务器为这台机器预留的端口,另一个client 可以向这个TURN服务的端口发送媒体数据
void AllocationSequence::OnMessage(rtc::Message* msg) {
RTC_DCHECK(rtc::Thread::Current() == session_->network_thread());
RTC_DCHECK(msg->message_id == MSG_ALLOCATION_PHASE);
const char* const PHASE_NAMES[kNumPhases] = {"Udp", "Relay", "Tcp"};
// Perform all of the phases in the current step.
RTC_LOG(LS_INFO) << network_->ToString()
<< ": Allocation Phase=" << PHASE_NAMES[phase_];
switch (phase_) {
case PHASE_UDP:
CreateUDPPorts();
CreateStunPorts();
break;
case PHASE_RELAY:
CreateRelayPorts();
break;
case PHASE_TCP:
CreateTCPPorts();
state_ = kCompleted;
break;
default:
RTC_NOTREACHED();
}
// 发送当前的状态
if (state() == kRunning) {
++phase_;
session_->network_thread()->PostDelayed(RTC_FROM_HERE,
session_->allocator()->step_delay(),
this, MSG_ALLOCATION_PHASE);
} else {
// If all phases in AllocationSequence are completed, no allocation
// steps needed further. Canceling pending signal.
session_->network_thread()->Clear(this, MSG_ALLOCATION_PHASE);
SignalPortAllocationComplete(this);
}
}
获取主机地址
由于代码比较多,我们本次只分析UDP 端口的创建过程。udp port 就是为了获取主机地址,并且由于在共享socket 的情况下,也会请求STUN 服务。 在共享socket 情况下,CreateStunPorts 是直接返回。
AllocationSequence
每一个本地network 中都有AllocationSequence 对象,用于负责创建port 和 发送STUN 请求和 接收respons
- 调用UDPPort::Create ,创建UDPPort
- 设置STUN 服务器
- session_ 添加端口
void AllocationSequence::CreateUDPPorts() {
if (IsFlagSet(PORTALLOCATOR_DISABLE_UDP)) {
RTC_LOG(LS_VERBOSE) << "AllocationSequence: UDP ports disabled, skipping.";
return;
}
// TODO(mallinath) - Remove UDPPort creating socket after shared socket
// is enabled completely.
// 1. 创建UDPPort
std::unique_ptr<UDPPort> port;
bool emit_local_candidate_for_anyaddress =
!IsFlagSet(PORTALLOCATOR_DISABLE_DEFAULT_LOCAL_CANDIDATE);
if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET) && udp_socket_) {
port = UDPPort::Create(
session_->network_thread(), session_->socket_factory(), network_,
udp_socket_.get(), session_->username(), session_->password(),
session_->allocator()->origin(), emit_local_candidate_for_anyaddress,
session_->allocator()->stun_candidate_keepalive_interval());
} else {
port = UDPPort::Create(
session_->network_thread(), session_->socket_factory(), network_,
session_->allocator()->min_port(), session_->allocator()->max_port(),
session_->username(), session_->password(),
session_->allocator()->origin(), emit_local_candidate_for_anyaddress,
session_->allocator()->stun_candidate_keepalive_interval());
}
if (port) {
// If shared socket is enabled, STUN candidate will be allocated by the
// UDPPort.
// 共享socket 就是,udp 主机端口和 stun 候选项公用本机一个udp 端口
if (IsFlagSet(PORTALLOCATOR_ENABLE_SHARED_SOCKET)) {
udp_port_ = port.get();
port->SignalDestroyed.connect(this, &AllocationSequence::OnPortDestroyed);
// If STUN is not disabled, setting stun server address to port.
// 2. 设置STUN 服务器
if (!IsFlagSet(PORTALLOCATOR_DISABLE_STUN)) {
if (config_ && !config_->StunServers().empty()) {
RTC_LOG(LS_INFO)
<< "AllocationSequence: UDPPort will be handling the "
"STUN candidate generation.";
port->set_server_addresses(config_->StunServers());
}
}
}
// 3. session_ 添加端口
session_->AddAllocatedPort(port.release(), this, true);
}
}
AddAllocatedPort函数
主要有以下几个功能
- 设置端口的一些属性
- 存储port 到 ports_
- 链接信号
- PrepareAddress
void BasicPortAllocatorSession::AddAllocatedPort(Port* port,
AllocationSequence* seq,
bool prepare_address) {
RTC_DCHECK_RUN_ON(network_thread_);
if (!port)
return;
// 1. 设置端口的一些属性
RTC_LOG(LS_INFO) << "Adding allocated port for " << content_name();
port->set_content_name(content_name());
port->set_component(component());
port->set_generation(generation());
if (allocator_->proxy().type != rtc::PROXY_NONE)
port->set_proxy(allocator_->user_agent(), allocator_->proxy());
port->set_send_retransmit_count_attribute(
(flags() & PORTALLOCATOR_ENABLE_STUN_RETRANSMIT_ATTRIBUTE) != 0);
// 2. 存储port
PortData data(port, seq);
ports_.push_back(data);
// 3. 链接信号
port->SignalCandidateReady.connect(
this, &BasicPortAllocatorSession::OnCandidateReady);
port->SignalPortComplete.connect(this,
&BasicPortAllocatorSession::OnPortComplete);
port->SignalDestroyed.connect(this,
&BasicPortAllocatorSession::OnPortDestroyed);
port->SignalPortError.connect(this, &BasicPortAllocatorSession::OnPortError);
RTC_LOG(LS_INFO) << port->ToString() << ": Added port to allocator";
// 4. 获得其他客户端可以直接发送的地址,也就获取候选
if (prepare_address)
port->PrepareAddress();
}
PrepareAddress函数
获得其他客户端可以直接发送的地址, 将花费一些时间在地址已经ready 的情况,如果已经ready , 就会发送SignalAddressReady,如果发生错误,停止获取地址,发送SignalAddressError
PortInterface {
virtual void PrepareAddress() = 0;
}
// udp 的实现
void UDPPort::PrepareAddress() {
RTC_DCHECK(requests_.empty());
if (socket_->GetState() == rtc::AsyncPacketSocket::STATE_BOUND) {
// 此处的socket 是 sequence 中的 udp socket, 并且已经bind
OnLocalAddressReady(socket_, socket_->GetLocalAddress());
}
}
void UDPPort::OnLocalAddressReady(rtc::AsyncPacketSocket* socket,
const rtc::SocketAddress& address) {
// When adapter enumeration is disabled and binding to the any address, the
// default local address will be issued as a candidate instead if
// |emit_local_for_anyaddress| is true. This is to allow connectivity for
// applications which absolutely requires a HOST candidate.
rtc::SocketAddress addr = address;
// If MaybeSetDefaultLocalAddress fails, we keep the "any" IP so that at
// least the port is listening.
MaybeSetDefaultLocalAddress(&addr);
// 由于主机ip,都是已知的,所以设置参数,直接构建候选项就可以了,参数ICE_TYPE_PREFERENCE_HOST 表示主机地址(host address)
AddAddress(addr, addr, rtc::SocketAddress(), UDP_PROTOCOL_NAME, "", "",
LOCAL_PORT_TYPE, ICE_TYPE_PREFERENCE_HOST, 0, "", false);
// 共享socket 设置的情况下,有可能需要获取stun 候选
MaybePrepareStunCandidate();
}
获取反射地址
刚才提到了,如果共享模式会复用socket 请求STUN 服务来获取反射地址
- udpPort 中如果设置了 stun 服务的地址,就请求服务,获取反射地址也就是STUN 候选
- 遍历所有的服务器,发送请求
void UDPPort::MaybePrepareStunCandidate() {
// Sending binding request to the STUN server if address is available to
// prepare STUN candidate.
// 如果设置了 stun 服务的地址,就请求服务,获取反射地址也就是STUN 候选
if (!server_addresses_.empty()) {
SendStunBindingRequests();
} else {
// Port is done allocating candidates.
MaybeSetPortCompleteOrError();
}
}
void UDPPort::SendStunBindingRequests() {
// We will keep pinging the stun server to make sure our NAT pin-hole stays
// open until the deadline (specified in SendStunBindingRequest).
RTC_DCHECK(requests_.empty());
// 遍历所有的服务器
for (ServerAddresses::const_iterator it = server_addresses_.begin();
it != server_addresses_.end(); ++it) {
SendStunBindingRequest(*it);
}
UDPPort::SendStunBindingRequest
- 构建请求 STUN 服务的数据
- 发送请求,StunBindingRequest 存储了请求数据
- requests_ 是StunRequestManager, 用于管理发送的请求,如果请求数据回来,可以根据key 找到对应的请求。
- StunRequestManager负责发送数据,StunBindingRequest 负责构建发送stun 请求的数据
void UDPPort::SendStunBindingRequest(const rtc::SocketAddress& stun_addr) {
// 地址是否已经通过dns 解析,如果没有解析先解析地址
if (stun_addr.IsUnresolvedIP()) {
ResolveStunAddress(stun_addr);
} else if (socket_->GetState() == rtc::AsyncPacketSocket::STATE_BOUND) {
// Check if |server_addr_| is compatible with the port's ip.
if (IsCompatibleAddress(stun_addr)) {
// 发送请求,StunBindingRequest
requests_.Send(
new StunBindingRequest(this, stun_addr, rtc::TimeMillis()));
} else {
// Since we can't send stun messages to the server, we should mark this
// port ready.
RTC_LOG(LS_WARNING) << "STUN server address is incompatible.";
OnStunBindingOrResolveRequestFailed(stun_addr);
}
}
}
发送STUN 数据
- 信号绑定了,requests_.SignalSendPacket.connect(this, &UDPPort::OnSendPacket); requests_ 中发送数据就是调用了信号SignalSendPacket
- 最终回调到UDPPort 中,调用socket 发送数据
- 底层的发送逻辑就不在展示
// 构建stun 请求数据,调用发送信号
void StunRequest::OnMessage(rtc::Message* pmsg) {
RTC_DCHECK(manager_ != NULL);
RTC_DCHECK(pmsg->message_id == MSG_STUN_SEND);
if (timeout_) {
OnTimeout();
delete this;
return;
}
tstamp_ = rtc::TimeMillis();
rtc::ByteBufferWriter buf;
msg_->Write(&buf);
// manager_ 就是 requests_StunRequestManager
manager_->SignalSendPacket(buf.Data(), buf.Length(), this);
OnSent();
manager_->thread_->PostDelayed(RTC_FROM_HERE, resend_delay(), this,
MSG_STUN_SEND, NULL);
}
void UDPPort::OnSendPacket(const void* data, size_t size, StunRequest* req) {
StunBindingRequest* sreq = static_cast<StunBindingRequest*>(req);
rtc::PacketOptions options(StunDscpValue());
options.info_signaled_after_sent.packet_type = rtc::PacketType::kStunMessage;
CopyPortInformationToPacketInfo(&options.info_signaled_after_sent);
// 已经绑定好的udp socket 发送数据
if (socket_->SendTo(data, size, sreq->server_addr(), options) < 0) {
RTC_LOG_ERR_EX(LERROR, socket_->GetError()) << "sendto";
}
stats_.stun_binding_requests_sent++;
}
接收STUN 消息
- STUN 服务返回的消息,调用 requests_.CheckResponse(data, size);
- 创建StunMessage 解析STUN 返回的消息
- 在Request中处理回复消息,获取到服务,ip 和 端口 返回给UDPPort
// AllocationSequence::Init 中绑定的信号接收数据的槽函数
if (udp_socket_) {
udp_socket_->SignalReadPacket.connect(this,
&AllocationSequence::OnReadPacket);
}
void AllocationSequence::OnReadPacket(rtc::AsyncPacketSocket* socket,
const char* data,
size_t size,
const rtc::SocketAddress& remote_addr,
const int64_t& packet_time_us) {
RTC_DCHECK(socket == udp_socket_.get());
bool turn_port_found = false;
// Try to find the TurnPort that matches the remote address. Note that the
// message could be a STUN binding response if the TURN server is also used as
// a STUN server. We don't want to parse every message here to check if it is
// a STUN binding response, so we pass the message to TurnPort regardless of
// the message type. The TurnPort will just ignore the message since it will
// not find any request by transaction ID.
for (auto* port : relay_ports_) {
if (port->CanHandleIncomingPacketsFrom(remote_addr)) {
if (port->HandleIncomingPacket(socket, data, size, remote_addr,
packet_time_us)) {
return;
}
turn_port_found = true;
}
}
// 先看udp 的处理
if (udp_port_) {
const ServerAddresses& stun_servers = udp_port_->server_addresses();
// Pass the packet to the UdpPort if there is no matching TurnPort, or if
// the TURN server is also a STUN server.
// 转发给 udp port
if (!turn_port_found ||
stun_servers.find(remote_addr) != stun_servers.end()) {
RTC_DCHECK(udp_port_->SharedSocket());
udp_port_->HandleIncomingPacket(socket, data, size, remote_addr,
packet_time_us);
}
}
}
UDPPort::OnReadPacket
这个阶段内容都非常简单,只是函数调用比较多,requests_ 负责解析stun 消息,最终会将结果返回到 UDPPort::OnStunBindingRequestSucceeded中。
- 从STUN 返回的数据,最终在UDPPort::OnReadPacket 进行处理
- 在requests_请求管理中,根据返回的数据,查找请求request, 然后进行处理requests_.CheckResponse(data, size);
- 使用StunMessage 解析 STUN message
- 最终调用,请求的OnResponse 中处理,也就是说需要在依赖请求的状态,解析到获取 反射的ip 和 端口
- 返回数据到 udp port
bool UDPPort::HandleIncomingPacket(rtc::AsyncPacketSocket* socket,
const char* data,
size_t size,
const rtc::SocketAddress& remote_addr,
int64_t packet_time_us) {
// All packets given to UDP port will be consumed.
OnReadPacket(socket, data, size, remote_addr, packet_time_us);
return true;
}
void UDPPort::OnReadPacket(rtc::AsyncPacketSocket* socket,
const char* data,
size_t size,
const rtc::SocketAddress& remote_addr,
const int64_t& packet_time_us) {
RTC_DCHECK(socket == socket_);
RTC_DCHECK(!remote_addr.IsUnresolvedIP());
// Look for a response from the STUN server.
// Even if the response doesn't match one of our outstanding requests, we
// will eat it because it might be a response to a retransmitted packet, and
// we already cleared the request when we got the first response.
// 1. 处理从STUN 服务器返回的消息
if (server_addresses_.find(remote_addr) != server_addresses_.end()) {
// 在请求管理中,根据返回的数据,查找请求request, 然后进行处理
requests_.CheckResponse(data, size);
return;
}
if (Connection* conn = GetConnection(remote_addr)) {
conn->OnReadPacket(data, size, packet_time_us);
} else {
Port::OnReadPacket(data, size, remote_addr, PROTO_UDP);
}
}
构建ID , 获取发送请求的消息id
根据ID找到对应的请求
创建StunMessage 解析STUN 返回的消息
bool StunRequestManager::CheckResponse(const char* data, size_t size) {
// Check the appropriate bytes of the stream to see if they match the
// transaction ID of a response we are expecting.
if (size < 20)
return false;
std::string id;
id.append(data + kStunTransactionIdOffset, kStunTransactionIdLength);
RequestMap::iterator iter = requests_.find(id);
if (iter == requests_.end()) {
// TODO(pthatcher): Log unknown responses without being too spammy
// in the logs.
return false;
}
// Parse the STUN message and continue processing as usual.
// 使用StunMessage 解析 STUN message
rtc::ByteBufferReader buf(data, size);
std::unique_ptr<StunMessage> response(iter->second->msg_->CreateNew());
if (!response->Read(&buf)) {
RTC_LOG(LS_WARNING) << "Failed to read STUN response "
<< rtc::hex_encode(id);
return false;
}
return CheckResponse(response.get());
}
bool StunRequestManager::CheckResponse(StunMessage* msg) {
RequestMap::iterator iter = requests_.find(msg->transaction_id());
if (iter == requests_.end()) {
// TODO(pthatcher): Log unknown responses without being too spammy
// in the logs.
return false;
}
StunRequest* request = iter->second;
if (msg->type() == GetStunSuccessResponseType(request->type())) {
request->OnResponse(msg);
} else if (msg->type() == GetStunErrorResponseType(request->type())) {
request->OnErrorResponse(msg);
} else {
RTC_LOG(LERROR) << "Received response with wrong type: " << msg->type()
<< " (expecting "
<< GetStunSuccessResponseType(request->type()) << ")";
return false;
}
delete request;
return true;
}
在Request中处理回复消息
class StunBindingRequest{
void OnResponse(StunMessage* response) override {
const StunAddressAttribute* addr_attr =
response->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
if (!addr_attr) {
RTC_LOG(LS_ERROR) << "Binding response missing mapped address.";
} else if (addr_attr->family() != STUN_ADDRESS_IPV4 &&
addr_attr->family() != STUN_ADDRESS_IPV6) {
RTC_LOG(LS_ERROR) << "Binding address has bad family";
} else {
// 1. 获取到服务,ip 和 端口
rtc::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port());
// 返回数据到 udp port
port_->OnStunBindingRequestSucceeded(this->Elapsed(), server_addr_, addr);
}
// The keep-alive requests will be stopped after its lifetime has passed.
// 2. 避免洞消失,进行保活
if (WithinLifetime(rtc::TimeMillis())) {
port_->requests_.SendDelayed(
new StunBindingRequest(port_, server_addr_, start_time_),
port_->stun_keepalive_delay());
}
}
}
UDPPort::OnStunBindingRequestSucceeded
打印返回的反射ip和端口,这是我本机执行结果如下
- 统计数据和保存已经请求成功的stun 服务的地址
- 如果socket 是共享并且反射地址和本地地址相同,或者 如果相同的地址被添加到其他的STUN服务, 就是丢弃stun地址
- 添加Udp 的反射地址、本地地址、相对地址
void UDPPort::OnStunBindingRequestSucceeded(
int rtt_ms,
const rtc::SocketAddress& stun_server_addr,
const rtc::SocketAddress& stun_reflected_addr) {
RTC_DCHECK(stats_.stun_binding_responses_received <
stats_.stun_binding_requests_sent);
// 1. 统计数据和保存已经请求成功的stun 服务的地址
stats_.stun_binding_responses_received++;
stats_.stun_binding_rtt_ms_total += rtt_ms;
stats_.stun_binding_rtt_ms_squared_total += rtt_ms * rtt_ms;
if (bind_request_succeeded_servers_.find(stun_server_addr) !=
bind_request_succeeded_servers_.end()) {
return;
}
bind_request_succeeded_servers_.insert(stun_server_addr);
// If socket is shared and |stun_reflected_addr| is equal to local socket
// address, or if the same address has been added by another STUN server,
// then discarding the stun address.
// For STUN, related address is the local socket address.
// 翻译上面的话,如果socket 是共享并且反射地址和本地地址相同,或者 如果相同的地址被添加到其他的STUN服务,
// 就是丢弃stun地址,下面是处理相反的条件,所以正好取反。
if ((!SharedSocket() || stun_reflected_addr != socket_->GetLocalAddress()) &&
!HasCandidateWithAddress(stun_reflected_addr)) {
rtc::SocketAddress related_address = socket_->GetLocalAddress();
// If we can't stamp the related address correctly, empty it to avoid leak.
if (!MaybeSetDefaultLocalAddress(&related_address)) {
related_address =
rtc::EmptySocketAddressWithFamily(related_address.family());
}
rtc::StringBuilder url;
url << "stun:" << stun_server_addr.ipaddr().ToString() << ":"
<< stun_server_addr.port();
// 打印日志,和上图对应
RTC_LOG(LS_INFO) << stun_reflected_addr.ToString();
RTC_LOG(LS_INFO) << "stun:" << stun_server_addr.ipaddr().ToString() << ":"
<< stun_server_addr.port();
// 2. 添加存储Udp 反射地址,也就类型,ICE_TYPE_PREFERENCE_SRFLX
AddAddress(stun_reflected_addr, socket_->GetLocalAddress(), related_address,
UDP_PROTOCOL_NAME, "", "", STUN_PORT_TYPE,
ICE_TYPE_PREFERENCE_SRFLX, 0, url.str(), false);
}
MaybeSetPortCompleteOrError();
}
后续的存储候选过程比较简单,大家可以自行查看。蓝色虚线就返回候选项的路径。最终到达Conductor::OnIceCandidate,然后通过信令通道发送给对方。
void Conductor::OnIceCandidate(const webrtc::IceCandidateInterface* candidate) {
RTC_LOG(INFO) << __FUNCTION__ << " " << candidate->sdp_mline_index();
// For loopback test. To save some connecting delay.
if (loopback_) {
if (!peer_connection_->AddIceCandidate(candidate)) {
RTC_LOG(WARNING) << "Failed to apply the received candidate";
}
return;
}
Json::StyledWriter writer;
Json::Value jmessage;
jmessage[kCandidateSdpMidName] = candidate->sdp_mid();
jmessage[kCandidateSdpMlineIndexName] = candidate->sdp_mline_index();
std::string sdp;
if (!candidate->ToString(&sdp)) {
RTC_LOG(LS_ERROR) << "Failed to serialize candidate";
return;
}
jmessage[kCandidateSdpName] = sdp;
SendMessage(writer.write(jmessage));
}
总结
我们主要分成 收集候选启动,获取主机地址,获取反射地址进行代码的分析。我们在来看一下其中几个类的作用
- StunRequestManager 请求管理类,为了解决发出去的请求,在接收到响应的时候找到对应的请求进行处理
- UDPPort , 负责主机地址、和反射地址(共享socket 模式下)的候选获取
- AllocationSequence,每一个网络都会有一个sequence, 负责获取 主机地址、反射地址、中继映射、Tcp 映射。里面调用UDPPort 等来获取具体反射地址。
- BasicPortAllocatorSession:负责在一个而会话中,根据配置来获取候选项, session 中创建每个网络的 AllocationSequence,也就是一个Session 中会保存所有网络接口的信息
- PortAllocator: 负责创建BasicPortAllocatorSession
- JsepTransportController : 是整个数据传输的控制类