# PeerConnection 这个上帝对象 · 内部构造

> 打开 pc/peer_connection.h，看这个「上帝对象」真实拥有哪些下属组件：sdp_handler_、rtp_manager_、transport_controller_、call_、data_channel_controller_、stats。每个 unique_ptr 成员对应一块职责与后续一个部分，而线程注解揭示它们各自活在哪条线程。

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

# PeerConnection 这个上帝对象 · 内部构造

[上一章](https://daiw.org/manual/webrtc/factory-and-context)工厂造出了 `PeerConnection`。现在推开它的大门。我们[早说过](https://daiw.org/manual/webrtc/call-lifecycle)它是个「上帝对象」，但它并非一团乱麻，而是**把职责整整齐齐拆给了一组下属组件**，每个都是它的成员、与它同生共死。这一章就点清这些成员——它们几乎**一一对应**本教程后面的各个部分。

## 成员清单：上帝对象的下属们

打开 `pc/peer_connection.h`，PeerConnection 的私有成员里，躺着这样几个 `unique_ptr`（下面是真实声明，附上它们在头文件里的大致行号）：

```cpp
// pc/peer_connection.h(节选真实成员声明)
const scoped_refptr<ConnectionContext> context_;                 // 共享上下文

std::unique_ptr<Call> call_ RTC_GUARDED_BY(worker_thread());     // 通话中枢
std::unique_ptr<JsepTransportController> transport_controller_;  // 传输配置
std::unique_ptr<SdpOfferAnswerHandler> sdp_handler_;             // SDP 协商
std::unique_ptr<RtpTransmissionManager> rtp_manager_;            // 收发器管理
std::unique_ptr<LegacyStatsCollector> legacy_stats_;             // (旧)统计
```

一眼扫过去，`g3doc/peer_connection.md` 描述的那张分工表，在这里**落成了实实在在的成员**：

| 成员 | 类型 | 职责 | canonical 路径 | 本教程 |
| --- | --- | --- | --- | --- |
| `context_` | `ConnectionContext` | [共享上下文](https://daiw.org/manual/webrtc/factory-and-context)（三线程等） | `pc/connection_context.h` | 第 1 部分 |
| `sdp_handler_` | `SdpOfferAnswerHandler` | **SDP 提议 / 应答协商** | `pc/sdp_offer_answer.h` | [本部分](https://daiw.org/manual/webrtc/offer-answer-handler) |
| `rtp_manager_` | `RtpTransmissionManager` | 管 `RtpSender`/`Receiver`/`Transceiver` | `pc/rtp_transmission_manager.h` | 本部分 |
| `transport_controller_` | `JsepTransportController` | 配置 ICE / DTLS 传输 | `pc/jsep_transport_controller.h` | 第 1–3 部分 |
| `data_channel_controller_` | `DataChannelController` | 数据通道与 SCTP | `pc/data_channel_controller.h` | 第 8 部分 |
| `call_` | `Call` | 整体通话与媒体流中枢 | `call/call.h` | 第 9 部分 |
| `legacy_stats_` / `RtcStatsCollector` | 统计收集 | 按需汇总各处统计 | `pc/rtc_stats_collector.h` | 第 11 部分 |

**读到后面某个部分时，回来看这张表**——你就知道那个子系统，是从 `PeerConnection` 的哪个成员生长出来的。

## 线程注解泄露了天机

注意 `call_` 那一行的[线程注解](https://daiw.org/manual/webrtc/threading-model)：

```cpp
std::unique_ptr<Call> call_ RTC_GUARDED_BY(worker_thread());
```

<Callout type="info">
  **`RTC_GUARDED_BY(worker_thread())` 告诉你：`Call` 及其掌管的媒体流，活在 worker 线程上。** 这和[三线程分工](https://daiw.org/manual/webrtc/threading-model)严丝合缝——`Call` 是「媒体处理」的中枢（[第 9 部分](https://daiw.org/manual/webrtc/source-map)），归 worker 线程管；而 `sdp_handler_`（SDP 协商属于控制面）则在 signaling 线程上运作。**同一个 PeerConnection，它的下属对象分散在不同线程上**——这正是为什么读代码时线程注解如此关键：它标出每个成员的「户口所在地」。这也解释了为什么 `PeerConnection` 内部充满[跨线程的任务派发](https://daiw.org/manual/webrtc/proxy-pattern)：signaling 线程上的 SDP 协商一旦要动媒体，就得把活儿投给 worker 线程上的 `Call`。
</Callout>

## 谁拥有谁：一张所有权图

`g3doc/peer_connection.md` 强调，这些下属对象**全部由 `PeerConnection` 拥有，活得和它一样久**。用 `unique_ptr` 持有正是这个语义——独占所有权，`PeerConnection` 析构时它们一起销毁。画成图：

```mermaid
flowchart TD
    PC["PeerConnection(上帝对象)"]
    PC --> CTX["context_ · ConnectionContext<br/>(共享,引用计数)"]
    PC --> SDP["sdp_handler_ · SdpOfferAnswerHandler<br/>signaling 线程 · SDP 协商"]
    PC --> RTP["rtp_manager_ · RtpTransmissionManager<br/>Sender/Receiver/Transceiver"]
    PC --> TC["transport_controller_ · JsepTransportController<br/>ICE/DTLS 传输"]
    PC --> DC["data_channel_controller_ · DataChannelController<br/>数据通道 + SCTP"]
    PC --> CALL["call_ · Call<br/>worker 线程 · 媒体流中枢"]
    PC --> ST["stats · RtcStatsCollector"]
```

`context_` 用 `scoped_refptr`（共享、[引用计数](https://daiw.org/manual/webrtc/factory-and-context)），因为它被多个 PeerConnection 共享；其余下属用 `unique_ptr`（独占），因为它们**专属于这一个** PeerConnection。所有权类型的选择，精确反映了共享还是独占。

## 初始化：从工厂到就绪

拼起[上一章](https://daiw.org/manual/webrtc/factory-and-context)的线索，`PeerConnection` 的诞生大致是：工厂在 `CreatePeerConnectionOrError` 里，先准备好共享的 `context_` 和一个 `Call`（注意构造函数签名里 `std::unique_ptr<Call> call` 是**外部造好、移动进来**的），再构造 `PeerConnection`，然后由它在内部初始化 `sdp_handler_`、`rtp_manager_`、`transport_controller_` 等下属。等这些都就位，一个能[协商、能收发](https://daiw.org/manual/webrtc/call-lifecycle)的 PeerConnection 就绪了。这些初始化步骤大多带着 `RTC_DCHECK_RUN_ON(signaling_thread())`——**创建也必须在对的线程上**。

## 小结与下一章

`PeerConnection` 是「上帝对象」，但内部**井然有序地**拆成一组 `unique_ptr` 下属成员：`sdp_handler_`（SDP 协商）、`rtp_manager_`（收发器）、`transport_controller_`（传输）、`data_channel_controller_`（数据通道）、`call_`（媒体中枢），外加共享的 `context_`。它们**由 PeerConnection 独占拥有、同生共死**，而**线程注解**揭示各自的户口——`call_` 在 worker、`sdp_handler_` 在 signaling。这张成员表几乎一一对应本教程后续各部分。

下属里最先登场、也是会话建立的绝对核心，是 `sdp_handler_`——它靠 **SDP** 和对端协商「这通电话怎么打」。但要读懂它，得先弄明白它操作的对象：**SDP 是什么、JSEP 状态机怎么转**。下一章打地基。👉 [SDP 与 JSEP](https://daiw.org/manual/webrtc/sdp-and-jsep)。

## 参考资料

- [1] libwebrtc 源码.`pc/peer_connection.h`（下属对象的真实成员声明与线程注解）。
- [2] libwebrtc 源码.`g3doc/peer_connection.md`（下属对象分工与「全部由 PeerConnection 拥有」）。
- [3] libwebrtc 源码.`pc/peer_connection.cc`（`CreatePeerConnectionOrError` 与初始化流程）。
