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.

  1. 端口管理 (base/ & client/):

• 负责创建不同类型的网络"端口"(即本地网络接口或隧道)。

• 核心类:UDPPort, TCPPort, RelayPort (TURN), StunPort.

  1. ICE 传输通道 (base/):

• 管理候选者对的连接检查(Connectivity Checks)。

• 核心类:P2PTransportChannel, Connection, ConnectionRequest.

  1. 会话管理 (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

cpp 复制代码
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。
cpp 复制代码
// 伪代码
auto udp_port = allocator->CreateUDPPort();
auto stun_port = allocator->CreateStunPort(stun_server);
auto relay_port = allocator->CreateRelayPort(turn_server);
  1. 生成 Candidate: 每个 Port 在初始化完成后,会通过信号(Signal)通知 Channel 它发现了新的 Candidate。
cpp 复制代码
// 在 UDPPort 中
SignalAddressReady(this, candidate); 
  1. 发送给对端: P2PTransportChannel 收到 Candidate 后,通过 JsepTransportController 将其放入 SDP 或作为 Trickle ICE 消息发送给对端。

第二步:远程候选者处理与配对

当收到对端的 SDP 或 Trickle ICE 消息时:

  1. 添加远程 Candidate: P2PTransportChannel::AddRemoteCandidate() 被调用。

  2. 创建 Connection: Channel 遍历所有本地 Candidate 和新收到的远程 Candidate,为每一对创建一个 Connection 对象。

cpp 复制代码
for (local_cand : local_candidates_) {
  for (remote_cand : remote_candidates_) {
    CreateConnection(local_cand, remote_cand);
  }
}
  1. 排序: 调用 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。

  1. 提名 (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 的频率。

  1. 连接失败:

• 查看 Connection 的状态日志。是 TIMEOUT 还是 REFUSED?

• 检查 NAT 类型。如果是 Symmetric NAT,必须依赖 TURN (Relay)。

• 检查防火墙是否拦截了 UDP 高位端口。

  1. TURN 中继流量大:

• 检查为什么 Host/Srflx 连接失败。

• 在 P2PTransportChannel 中,可以看到最终选中的 Connection 类型。如果是 RELAY,说明直连失败。

六、总结

rtc_p2p 模块是一个高度异步、基于状态机的网络探测引擎。

• 输入: 本地网络接口、STUN/TURN 服务器配置、远程 Candidate 列表。

• 处理: 并行探测所有可能的路径,通过 STUN 协议验证连通性,并根据 ICE 算法选出最优路径。

• 输出: 一个可用的、经过验证的 UDP 通道(Socket),供上层 RTP 模块使用。

理解 P2PTransportChannel 如何调度 Connection 的 Ping 操作,以及 Port 如何封装不同的网络协议(UDP/TCP/TLS),是掌握 WebRTC 网络连接机制的关键。

相关推荐
换个昵称都难1 小时前
webrtc StunServer源码介绍
webrtc
数据知道1 天前
指纹浏览器:DNS 泄漏防范与 WebRTC 本地 IP 屏蔽的底层实现
爬虫·网络协议·tcp/ip·安全·webrtc·数据采集·指纹浏览器
换个昵称都难2 天前
webrtc源码解析概要介绍
webrtc
换个昵称都难2 天前
WebRTC 完整调用流程(前端纯 JS 实现,最简可运行)
webrtc
HwJack202 天前
HarmonyOS APP开发终结“户外运动数据失踪”的玄学:玩透穿戴设备 P2P 穿透与心跳保活的心法
华为·harmonyos·p2p
资源分享交流2 天前
OmniGet:一个更省事的跨平台下载器,支持 yt-dlp、BT、磁力和 P2P 传输
网络·网络协议·p2p
cjp5603 天前
009. ASP.NET WEB API 用户关联esp32设备
前端·后端·asp.net
无风听海3 天前
在 ASP.NET Core 开发环境中为自定义域名签发受信任的自签名证书—HSTS 启用后的完整实践
windows·后端·asp.net
无风听海3 天前
深入理解 ASP.NET Core 中的UseHsts()
后端·asp.net