webrtc RTC_P2P源码解析
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.hclass 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.h2.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.h2.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 Candidatep2p/base/stun_port.h/cc实现 STUN 客户端逻辑生成 Srflx Candidatep2p/base/relay_port.h/cc实现 TURN 客户端逻辑生成 Relay Candidatep2p/base/p2p_transport_channel.h/cc核心类管理 ICE 状态机、配对、排序、Ping 调度p2p/base/connection.h/cc代表单个候选者对执行具体的 STUN Ping/Pongp2p/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 网络连接机制的关键。