webrtc-m79-peerconnection_client-收集 sflx candidate

收集 srflx candidate 的原理是,向 STUN server 发送一个 UDP 包(叫 STUN Binding request),server 会把这个包里的源 IP 地址、UDP 端口返回给客户端(叫 STUN Binding response),这个 IP 和端口将来可能可以用来和其他客户端建立 P2P 连接。关于 STUN 协议的具体内容,可以查阅 RFC Session Traversal Utilities for NAT (STUN)

收集 srflx candicate 时可以复用收集 host candidate 时创建的 socket 对象,这一逻辑通过 PORTALLOCATOR_ENABLE_SHARED_SOCKET flag 控制,默认是开启的。

复用 socket 的情况下,AllocationSequence::CreateStunPorts 函数会直接返回,因为早在 AllocationSequence::CreateUDPPorts 函数的执行过程中,就已经执行了 STUN Binding request 的发送逻辑。

 

///
收集 sflx candidate 流程

/// 前面是有发送 binding request 的流程的
===> SocketDispatcher::OnEvent(uint32_t ff, int err) ===> SignalReadEvent(this);
===> AsyncUDPSocket::OnReadEvent(AsyncSocket* socket)  ===> SignalReadPacket(this, buf_, static_cast<size_t>(len), remote_addr,
                                                                                                           (timestamp > -1 ? timestamp : TimeMicros()));
===> AllocationSequence::OnReadPacket(rtc::AsyncPacketSocket* socket,
                                      const char* data,
                                      size_t size,
                                      const rtc::SocketAddress& remote_addr,
                                      const int64_t& packet_time_us)        ===> udp_port_->HandleIncomingPacket(socket, data, size, remote_addr,
                                                                                                                                                      packet_time_us);
===> UDPPort::HandleIncomingPacket(rtc::AsyncPacketSocket* socket,
                                   const char* data,
                                   size_t size,
                                   const rtc::SocketAddress& remote_addr,
                                   int64_t packet_time_us)                ===> OnReadPacket(socket, data, size, remote_addr, packet_time_us);
                                   
===> UDPPort::OnReadPacket(rtc::AsyncPacketSocket* socket,
                           const char* data,
                           size_t size,
                           const rtc::SocketAddress& remote_addr,
                           const int64_t& packet_time_us)         ===> requests_.CheckResponse(data, size);
                           
===> StunRequestManager::CheckResponse(const char* data, size_t size) ===> return CheckResponse(response.get());
===> StunRequestManager::CheckResponse(StunMessage* msg)===> request->OnResponse(msg); 
===> StunBindingRequest::OnResponse(StunMessage* response) override ===> port_->OnStunBindingRequestSucceeded(this->Elapsed(), server_addr_, addr);
===> UDPPort::OnStunBindingRequestSucceeded ===>    AddAddress(stun_reflected_addr, socket_->GetLocalAddress(), related_address,
                                                                                           UDP_PROTOCOL_NAME, "", "", STUN_PORT_TYPE,
                                                                                           ICE_TYPE_PREFERENCE_SRFLX, 0, url.str(), false); / is_final 又是 false 
===> Port::AddAddress ===> FinishAddingAddress(c, is_final);
===> Port::FinishAddingAddress ===> SignalCandidateReady(this, c);
===> BasicPortAllocatorSession::OnCandidateReady ===> SignalCandidatesReady(this, candidates);
===> P2PTransportChannel::OnCandidatesReady ===> SignalCandidateGathered(this, candidates[i]);
===> JsepTransportController::OnTransportCandidateGathered_n ===> SignalIceCandidatesGathered(transport_name, {candidate});
===> PeerConnection::OnTransportControllerCandidatesGathered ===> OnIceCandidate(std::move(candidate)); 
===> PeerConnection::OnIceCandidate ===> Observer()->OnIceCandidate(candidate.get());
===> Conductor::OnIceCandidate ===> SendMessage(writer.write(jmessage));

 

 

具体代码调用逻辑如下:


  发送  STUN Binding request

UDPPort::OnLocalAddressReady 上面已经分析过
===>
MaybePrepareStunCandidate();

void UDPPort::MaybePrepareStunCandidate() {
  // Sending binding request to the STUN server if address is available to
  // prepare STUN candidate.
  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); //
  }
}


void UDPPort::SendStunBindingRequest(const rtc::SocketAddress& stun_addr) {
  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)) {
      requests_.Send(  class StunBindingRequest : public StunRequest
          new StunBindingRequest(this, stun_addr, rtc::TimeMillis()));  StunBindingRequest 中保存 UDPPort 的 this 指针到 port_ 成变中
    } else {
      // Since we can't send stun messages to the server, we should mark this
      // port ready.
      const char* reason = "STUN server address is incompatible.";
      RTC_LOG(LS_WARNING) << reason;
      OnStunBindingOrResolveRequestFailed(stun_addr, SERVER_NOT_REACHABLE_ERROR,
                                          reason);
    }
  }
}


