# IceController · 选路的大脑

> 一堆候选对里，用哪条传媒体、什么时候切换、下一个 ping 谁？这些决策抽出成了 IceController。这一章读 base/ice_controller_interface.h 的 ShouldSwitchConnection/SortAndSwitchConnection，以及 controlling/controlled 两种 ICE 角色如何分工——一方拍板，一方跟随。

- 作者：David（道雾轩）
- 专栏：音视频与 WebRTC 详解（https://daiw.org/manual/webrtc.md）
- 最后更新：2026-07-14
- 原文：https://daiw.org/manual/webrtc/ice-controller
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# IceController · 选路的大脑

[`P2PTransportChannel`](https://daiw.org/manual/webrtc/p2p-transport-channel) 手里握着一堆 [`Connection`](https://daiw.org/manual/webrtc/connection-and-checks)，每条路的通断、RTT 各不相同。**到底用哪条传媒体？什么时候从一条切到另一条？下一个该 ping 谁？** 这些「选路决策」被专门抽成一个组件——**`IceController`**（`p2p/base/ice_controller_interface.h`）。把决策独立出来，是为了能替换不同的选路策略。这一章读它。

## 决策接口

`IceControllerInterface` 的核心方法，就是几个「决策」（`p2p/base/ice_controller_interface.h`）：

```cpp
// p2p/base/ice_controller_interface.h(节选)
class IceControllerInterface {
  // 该不该从当前连接切换?(某事件触发时问)
  virtual SwitchResult ShouldSwitchConnection(IceSwitchReason reason,
                                              const Connection* connection) = 0;
  // 重新排序所有连接,并决定是否切换选中的那条
  virtual SwitchResult SortAndSwitchConnection(IceSwitchReason reason) = 0;
  // 还有:选下一个该 ping 的连接、决定该剔除哪些差连接……
};
```

它不亲自收发包，只做**判断**：给它一堆连接的当前状况（通断、RTT、[候选类型优先级](https://daiw.org/manual/webrtc/candidates)、是否被[提名](https://daiw.org/manual/webrtc/nomination-and-restart)），它回答「用哪条、切不切、ping 谁」。`P2PTransportChannel` 的 [`SortConnectionsAndUpdateState`](https://daiw.org/manual/webrtc/p2p-transport-channel) 每次跑，都来问它。

## 选路的权衡

`IceController` 选路，权衡这些因素：

- **通不通、稳不稳** —— [`writable` 且 `receiving`](https://daiw.org/manual/webrtc/connection-and-checks) 的连接才够格；`UNRELIABLE` / `TIMEOUT` 的靠边。
- **优先级** —— [host > srflx > relay](https://daiw.org/manual/webrtc/candidates)：同样能通，优先选延迟低、不绕中继的。
- **RTT** —— 往返时延小的更好。
- **是否被提名** —— [被 nominated 的连接](https://daiw.org/manual/webrtc/nomination-and-restart)是「钦定」要用的。

<Callout type="info">
  **选路不是「选一次就完」，而是持续的动态过程。** 网络在变：Wi-Fi 信号忽强忽弱、更优的路径可能刚刚才通、选中的路可能突然[丢 ping 变得不可靠](https://daiw.org/manual/webrtc/connection-and-checks)。所以 `IceController` 会在每次连接状态变化时**重新评估**（`SortAndSwitchConnection`），必要时**无缝切换**到更好的连接——媒体正在流，切换却对上层几乎透明。这就是为什么 [STUN ping 永不停止](https://daiw.org/manual/webrtc/stun-protocol)：持续探测，才能持续掌握每条路的实时状况，让选路始终基于最新情报。把选路逻辑抽成独立的 `IceController` 接口，也让 WebRTC 能试验不同策略（甚至[让上层注入自定义 controller](https://daiw.org/manual/webrtc/factory-and-context)）。
</Callout>

## Controlling 与 Controlled：谁拍板

选路还有一个「谁说了算」的问题。ICE 规定，一次连接里两端**角色不对等**（`p2p/base/transport_description.h`）：

```cpp
// p2p/base/transport_description.h
enum IceRole { ICEROLE_CONTROLLING = 0, ICEROLE_CONTROLLED, ICEROLE_UNKNOWN };
```

- **`ICEROLE_CONTROLLING`（控制方）** —— **最终拍板**用哪条连接的一方。它通过[提名](https://daiw.org/manual/webrtc/nomination-and-restart)机制，指定「就用这条」。
- **`ICEROLE_CONTROLLED`（受控方）** —— **跟随**控制方的决定。

<Callout type="info">
  **为什么要分角色？为了避免「两边各选各的、选到不同的路」。** 如果两端都自作主张，可能 A 认定用路径 1、B 认定用路径 2，媒体就对不上。ICE 用「一方控制、一方跟随」打破对称：**控制方拍板，受控方服从**，保证双方最终**选中同一条路**。角色在协商时确定（通常发 offer 的一方是 controlling），写在 [SDP 的 ice 参数](https://daiw.org/manual/webrtc/sdp-and-jsep)里。万一双方角色冲突（都以为自己 controlling，即「角色冲突」），ICE 有一套基于 tie-breaker 随机数的规则来裁决，让一方让步。**分布式系统里，『谁做决定』和『决定是什么』同样重要**——这是 ICE 一个精巧的设计。
</Callout>

## 小结与下一章

**`IceController` 是选路的大脑**（`p2p/base/ice_controller_interface.h`），被 [`P2PTransportChannel`](https://daiw.org/manual/webrtc/p2p-transport-channel) 委托做决策：`ShouldSwitchConnection` / `SortAndSwitchConnection` 判断「用哪条、切不切、ping 谁」，权衡[通断稳定性](https://daiw.org/manual/webrtc/connection-and-checks)、[候选优先级](https://daiw.org/manual/webrtc/candidates)、RTT、[提名](https://daiw.org/manual/webrtc/nomination-and-restart)。选路是**持续动态**的，网络一变就重评估、无缝切换。角色上分 **controlling（拍板）/ controlled（跟随）**，避免两端选到不同的路；冲突用 tie-breaker 裁决。

控制方怎么「拍板」？靠**提名**。而网络剧变（换 Wi-Fi、掉线）时，整个 ICE 还能**重启**重来。下一章讲提名与 ICE 重启。👉 [提名与 ICE 重启](https://daiw.org/manual/webrtc/nomination-and-restart)。

## 参考资料

- [1] libwebrtc 源码.`p2p/base/ice_controller_interface.h`（`ShouldSwitchConnection` / `SortAndSwitchConnection`）、`g3doc/ice.md`。
- [2] libwebrtc 源码.`p2p/base/transport_description.h`（`IceRole`）、`p2p/base/basic_ice_controller.h`（默认实现）。
- [3] IETF RFC 8445「ICE」§ Role Determination、Nomination。
