# PortAllocator 与候选采集 · 四种 Port

> 候选由 Port 采集，Port 由 PortAllocator 创建。这一章读 p2p/ 的 PortAllocator/BasicPortAllocator 与 PortAllocatorSession，以及四种 Port——UDPPort、StunPort、TcpPort、TurnPort，它们分别产出哪种候选，以及怎么用 flag 开关它们。

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

# PortAllocator 与候选采集 · 四种 Port

[候选](https://daiw.org/manual/webrtc/candidates)不会凭空出现，得有人去「采集」。采集的执行者是 **`Port`**，而创建和调度 `Port` 的是 **`PortAllocator`**。这一章读这套采集机制——它是 [ICE 第一步](https://daiw.org/manual/webrtc/ice-overview)的引擎。

## PortAllocator：采集的总调度

`P2PTransportChannel` 不亲自采集候选，而是委托给 **`PortAllocator`**（`p2p/base/port_allocator.h`，默认实现 `BasicPortAllocator`，在 `p2p/client/`）。当 [`MaybeStartGathering`](https://daiw.org/manual/webrtc/ice-overview) 被 [`SetLocalDescription`](https://daiw.org/manual/webrtc/offer-answer-handler) 触发时，流程是（`g3doc/ice.md`）：

```text
MaybeStartGathering()
  → PortAllocator 创建一个 PortAllocatorSession(一次采集会话)
     → Session 按配置创建各种 Port
        → 每个 Port 开始采集它那一类候选
           → 采到就通过 SignalCandidateGathered 报上去
```

- **`PortAllocatorSession`**(`p2p/base/port_allocator.h`)—— **一次采集会话**。[ICE 重启](https://daiw.org/manual/webrtc/source-map)时会开一个新 session 重新采集，老的作废。用「会话」把一轮采集封装起来，便于整体管理和替换。
- 采集是**异步、持续**的：候选一个个冒出来（host 最快、srflx 要等 STUN 往返、relay 要等 TURN 分配），[trickle](https://daiw.org/manual/webrtc/candidates) 给应用。

## 四种 Port，四种候选

`Port` 是「一种本地通信机制」的抽象（`g3doc/ice.md`）。它有一个基类 `Port`（`p2p/base/port.h`）和四个实现，**每种产出一类[候选](https://daiw.org/manual/webrtc/candidates)**：

| Port 实现 | canonical 路径 | 产出候选 | 干什么 |
| --- | --- | --- | --- |
| `UDPPort` | `p2p/base/stun_port.h` | `host`(UDP) | 直接用本机网卡的 UDP 地址 |
| `StunPort` | `p2p/base/stun_port.h` | `srflx` | 向 [STUN 服务器](https://daiw.org/manual/webrtc/stun-protocol)问公网地址 |
| `TcpPort` | `p2p/base/tcp_port.h` | `host`(TCP) | 本机网卡的 TCP 地址（UDP 被封时的退路） |
| `TurnPort` | `p2p/base/turn_port.h` | `relay` | 向 [TURN 服务器](https://daiw.org/manual/webrtc/source-map)申请中继地址 |

（注意 `UDPPort` 和 `StunPort` 都定义在 `stun_port.h` 里——`UDPPort` 是基础，`StunPort` 在它之上加了向 STUN 服务器查询的能力。）它们共享 `Port` 基类的大量逻辑（管理候选、创建 [`Connection`](https://daiw.org/manual/webrtc/source-map)、收发包）。

<Callout type="info">
  **一台设备通常有多个网络接口（Wi-Fi、有线、蜂窝），`PortAllocator` 会为每个接口、每种类型都建 Port。** 于是一次采集可能产出十几个候选：每个网卡一个 host（UDP）、一个 host（TCP）、一个 srflx（经 STUN）、可能还有 relay（经 TURN）。**候选越多，配对越多，找到能通路径的机会越大**——这正是 [ICE「全备齐」哲学](https://daiw.org/manual/webrtc/candidates)的落地。当然候选太多也会让[连通性检查](https://daiw.org/manual/webrtc/stun-protocol)变多，所以有各种策略（如按接口/网络成本裁剪）来平衡。
</Callout>

## 用 flag 开关采集

采集行为可以通过 `PortAllocator` 的一组 **flag** 精细控制（`p2p/base/port_allocator.h`）。几个真实的：

```cpp
// p2p/base/port_allocator.h(节选 flag)
PORTALLOCATOR_DISABLE_UDP        = 0x01,   // 禁用 UDP host 候选
PORTALLOCATOR_DISABLE_TCP        = 0x08,   // 禁用 TCP host 候选
PORTALLOCATOR_ENABLE_IPV6        = 0x40,   // 启用 IPv6 候选
PORTALLOCATOR_DISABLE_UDP_RELAY  = 0x1000, // 禁用 UDP 中继
```

这些 flag 让上层能按需裁剪：比如某些企业环境禁 UDP，就只能靠 TCP host / TURN-TCP；要支持 IPv6 就开 `ENABLE_IPV6`。**采集哪些候选，是可配置的策略**——又是 WebRTC [可定制哲学](https://daiw.org/manual/webrtc/factory-and-context)的体现。IceServers（STUN/TURN 服务器地址）则通过[连接级的 `RTCConfiguration`](https://daiw.org/manual/webrtc/factory-and-context) 传入，决定 `StunPort` / `TurnPort` 去问哪台服务器。

## 一张采集图

```mermaid
flowchart TD
    MSG["MaybeStartGathering()<br/>(由 SetLocalDescription 触发)"]
    MSG --> PA["PortAllocator → PortAllocatorSession"]
    PA --> P1["UDPPort → host(UDP) 候选"]
    PA --> P2["StunPort → srflx 候选(问 STUN)"]
    PA --> P3["TcpPort → host(TCP) 候选"]
    PA --> P4["TurnPort → relay 候选(问 TURN)"]
    P1 --> SIG["SignalCandidateGathered → OnIceCandidate → 信令发对端"]
    P2 --> SIG
    P3 --> SIG
    P4 --> SIG
```

## 小结与下一章

**候选采集**：[`P2PTransportChannel`](https://daiw.org/manual/webrtc/ice-overview) 委托 **`PortAllocator`**（`BasicPortAllocator`）创建 **`PortAllocatorSession`**（一次采集会话，[ICE 重启](https://daiw.org/manual/webrtc/source-map)换新的），session 按配置建四种 **`Port`**——`UDPPort`（host/UDP）、`StunPort`（srflx）、`TcpPort`（host/TCP）、`TurnPort`（relay），每种产一类[候选](https://daiw.org/manual/webrtc/candidates)，异步 [trickle](https://daiw.org/manual/webrtc/candidates) 上报。多网卡 × 多类型 → 候选众多，提高穿透率；用 **flag** 精细开关。

`StunPort` 靠 **STUN** 问出公网地址，而 STUN 还是[连通性检查](https://daiw.org/manual/webrtc/ice-overview)的核心。下一章专讲这个 ICE 的「万能工具」协议。👉 [STUN 协议](https://daiw.org/manual/webrtc/stun-protocol)。

## 参考资料

- [1] libwebrtc 源码.`p2p/base/port_allocator.h`、`p2p/client/basic_port_allocator.h`（采集调度与 flag）。
- [2] libwebrtc 源码.`p2p/base/port.h`、`stun_port.h`、`tcp_port.h`、`turn_port.h`（四种 Port）。
- [3] libwebrtc 源码.`g3doc/ice.md`（采集流程）。