void StunRequestManager::Send(StunRequest* request) {
  SendDelayed(request, 0); ///
}


void StunRequestManager::SendDelayed(StunRequest* request, int delay) {
  request->set_manager(this); ///
  RTC_DCHECK(requests_.find(request->id()) == requests_.end());
  request->set_origin(origin_);
  request->Construct();
  requests_[request->id()] = request; / 将 class StunBindingRequest : public StunRequest 原始消息进行了保存
  if (delay > 0) {
    thread_->PostDelayed(RTC_FROM_HERE, delay, request, MSG_STUN_SEND, NULL);
  } else {
    thread_->Send(RTC_FROM_HERE, request, MSG_STUN_SEND, NULL); ///
  }
}


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_->SignalSendPacket(buf.Data(), buf.Length(), this); / StunRequestManager->SignalSendPacket , 具体见 UDPPort::Init

  OnSent();
  manager_->thread_->PostDelayed(RTC_FROM_HERE, resend_delay(), this,
                                 MSG_STUN_SEND, NULL);
}


                                                bool UDPPort::Init() {
                                                  stun_keepalive_lifetime_ = GetStunKeepaliveLifetime();
                                                  if (!SharedSocket()) {
                                                    RTC_DCHECK(socket_ == nullptr);
                                                    socket_ = socket_factory()->CreateUdpSocket(
                                                        rtc::SocketAddress(Network()->GetBestIP(), 0), min_port(), max_port());
                                                    if (!socket_) {
                                                      RTC_LOG(LS_WARNING) << ToString() << ": UDP socket creation failed";
                                                      return false;
                                                    }
                                                    socket_->SignalReadPacket.connect(this, &UDPPort::OnReadPacket);
                                                  }
                                                  socket_->SignalSentPacket.connect(this, &UDPPort::OnSentPacket);
                                                  socket_->SignalReadyToSend.connect(this, &UDPPort::OnReadyToSend);
                                                  socket_->SignalAddressReady.connect(this, &UDPPort::OnLocalAddressReady);
                                                  requests_.SignalSendPacket.connect(this, &UDPPort::OnSendPacket);  StunRequestManager requests_;
                                                  return true;
                                                }


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);
  if (socket_->SendTo(data, size, sreq->server_addr(), options) < 0) { // AsyncUDPSocket::SendTo 发往 stun server
    RTC_LOG_ERR_EX(LERROR, socket_->GetError()) << "sendto";
  }
  stats_.stun_binding_requests_sent++;
}


int AsyncUDPSocket::SendTo(const void* pv,
                           size_t cb,
                           const SocketAddress& addr,
                           const rtc::PacketOptions& options) {
  rtc::SentPacket sent_packet(options.packet_id, rtc::TimeMillis(),
                              options.info_signaled_after_sent);
  CopySocketInformationToPacketInfo(cb, *this, true, &sent_packet.info);  class SocketDispatcher : public Dispatcher, public PhysicalSocket
  int ret = socket_->SendTo(pv, cb, addr); / socket_ 实际上就是 SocketDispatcher 的指针,具体见  BasicPacketSocketFactory::CreateUdpSocket
  SignalSentPacket(this, sent_packet); / SocketDispatcher::SendTo 就是  PhysicalSocket::SendTo
  return ret; / SignalSentPacket 来自 AsyncPacketSocket , 因为 class AsyncUDPSocket : public AsyncPacketSocket
}  / 这里 SignalSentPacket 就会触发 UDPPort::OnSentPacket (见下面分析) , 具体见 UDPPort::Init


                                                        AsyncUDPSocket::AsyncUDPSocket(AsyncSocket* socket) : socket_(socket) {
                                                          size_ = BUF_SIZE;
                                                          buf_ = new char[size_];
                                                        
                                                          // The socket should start out readable but not writable.
                                                          socket_->SignalReadEvent.connect(this, &AsyncUDPSocket::OnReadEvent);
                                                          socket_->SignalWriteEvent.connect(this, &AsyncUDPSocket::OnWriteEvent);
                                                        }

