美文网首页
WebRTC 源码分析 三 连通性检查与提名

WebRTC 源码分析 三 连通性检查与提名

作者: iBOBO | 来源:发表于2021-05-24 11:53 被阅读0次
    • 本文基于 WebRTC M89。

    连通性检查

    WebRTC 有三处会触发连通性检查:

    • 收集到本地 Candidate,触发 OnCandidateReady
    • 接收到 Remote Candidate,触发 PeerConnection::AddIceCandidate
    • 接收到对端 Stun ping request
    连通性检查

    这三种情况下都会触发 P2PTransportChannel::SortConnectionsAndUpdateState ,这个方法里主要会做三件事,见注释。

    void P2PTransportChannel::SortConnectionsAndUpdateState(
        IceControllerEvent reason_to_sort) {
      RTC_DCHECK_RUN_ON(network_thread_);
    
      // Make sure the connection states are up-to-date since this affects how they
      // will be sorted.
      UpdateConnectionStates();
    
      // Any changes after this point will require a re-sort.
      sort_dirty_ = false;
    
      // If necessary, switch to the new choice. Note that |top_connection| doesn't
      // have to be writable to become the selected connection although it will
      // have higher priority if it is writable.
      // 1、排序,如果可以选择新的 connection
      MaybeSwitchSelectedConnection(
          reason_to_sort, ice_controller_->SortAndSwitchConnection(reason_to_sort));
    
      // The controlled side can prune only if the selected connection has been
      // nominated because otherwise it may prune the connection that will be
      // selected by the controlling side.
        // 2、修剪
      if (ice_role_ == ICEROLE_CONTROLLING ||
          (selected_connection_ && selected_connection_->nominated())) {
        PruneConnections();
      }
    
      // Check if all connections are timedout.
      bool all_connections_timedout = true;
      for (const Connection* conn : connections()) {
        if (conn->write_state() != Connection::STATE_WRITE_TIMEOUT) {
          all_connections_timedout = false;
          break;
        }
      }
    
      // Now update the writable state of the channel with the information we have
      // so far.
      if (all_connections_timedout) {
        HandleAllTimedOut();
      }
    
      // Update the state of this channel.
      UpdateState();
    
      // Also possibly start pinging.
      // We could start pinging if:
      // * The first connection was created.
      // * ICE credentials were provided.
      // * A TCP connection became connected.
        // 3、ping 测试连通性以及 cost
      MaybeStartPinging();
    }
    
    

    Connection 排序

    排序的条件主要有(优先级从高到低):

    • WriteState
    • Remote nomination 标志
    • 上次接收数据时间
    • Network cost
    • Connection priority
    • Candidate generation
    IceControllerInterface::SwitchResult
    BasicIceController::SortAndSwitchConnection(IceControllerEvent reason) {
      // stable 排序
      absl::c_stable_sort(
          connections_, [this](const Connection* a, const Connection* b) {
            int cmp = CompareConnections(a, b, absl::nullopt, nullptr);
            if (cmp != 0) {
              return cmp > 0;
            }
            // Otherwise, sort based on latency estimate.
            return a->rtt() < b->rtt();
          });
    
      ...
    
      const Connection* top_connection =
          (!connections_.empty()) ? connections_[0] : nullptr;
    
      return ShouldSwitchConnection(reason, top_connection);
    }
    
    
    int BasicIceController::CompareConnections(
        const Connection* a,
        const Connection* b,
        absl::optional<int64_t> receiving_unchanged_threshold,
        bool* missed_receiving_unchanged_threshold) const {
      RTC_CHECK(a != nullptr);
      RTC_CHECK(b != nullptr);
    
      //1、比较 State
      int state_cmp = CompareConnectionStates(a, b, receiving_unchanged_threshold,
                                              missed_receiving_unchanged_threshold);
      if (state_cmp != 0) {
        return state_cmp;
      }
    
      // 如果是受控端,比较 Remote nomination 和上次接收数据时间
      if (ice_role_func_() == ICEROLE_CONTROLLED) {
        // Compare the connections based on the nomination states and the last data
        // received time if this is on the controlled side.
        if (a->remote_nomination() > b->remote_nomination()) {
          return a_is_better;
        }
        if (a->remote_nomination() < b->remote_nomination()) {
          return b_is_better;
        }
    
        if (a->last_data_received() > b->last_data_received()) {
          return a_is_better;
        }
        if (a->last_data_received() < b->last_data_received()) {
          return b_is_better;
        }
      }
    
      // 比较 cost 和 priority
      return CompareConnectionCandidates(a, b);
    }
    
    

    WriteState 比较

    int BasicIceController::CompareConnectionStates(
        const Connection* a,
        const Connection* b,
        absl::optional<int64_t> receiving_unchanged_threshold,
        bool* missed_receiving_unchanged_threshold) const {
      // First, prefer a connection that's writable or presumed writable over
      // one that's not writable.
      bool a_writable = a->writable() || PresumedWritable(a);
      bool b_writable = b->writable() || PresumedWritable(b);
      if (a_writable && !b_writable) {
        return a_is_better;
      }
      if (!a_writable && b_writable) {
        return b_is_better;
      }
    
      // Sort based on write-state. Better states have lower values.
      // 值越低优先级越高
      if (a->write_state() < b->write_state()) {
        return a_is_better;
      }
      if (b->write_state() < a->write_state()) {
        return b_is_better;
      }
    
      // We prefer a receiving connection to a non-receiving, higher-priority
      // connection when sorting connections and choosing which connection to
      // switch to.
      // 最近有接收消息的优先
      if (a->receiving() && !b->receiving()) {
        return a_is_better;
      }
      if (!a->receiving() && b->receiving()) {
        if (!receiving_unchanged_threshold ||
            (a->receiving_unchanged_since() <= *receiving_unchanged_threshold &&
             b->receiving_unchanged_since() <= *receiving_unchanged_threshold)) {
          return b_is_better;
        }
        *missed_receiving_unchanged_threshold = true;
      }
    
      // 已经连接比未连接的优先级高
      // TCP 才有未连接状态
      if (a->write_state() == Connection::STATE_WRITABLE &&
          b->write_state() == Connection::STATE_WRITABLE) {
        if (a->connected() && !b->connected()) {
          return a_is_better;
        }
        if (!a->connected() && b->connected()) {
          return b_is_better;
        }
      }
    
      return 0;
    }
    
    

    WriteState 一共有四种状态:

    • STATE_WRITABLE = 0, // 最近收到 ping response
    • STATE_WRITE_UNRELIABLE = 1, // 之前处于 writable,最近发生过 ping 失败
    • STATE_WRITE_INIT = 2, // 还没收到 ping response
    • STATE_WRITE_TIMEOUT = 3, //有很多的 ping 失败

    STATE_WRITABLE 优先级最高,除此之外 WriteState 值低的优先级相对较高。
    WriteState 状态切换在 Connection::UpdateState 中,这段分析在排序后面。

    Cost 和 priority 比较

    int BasicIceController::CompareConnectionCandidates(const Connection* a,
                                                        const Connection* b) const {
      // 比较 network cost
      int compare_a_b_by_networks =
          CompareCandidatePairNetworks(a, b, config_.network_preference);
      if (compare_a_b_by_networks != a_and_b_equal) {
        return compare_a_b_by_networks;
      }
    
      // Compare connection priority. Lower values get sorted last.
      // 比较 priority,值高排名在前
      if (a->priority() > b->priority()) {
        return a_is_better;
      }
      if (a->priority() < b->priority()) {
        return b_is_better;
      }
    
      // If we're still tied at this point, prefer a younger generation.
      // (Younger generation means a larger generation number).
      int cmp = (a->remote_candidate().generation() + a->generation()) -
                (b->remote_candidate().generation() + b->generation());
      if (cmp != 0) {
        return cmp;
      }
    
      // A periodic regather (triggered by the regather_all_networks_interval_range)
      // will produce candidates that appear the same but would use a new port. We
      // want to use the new candidates and purge the old candidates as they come
      // in, so use the fact that the old ports get pruned immediately to rank the
      // candidates with an active port/remote candidate higher.
      bool a_pruned = is_connection_pruned_func_(a);
      bool b_pruned = is_connection_pruned_func_(b);
      if (!a_pruned && b_pruned) {
        return a_is_better;
      }
      if (a_pruned && !b_pruned) {
        return b_is_better;
      }
    
      // Otherwise, must be equal
      return 0;
    }
    
    

    WriteState 更新

    void Connection::UpdateState(int64_t now) {
      int rtt = ConservativeRTTEstimate(rtt_);
    
      if ((write_state_ == STATE_WRITABLE) &&
          TooManyFailures(pings_since_last_response_, unwritable_min_checks(), rtt,
                          now) &&
          TooLongWithoutResponse(pings_since_last_response_, unwritable_timeout(),
                                 now)) {
        uint32_t max_pings = unwritable_min_checks();
        
        set_write_state(STATE_WRITE_UNRELIABLE);
      }
      if ((write_state_ == STATE_WRITE_UNRELIABLE ||
           write_state_ == STATE_WRITE_INIT) &&
          TooLongWithoutResponse(pings_since_last_response_, inactive_timeout(),
                                 now)) {
        RTC_LOG(LS_INFO) << ToString() << ": Timed out after "
                         << now - pings_since_last_response_[0].sent_time
                         << " ms without a response, rtt=" << rtt;
        set_write_state(STATE_WRITE_TIMEOUT);
      }
    
      // Update the receiving state.
      UpdateReceiving(now);
      if (dead(now)) {
        Destroy();
      }
    }
    
    

    这段代码里只有 WriteState 的「降级」,WriteState 的「升级」在 Connection::ReceivedPingResponse 里。在收到 ping response 后,WriteState 会被设置为 STATE_WRITABLE。

    ping 远端

    Connection 选中

    以下几种情况可能触发选择新的 Connection:

    • 受控端接收到新的候选信息触发 OnNominated
    • 受控端接收到新的 packet
    • 上面触发连通性测试的三种情形
      1. 收集到本地 Candidate
      2. 接收到 Remote Candidate
      3. 接收到对端 Stun ping request

    当 selected_connection_ 产生,连接就建立成功,可以同另一个 Peer 进行通信了。

    ICE Nomination

    ICE 有两种提名方式:

    • Regular Nomination
    • Aggressive nomination

    常规流程如下:

    L                        R
    -                        -
    STUN request ->             \  L's
    <- STUN response  /  check
    
    <- STUN request  \  R's
    STUN response ->            /  check
    
    STUN request + flag ->      \  L's
    <- STUN response  /  check
    
    Regular Nomination  
    

    常规流程中,前四次握手为两端分别发起一次请求,然后分别收到一次回复。之后 Controlling 一端会再次发出一个携带 USE_CANDIDATE 标志位的 Binding Request,当 Controlled 一端收到了,就接受这次提名。

    激进流程如下:

    L                        R
    -                        -
    STUN request + flag ->      \  L's
    
    <- STUN response  /  check
    <- STUN request  \  R's
    STUN response ->            /  check
    
    Figure 5: Aggressive Nomination
    

    相比常规流程,它能节省一次握手。Controlling 端第一次 Binding Request 中会直接携带 USE_CANDIDATE 的标志位,Controlled 模式下的 Agent 收到了以后就接受这次提名。

    相关文章

      网友评论

          本文标题:WebRTC 源码分析 三 连通性检查与提名

          本文链接:https://www.haomeiwen.com/subject/hjkxsltx.html