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

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

作者 David更新于 第 8 篇(共 112 篇)

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

上一章工厂造出了 PeerConnection。现在推开它的大门。我们早说过它是个「上帝对象」,但它并非一团乱麻,而是把职责整整齐齐拆给了一组下属组件,每个都是它的成员、与它同生共死。这一章就点清这些成员——它们几乎一一对应本教程后面的各个部分。

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

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

// 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共享上下文(三线程等)pc/connection_context.h第 1 部分
sdp_handler_SdpOfferAnswerHandlerSDP 提议 / 应答协商pc/sdp_offer_answer.h本部分
rtp_manager_RtpTransmissionManager管 RtpSender/Receiver/Transceiverpc/rtp_transmission_manager.h本部分
transport_controller_JsepTransportController配置 ICE / DTLS 传输pc/jsep_transport_controller.h第 1–3 部分
data_channel_controller_DataChannelController数据通道与 SCTPpc/data_channel_controller.h第 8 部分
call_Call整体通话与媒体流中枢call/call.h第 9 部分
legacy_stats_ / RtcStatsCollector统计收集按需汇总各处统计pc/rtc_stats_collector.h第 11 部分

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

线程注解泄露了天机

注意 call_ 那一行的线程注解:

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

RTC_GUARDED_BY(worker_thread()) 告诉你:Call 及其掌管的媒体流,活在 worker 线程上。 这和三线程分工严丝合缝——Call 是「媒体处理」的中枢(第 9 部分),归 worker 线程管;而 sdp_handler_(SDP 协商属于控制面)则在 signaling 线程上运作。同一个 PeerConnection,它的下属对象分散在不同线程上——这正是为什么读代码时线程注解如此关键:它标出每个成员的「户口所在地」。这也解释了为什么 PeerConnection 内部充满跨线程的任务派发:signaling 线程上的 SDP 协商一旦要动媒体,就得把活儿投给 worker 线程上的 Call。

谁拥有谁:一张所有权图

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

图表加载中…

context_ 用 scoped_refptr(共享、引用计数),因为它被多个 PeerConnection 共享;其余下属用 unique_ptr(独占),因为它们专属于这一个 PeerConnection。所有权类型的选择,精确反映了共享还是独占。

初始化:从工厂到就绪

拼起上一章的线索,PeerConnection 的诞生大致是:工厂在 CreatePeerConnectionOrError 里,先准备好共享的 context_ 和一个 Call(注意构造函数签名里 std::unique_ptr<Call> call 是外部造好、移动进来的),再构造 PeerConnection,然后由它在内部初始化 sdp_handler_、rtp_manager_、transport_controller_ 等下属。等这些都就位,一个能协商、能收发的 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。

参考资料

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

本页目录