WebRTCNative源码分析——P2P连接过程详解

玛格丽特阿特伍德WebRTCNative源码分析——P2P连接过程详解
⼀年前我初步分析了 WebRTC 的 P2P 连接过程,并总结为了⼀⽂,那会⼉我刚接触 WebRTC C++ 的代码,看起来着实头⼤,⽽且安卓的代码要调试、测试也很⿇烦,所以很多细节就没有展开,今天就让我们在 iOS 的⼯程⾥,对 P2P 连接的过程进⾏⼀个彻底的剖析。
概览
⾸先我们从宏观上了解⼀下 P2P 连接的过程,以及⼀些关键类之间的关系,这样在看代码时就不⾄于迷失在细节⾥。此外,没看过的朋友,也建议先看⼀下。
注:除⾮你对这个话题有很⼤兴趣,否则很可能⽆法读完,那我建议尽早放弃;如果确实需要研究这块内容,那我建议打开源码,反复阅读此⽂,应当会有些收获。
宏观流程
设置 local sdp;
创建⼀个 transport 对象(启⽤了 bundle);
收集 local candidates;
设置 remote sdp,添加 remote candidates;
ICE 连通性检查,建⽴ P2P 连接;
P2P 数据传输;
P2P 关键类
PeerConnection: WebRTC 核⼼类;
JsepTransportController: 管理 P2P 连接;
各种 transport 类:P2P 连接的封装,封装了加解密、mux/demux 等逻辑,提供收发数据的接⼝;
BasicPortAllocator, PortAllocator: 保存各种配置,管理 PortAllocatorSession;
BasicPortAllocatorSession, PortAllocatorSession: 遍历所有⽹络设备(Network 对象),分配 port;
AllocationSequence: 负责对单个⽹络设备(Network 对象)分配 port,分阶段进⾏;
各种 port 类:代表的是⼀种通讯机制的本地实例,它可以和远端的类似实例⼀起实现数据通讯;
Connection: 代表的是⼀个 local port 和⼀个 remote port 的通讯链接;
注:列在同⼀点⾥的类,是继承关系,左侧是⼦类,右侧是基类,下同。
各种 transport 类的关系
PacketTransportInternal, PacketTransportInterface: packet base transport 的基类;
P2PTransportChannel, IceTransportInternal: 继承⾃ PacketTransportInternal,负责 ICE 相关的功能,包括:收集本地candidate、和远端 candidate 做连通性检查、数据传输;
DtlsTransport, DtlsTransportInternal: 继承⾃ PacketTransportInternal,负责 DTLS 相关的功能,包括:DTLS 握⼿、DTLS 加解密;内含⼀个 IceTransportInternal (P2PTransportChannel),收发数据通过它实现;
不加密时,写⼊ DtlsTransport 的数据,直接交给 P2PTransportChannel;
加密时,数据会交给 SSLStreamAdapter -> StreamInterfaceChannel -> P2PTransportChannel,这是为了桥接加解密的流式接⼝与 DtlsTransport 的包式接⼝;
RtpTransport, RtpTransportInternal, SrtpTransportInterface, RtpTransportInterface: 提供了收发 RTP, RTCP 包的接⼝,其内部包了两个实际收发 RTP 和 RTCP 数据的 PacketTransportInternal (DtlsTransport);
DtlsSrtpTransport, SrtpTransport: DTLS-SRTP, SRTP 的实现类,继承⾃ RtpTransport; _有了 DTLS,为何还要 SRTP?_ JsepTransport: JsepTransportController 管理 transport 的辅助类,sdp ⾥每个 m line 都对应于⼀个数据流(⾳频、视频、应⽤
数据),每个数据流都需要⼀个 transport,但可以通过 bundle 技术复⽤同⼀个 transport,m line ⾥的 attribute 描述了transport 的属性;
根据 transport 的加密属性,构造它时会准备⽆加密的 RtpTransport,或 SDES 加密的 SrtpTransport,或 DTLS 加密的DtlsSrtpTransport;这⼀逻辑在 JsepTransportController::MaybeCreateJsepTransport 函数⾥;
transport 对象将在设置 sdp 时创建,⼀个 transport 对象将会对应于⼀个最终的 P2P ⽹络连接(socket);
关键类的数量关系
⼀个 PeerConnection - ⼀个 JsepTransportController - ⼀个 JsepTransport(启⽤了 bundle) - ⼀个 DtlsSrtpTransport - ⼀个DtlsTransport - ⼀个 P2PTransportChannel。
⼀个 JsepTransportController - ⼀个 BasicPortAllocator - 多个 BasicPortAllocatorSession,但⼀次分配过程只会有⼀个session。
⼀个 BasicPortAllocatorSession - 多个 AllocationSequence。
⼀个 AllocationSequence - 多个 port。
⼀个 P2PTransportChannel - 多个 Connection,但最终会选出⼀个 Connection 使⽤。
接下来我们就对宏观过程的代码细节进⾏展开。
再次预警,如果此时你已经有些倦意,那我建议⽴刻关闭这个页⾯。
收集本地 candidate
设置 local sdp,开始收集 candidate:
PeerConnection::SetLocalDescription
JsepTransportController::MaybeStartGathering
P2PTransportChannel::MaybeStartGathering
BasicPortAllocatorSession::StartGettingPorts
正弦信号发生器↓
BasicPortAllocatorSession::DoAllocate
DoAllocate
DoAllocate ⾥会遍历所有⽹络设备(Network 对象),创建 AllocationSequence 对象,调⽤其 Init Start 函数,分配 port。
BasicPortAllocatorSession::DoAllocate
AllocationSequence::Start
↓ message
AllocationSequence::OnMessage
AllocationSequence 分配 port 分为三个 phase:UDP, RELAY, TCP。每个 phase 之间间隔⼀个 step delay。_⼀年前我在分析时还有⼀个 SslTcp phase,现在已经删掉了_。
UDP phase
UDP phase 会收集两种类型的 candidate:host 和 srflx。
host candidate
⼀旦创建了 AsyncPacketSocket 对象,有了本地 IP 和端⼝,host 类型的 candidate 也就已经就绪了,⽽ AsyncPacketSocket 对象在 AllocationSequence::Init ⾥就已经创建好了,所以可以直接发出 host candidate。
AllocationSequence::OnMessage
AllocationSequence::CreateUDPPorts
BasicPortAllocatorSession::AddAllocatedPort
kkk43↓
UDPPort::PrepareAddress
UDPPort::OnLocalAddressReady
Port::AddAddress
↓ sig slot (SignalCandidateReady)
BasicPortAllocatorSession::OnCandidateReady
srflx candidate
收集 srflx candidate 的原理是,向 STUN server 发送⼀个 UDP 包(叫 STUN Binding request),server 会把这个包⾥的源 IP 地址、UDP 端⼝返回给客户端(叫 STUN Binding response),这个 IP 和端⼝将来可能可以⽤来和其他客户端建⽴ P2P 连接。关于STUN 协议的具体内容,可以查阅 。
收集 srflx candicate 时可以复⽤收集 host candidate 时创建的 socket 对象,这⼀逻辑通
过 PORTALLOCATOR_ENABLE_SHARED_SOCKET flag 控制,默认是开启的。
复⽤ socket 的情况下,AllocationSequence::CreateStunPorts 函数会直接返回,因为早在 AllocationSequence::CreateUDPPorts 函数的执⾏过程中,就已经执⾏了 STUN Binding request 的发送逻辑。
发送 STUN Binding request:
分水实验小学UDPPort::OnLocalAddressReady
UDPPort::MaybePrepareStunCandidate ↓
UDPPort::SendStunBindingRequest
StunRequestManager::SendDelayed
↓ message
StunRequest::OnMessage
↓ sig slot (SignalSendPacket) UDPPort::OnSendPacket
AsyncUDPSocket::SendTo
大旗文学
PhysicalSocket::SendTo
系统 socket sendto
收到 STUN Binding response:
PhysicalSocketServer::WaitSelect
SocketDispatcher::OnEvent
↓ sig slot (SignalReadEvent)
AsyncUDPSocket::OnReadEvent
↓ sig slot (SignalReadPacket)
AllocationSequence::OnReadPacket
UDPPort::HandleIncomingPacket
StunRequestManager::CheckResponse
路网StunBindingRequest::OnResponse
UDPPort::OnStunBindingRequestSucceeded
Port::AddAddress
↓ sig slot (SignalCandidateReady)
BasicPortAllocatorSession::OnCandidateReady
RELAY phase
WebRTC ⽬前⽀持两种中继协议:GTURN 和 TURN。现在基本都是使⽤标准的 TURN 协议。TURN 协议是 STUN 协议的⼀个扩展,它利⽤⼀个中继服务器,使得⽆法建⽴ P2P 连接的客户端(NAT 严格限制导致)也能实现通讯。_关于 NAT 类型与 P2P 连接的可⾏性,可参考_。
TURN 协议的⼯作流程如下:
客户端发送 Allocate request 到 server,server 返回 401 未授权错误(带有 realm 和 nonce),客户端再发送带上认证信息的Allocate request,server 返回成功分配的 relay address。分配成功后,客户端需要通过发送机制(Send Mechanism)或信道机制(Channels)在 server 上配置和其他 peer 的转发信息。此外 allocation 和 channel 都需要保活。
WebRTC 使⽤的是信道机制,因为这⼀机制的数据开销更低。
收集 TURN relay candidate 时也可以复⽤收集 host candidate 时创建的 socket 对象,这⼀逻辑通
过 PORTALLOCATOR_ENABLE_SHARED_SOCKET flag 控制,前⾯我们就已经知道,默认情况下它是开启的。
由于 TURN 协议是 STUN 协议的扩展,所以基本的发送请求、接收响应的代码是复⽤的,下⾯只描述 TURN 协议独特的部分:

本文发布于:2024-09-23 03:32:28,感谢您对本站的认可!

本文链接:https://www.17tex.com/xueshu/126688.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

下一篇:通信技术分类
标签:发送   协议   对象
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议