WebRTC 的 rtc_p2p 模块(位于 src/p2p/ 目录下)是实现 ICE (Interactive Connectivity Establishment) 协议的核心部分。它的主要任务是在两个对等端(Peer)之间寻找最佳的网络路径,以便建立直接的 UDP 连接进行媒体传输。
如果直连失败,它还负责通过 TURN 服务器中继数据。
以下是 rtc_p2p 源码的核心架构、关键类和流程解析:
一、 核心架构子系统
1. 基础网络抽象 (base/):
• 封装了 Socket、地址、STUN/TURN 协议解析。
• 核心类:StunServer, TurnServer, PortInterface, Candidate.
2. 端口管理 (base/ & client/):
• 负责创建不同类型的网络“端口”(即本地网络接口或隧道)。
• 核心类:UDPPort, TCPPort, RelayPort (TURN), StunPort.
3. ICE 传输通道 (base/):
• 管理候选者对的连接检查(Connectivity Checks)。
• 核心类:P2PTransportChannel, Connection, ConnectionRequest.
4. 会话管理 (session/):
• 高层封装,将 ICE 逻辑与 SDP Offer/Answer 结合。
• 核心类:JsepTransportController, IceTransportInternal.
二、关键概念和类
2.1 Candidate (候选者)
在 ICE 中,"Candidate" 是一个可能的通信端点(IP:Port + 协议 + 类型)。
• Host Candidate: 本地网卡 IP。
• Srflx (Server Reflexive): 通过 STUN 服务器获取的公网映射 IP。
• Relay Candidate: 通过 TURN 服务器分配的中继 IP。
• Prflx (Peer Reflexive): 在连接检查过程中发现的对方映射地址。
代码位置: p2p/base/candidate.h
class Candidate { public: // 类型: HOST, SRFLX, RELAY, PRFLX enum Type { HOST = 0, SRFLX = 1, RELAY = 2, PRFLX = 3 }; const rtc::SocketAddress& address() const; const std::string& type() const; int priority() const; // ICE 优先级,用于排序 };2.2 Port (端口)
Port 是 WebRTC 中对网络接口的抽象。每个 Port 负责生成一组 Candidate,并发送/接收 STUN 包。
• UDPPort: 绑定本地 UDP 端口,生成 Host Candidate。
• StunPort: 向 STUN 服务器发送 Binding Request,生成 Srflx Candidate。
• RelayPort: 向 TURN 服务器发送 Allocate Request,生成 Relay Candidate,并处理后续的数据中继。
代码位置: p2p/base/port.h, p2p/base/udp_port.h, p2p/base/stun_port.h, p2p/base/relay_port.h
2.3 P2PTransportChannel (传输通道)
这是 ICE 算法的大脑。它持有所有的本地 Ports 和远程 Candidates。
• 职责:
1. 配对 (Pairing): 将本地 Candidate 与远程 Candidate 组合成 Connection 对象。
2. 排序 (Sorting): 根据 ICE 规则(优先级、网络类型)对 Connection 进行排序。
3. 连接检查 (Connectivity Checks): 按照排序顺序,依次发送 STUN Binding Request 来测试连通性。
4. 提名 (Nomination): 一旦某个 Connection 检查成功,将其标记为“选中”,后续媒体数据将通过该路径发送。
代码位置: p2p/base/p2p_transport_channel.h
2.4 Connection (连接)
代表一个具体的“本地 Candidate <-> 远程 Candidate”的对子。
• 每个 Connection 维护自己的状态机(Init -> Connected -> Failed)。
• 负责发送具体的 STUN Ping 包并等待 Pong。
代码位置: p2p/base/connection.h
三、ICE 工作流程源码追踪
第一步:收集候选者 (Gathering)
当 PeerConnection 创建时,会触发 ICE 收集过程。
1. 创建 Ports: P2PTransportChannel 调用 PortAllocator(通常在 pc/ 层)创建各种 Port。
// 伪代码 auto udp_port = allocator->CreateUDPPort(); auto stun_port = allocator->CreateStunPort(stun_server); auto relay_port = allocator->CreateRelayPort(turn_server);
2. 生成 Candidate: 每个 Port 在初始化完成后,会通过信号(Signal)通知 Channel 它发现了新的 Candidate。
// 在 UDPPort 中 SignalAddressReady(this, candidate);
3. 发送给对端: P2PTransportChannel 收到 Candidate 后,通过 JsepTransportController 将其放入 SDP 或作为 Trickle ICE 消息发送给对端。
第二步:远程候选者处理与配对
当收到对端的 SDP 或 Trickle ICE 消息时:
1. 添加远程 Candidate: P2PTransportChannel::AddRemoteCandidate() 被调用。
2. 创建 Connection: Channel 遍历所有本地 Candidate 和新收到的远程 Candidate,为每一对创建一个 Connection 对象。
for (local_cand : local_candidates_) { for (remote_cand : remote_candidates_) { CreateConnection(local_cand, remote_cand); } }
3. 排序: 调用 SortConnectionsAndUpdateState()。ICE 规则规定:
• 优先检查高优先级的对子。
• 优先检查同类型的网络(如 Host-Host 优于 Relay-Relay)。
第三步:连接检查 (Connectivity Checks)
这是 ICE 的核心循环。
1. 发送 Ping: P2PTransportChannel 按顺序取出未检查的 Connection,调用 conn->Ping()。 这会构造一个 STUN Binding Request,包含 ICE-CONTROLLED 或 ICE-CONTROLLING 属性,以及 Tie-breaker。
2. 处理响应:
• 成功: 收到 STUN Binding Response。Connection 状态变为 STATE_WRITABLE。
• 失败: 超时或收到错误响应。Connection 状态变为 STATE_FAILED。
3. 提名 (Nomination): 如果是主动方(Controlling),当第一个 Connection 变Writable时,会在后续的 STUN 包中设置 USE-CANDIDATE 属性。对端收到后,确认该连接为最终选择。
第四步:数据传输
一旦 Connection 被提名并确认为 Writable:
• P2PTransportChannel 将媒体数据(RTP/RTCP)交给该 Connection。
• Connection 通过其关联的 Port 的 Socket 发送数据。
• 如果是 Relay Connection,数据先发给 TURN 服务器,由 TURN 服务器转发给对端。
四、部分文件介绍
| 文件路径 | 作用 |
| p2p/base/candidate.h | 定义 ICE Candidate 数据结构 |
| p2p/base/port.h | 定义 Port 接口,所有端口类型的基类 |
| p2p/base/udp_port.h/cc | 实现本地 UDP 端口,生成 Host Candidate |
| p2p/base/stun_port.h/cc | 实现 STUN 客户端逻辑,生成 Srflx Candidate |
| p2p/base/relay_port.h/cc | 实现 TURN 客户端逻辑,生成 Relay Candidate |
| p2p/base/p2p_transport_channel.h/cc | 核心类:管理 ICE 状态机、配对、排序、Ping 调度 |
| p2p/base/connection.h/cc | 代表单个候选者对,执行具体的 STUN Ping/Pong |
| p2p/base/stun_request.h/cc | 封装 STUN 请求的发送、重传和超时处理 |
| p2p/base/basic_packet_socket_factory.h | 创建底层 UDP/TCP Socket 的工厂 |
| p2p/client/basic_port_allocator.cc | 协调创建各种 Port 的逻辑(通常被 PeerConnection 调用) |
五、常用调试和优化
1. ICE 速度慢:
• 检查 P2PTransportChannel 中的 SortConnectionsAndUpdateState。
• 查看是否因为 DNS 解析 STUN/TURN 域名耗时过长(异步解析可能阻塞配对)。
• 检查 kInitialPingInterval 等常量,调整 Ping 的频率。
2. 连接失败:
• 查看 Connection 的状态日志。是 TIMEOUT 还是 REFUSED?
• 检查 NAT 类型。如果是 Symmetric NAT,必须依赖 TURN (Relay)。
• 检查防火墙是否拦截了 UDP 高位端口。
3. TURN 中继流量大:
• 检查为什么 Host/Srflx 连接失败。
• 在 P2PTransportChannel 中,可以看到最终选中的 Connection 类型。如果是 RELAY,说明直连失败。
六、总结
rtc_p2p 模块是一个高度异步、基于状态机的网络探测引擎。
• 输入: 本地网络接口、STUN/TURN 服务器配置、远程 Candidate 列表。
• 处理: 并行探测所有可能的路径,通过 STUN 协议验证连通性,并根据 ICE 算法选出最优路径。
• 输出: 一个可用的、经过验证的 UDP 通道(Socket),供上层 RTP 模块使用。
理解 P2PTransportChannel 如何调度 Connection 的 Ping 操作,以及 Port 如何封装不同的网络协议(UDP/TCP/TLS),是掌握 WebRTC 网络连接机制的关键。