int PhysicalSocket::SendTo(const void* buffer,
                           size_t length,
                           const SocketAddress& addr) {
  sockaddr_storage saddr;
  size_t len = addr.ToSockAddrStorage(&saddr);
  int sent =
      DoSendTo(s_, static_cast<const char*>(buffer), static_cast<int>(length), 
#if defined(WEBRTC_LINUX) && !defined(WEBRTC_ANDROID)
               // Suppress SIGPIPE. See above for explanation.
               MSG_NOSIGNAL,
#else
               0,
#endif
               reinterpret_cast<sockaddr*>(&saddr), static_cast<int>(len));
  UpdateLastError();
  MaybeRemapSendError();
  // We have seen minidumps where this may be false.
  RTC_DCHECK(sent <= static_cast<int>(length));
  if ((sent > 0 && sent < static_cast<int>(length)) ||
      (sent < 0 && IsBlockingError(GetError()))) {
    EnableEvents(DE_WRITE);
  }
  return sent;
}


int PhysicalSocket::DoSendTo(SOCKET socket,
                             const char* buf,
                             int len,
                             int flags,
                             const struct sockaddr* dest_addr,
                             socklen_t addrlen) {
  return ::sendto(socket, buf, len, flags, dest_addr, addrlen); //
}



  接收 STUN Binding response

 事件循环参考前面描述,这里直接借用前面分析的结果

void SocketDispatcher::OnEvent(uint32_t ff, int err) {
#if defined(WEBRTC_USE_EPOLL)
  // Remember currently enabled events so we can combine multiple changes
  // into one update call later.
  // The signal handlers might re-enable events disabled here, so we can't
  // keep a list of events to disable at the end of the method. This list
  // would not be updated with the events enabled by the signal handlers.
  StartBatchedEventUpdates();
#endif
  // Make sure we deliver connect/accept first. Otherwise, consumers may see
  // something like a READ followed by a CONNECT, which would be odd.
  if ((ff & DE_CONNECT) != 0) {
    DisableEvents(DE_CONNECT);
    SignalConnectEvent(this); 
                                                                                                                                                                                      
  }
  if ((ff & DE_ACCEPT) != 0) {
    DisableEvents(DE_ACCEPT);
    SignalReadEvent(this); 
  } 
  if ((ff & DE_READ) != 0) {
    DisableEvents(DE_READ);
    SignalReadEvent(this);// 在 BasicPacketSocketFactory::CreateUdpSocket 中将 SocketDispatcher 的指针传递到了 AsyncUDPSocket ,
  }// 并在 AsyncUDPSocket 的构造函数中设置了 SocketDispatcher 的 SignalReadEvent 和 SignalWriteEvent ,分别将其绑定到了 AsyncUDPSocket::OnReadEvent AsyncUDPSocket::OnWriteEvent
  if ((ff & DE_WRITE) != 0) {
    DisableEvents(DE_WRITE);
    SignalWriteEvent(this);
  }
  if ((ff & DE_CLOSE) != 0) {
    // The socket is now dead to us, so stop checking it.
    SetEnabledEvents(0);
    SignalCloseEvent(this, err);
  }
#if defined(WEBRTC_USE_EPOLL)
  FinishBatchedEventUpdates();
#endif
}


void AsyncUDPSocket::OnReadEvent(AsyncSocket* socket) {
  RTC_DCHECK(socket_.get() == socket);

  SocketAddress remote_addr;
  int64_t timestamp;
  int len = socket_->RecvFrom(buf_, size_, &remote_addr, &timestamp); /
  if (len < 0) {
    // An error here typically means we got an ICMP error in response to our
    // send datagram, indicating the remote address was unreachable.
    // When doing ICE, this kind of thing will often happen.
    // TODO: Do something better like forwarding the error to the user.
    SocketAddress local_addr = socket_->GetLocalAddress();
    RTC_LOG(LS_INFO) << "AsyncUDPSocket[" << local_addr.ToSensitiveString()
                     << "] receive failed with error " << socket_->GetError();
    return;
  }

  // TODO: Make sure that we got all of the packet.
  // If we did not, then we should resize our buffer to be large enough.
  SignalReadPacket(this, buf_, static_cast<size_t>(len), remote_addr,
                   (timestamp > -1 ? timestamp : TimeMicros()));  AsyncUDPSocket 的 SignalReadPacket 事件来自其父类 AsyncPacketSocket  
} // AsyncPacketSocket 的 SignalReadPacket 事件是在 AllocationSequence::Init 中设置,将其绑定到了 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());  udp_socket_ 是 AsyncPacketSocket 指针,且该指针指向 AsyncUDPSocket

  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;
    }
  }

  if (udp_port_) {  udp_port_ 是 UDPPort 指针(UDPPort内部包含 rtc::AsyncPacketSocket 指针,实际上指向 AsyncUDPSocket)
    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.
    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::HandleIncomingPacket
    }
  }
}


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.
  if (server_addresses_.find(remote_addr) != server_addresses_.end()) {
    requests_.CheckResponse(data, size);  StunRequestManager::CheckResponse
    return;
  }

  if (Connection* conn = GetConnection(remote_addr)) {
    conn->OnReadPacket(data, size, packet_time_us);
  } else {
    Port::OnReadPacket(data, size, remote_addr, PROTO_UDP);
  }
}


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.

  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()) { // requests_ 原始消息的保存参考见 StunRequestManager::SendDelayed 
    // TODO(pthatcher): Log unknown responses without being too spammy
    // in the logs.
    return false;
  }

  StunRequest* request = iter->second;  requests_ 中实际上保存的是 StunBindingRequest  class StunBindingRequest : public StunRequest
  if (!msg->GetNonComprehendedAttributes().empty()) {
    // If a response contains unknown comprehension-required attributes, it's
    // simply discarded and the transaction is considered failed. See RFC5389
    // sections 7.3.3 and 7.3.4.
    RTC_LOG(LS_ERROR) << ": Discarding response due to unknown "
                         "comprehension-required attribute.";
    delete request;
    return false;
  } else if (msg->type() == GetStunSuccessResponseType(request->type())) {
    request->OnResponse(msg);  StunBindingRequest::OnResponse 直接在 StunBindingRequest 声明中定义
  } 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;
}

