webrtc源码分析-ICE交互链接建立(代码实现)

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 : 是整个数据传输的控制类
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值