void StunBindingRequest::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 {
    rtc::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port()); /// port_ 是 UDPPort 指针,具体参考上面的 UDPPort::SendStunBindingRequest
    port_->OnStunBindingRequestSucceeded(this->Elapsed(), server_addr_, addr); // UDPPort::OnStunBindingRequestSucceeded
  }

  // The keep-alive requests will be stopped after its lifetime has passed.
  if (WithinLifetime(rtc::TimeMillis())) {
    port_->requests_.SendDelayed(
        new StunBindingRequest(port_, server_addr_, start_time_),
        port_->stun_keepalive_delay());  
  }
}

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);
  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.
  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();
    AddAddress(stun_reflected_addr, socket_->GetLocalAddress(), related_address,
               UDP_PROTOCOL_NAME, "", "", STUN_PORT_TYPE,
               ICE_TYPE_PREFERENCE_SRFLX, 0, url.str(), false); / is_final 又是 false 
  }
  MaybeSetPortCompleteOrError();
}

// 前面收集 host candidate 的时候也调用了 Port::AddAddress 具体参考前面 
void Port::AddAddress(const rtc::SocketAddress& address,
                      const rtc::SocketAddress& base_address,
                      const rtc::SocketAddress& related_address,
                      const std::string& protocol,
                      const std::string& relay_protocol,
                      const std::string& tcptype,
                      const std::string& type,
                      uint32_t type_preference,
                      uint32_t relay_preference,
                      const std::string& url,
                      bool is_final) {
  if (protocol == TCP_PROTOCOL_NAME && type == LOCAL_PORT_TYPE) {
    RTC_DCHECK(!tcptype.empty());
  }

  std::string foundation =
      ComputeFoundation(type, protocol, relay_protocol, base_address);
  Candidate c(component_, protocol, address, 0U, username_fragment(), password_, / 创建 candidate 
              type, generation_, foundation, network_->id(), network_cost_);
  c.set_priority(
      c.GetPriority(type_preference, network_->preference(), relay_preference));
  c.set_relay_protocol(relay_protocol);
  c.set_tcptype(tcptype);
  c.set_network_name(network_->name());
  c.set_network_type(network_->type());
  c.set_url(url);
  c.set_related_address(related_address);

  bool pending = MaybeObfuscateAddress(&c, type, is_final);

  if (!pending) {
    FinishAddingAddress(c, is_final);  
  }
}

void Port::FinishAddingAddress(const Candidate& c, bool is_final) {
  candidates_.push_back(c);
  SignalCandidateReady(this, c); / 触发  BasicPortAllocatorSession::OnCandidateReady 具体参考前面
  /// 注意这里的 this 是 Port 的指针,而实际上指向的是 UDPPort (收集 sflx candidate 使用的就是 UDPPort)
    /// 这里触发调用 BasicPortAllocatorSession::OnCandidateReady , 此函数内部又触发  SignalPortReady(this, port);
    /// 从而再次引发槽函数 P2PTransportChannel::OnPortReady 的调用,执行 ports_.push_back(port) ,从而将指向 TurnPort 的 Port 
    /// 指针保存到 ports_ ,所以在 P2PTransportChannel::CreateConnection 中调用 port->CreateConnection 创建 connection 时
     会调用 UDPPort::CreateConnection 或者 TurnPort::CreateConnection 
  PostAddAddress(is_final);
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值