18 KiB
18 KiB
PeerConnection 管理
**本文引用的文件** - [src/index.ts](file://src/index.ts) - [src/websocket.ts](file://src/websocket.ts) - [src/class/websockethandler.ts](file://src/class/websockethandler.ts) - [src/class/offer.ts](file://src/class/offer.ts) - [src/class/answer.ts](file://src/class/answer.ts) - [src/class/candidate.ts](file://src/class/candidate.ts) - [client/src/peer.js](file://client/src/peer.js) - [client/src/signaling.js](file://client/src/signaling.js) - [client/test/peerconnection.test.js](file://client/test/peerconnection.test.js) - [client/test/peerconnectionmock.js](file://client/test/peerconnectionmock.js) - [client/src/logger.js](file://client/src/logger.js) - [client/test/testutils.js](file://client/test/testutils.js) - [client/public/bidirectional/js/main.js](file://client/public/bidirectional/js/main.js) - [client/public/onebyone/main.js](file://client/public/onebyone/main.js) - [package.json](file://package.json)目录
简介
本技术文档聚焦于 PeerConnection 管理模块,系统阐述 RTCPeerConnection 的创建与配置、连接标识符与极性判断、SDP 协商流程(Offer/Answer 模型)、ICE 候选收集与处理、连接生命周期管理(建立、维护、关闭),以及常见状态变化与错误处理策略。文档同时提供基于仓库实际代码的图示与路径引用,帮助读者快速定位实现细节。
项目结构
该项目采用前后端分离的模块化组织方式:
- 服务端:基于 Express 与 WebSocket 的信令服务器,负责连接组管理、消息路由与广播。
- 客户端:封装 RTCPeerConnection 的管理类,负责 SDP 协商、ICE 候选处理、事件分发与状态监控。
- 测试:包含单元测试与模拟器,覆盖协商流程、候选处理与状态机行为。
graph TB
subgraph "客户端"
P["Peer<br/>RTCPeerConnection 管理"]
S["Signaling<br/>HTTP/WebSocket 信令"]
L["Logger<br/>日志工具"]
end
subgraph "服务端"
WS["WSSignaling<br/>WebSocket 信令服务器"]
WH["WebSocketHandler<br/>连接组/消息路由"]
DTO["Offer/Answer/Candidate DTO"]
end
P -- "事件: sendoffer/sendanswer/sendcandidate" --> S
S -- "HTTP 轮询" --> P
S -- "WebSocket" --> P
P -- "RTCPeerConnection 事件" --> P
P -- "ontrack/ondatachannel" --> P
P -- "ICE 候选" --> S
S -- "offer/answer/candidate" --> WS
WS --> WH
WH --> DTO
图表来源
- client/src/peer.js:1-188
- client/src/signaling.js:1-292
- src/websocket.ts:1-118
- src/class/websockethandler.ts:1-479
- src/class/offer.ts:1-11
- src/class/answer.ts:1-8
- src/class/candidate.ts:1-12
章节来源
- src/index.ts:1-109
- src/websocket.ts:1-118
- src/class/websockethandler.ts:1-479
- client/src/peer.js:1-188
- client/src/signaling.js:1-292
核心组件
- Peer(客户端)
- 负责创建 RTCPeerConnection、监听 onnegotiationneeded/onicecandidate 等事件、封装 SDP 设置与候选处理、维护协商状态标志位、周期性重发 Offer。
- Signaling(客户端)
- 提供 HTTP 与 WebSocket 两种信令通道,封装 create/delete connection、发送 offer/answer/candidate 与 on-message。
- WSSignaling(服务端)
- 基于 ws 的 WebSocket 服务器,接收并分发 offer/answer/candidate/broadcast/on-message 等消息。
- WebSocketHandler(服务端)
- 实现连接组管理(host/participants)、消息路由、广播与参与者变更通知。
- DTO(服务端)
- Offer/Answer/Candidate 数据模型,承载 SDP 与 ICE 候选元数据。
章节来源
- client/src/peer.js:1-188
- client/src/signaling.js:1-292
- src/websocket.ts:1-118
- src/class/websockethandler.ts:1-479
- src/class/offer.ts:1-11
- src/class/answer.ts:1-8
- src/class/candidate.ts:1-12
架构总览
下面的序列图展示了典型的双向视频通话建立流程:客户端通过信令创建连接、协商 Offer/Answer、交换 ICE 候选并建立连接。
sequenceDiagram
participant C as "客户端 Peer"
participant Sig as "信令层(HTTP/WebSocket)"
participant WS as "WebSocket 服务器"
participant H as "WebSocketHandler"
participant R as "远端 Peer"
C->>Sig : "createConnection(connectionId)"
Sig-->>C : "connect(polite, role, participantId)"
Note right of C : "根据 role 决定极性(polite)"
C->>C : "ontrack/onicecandidate 注册"
C->>C : "addTrack/addTransceiver/createDataChannel"
C->>C : "setLocalDescription() 触发 negotiationneeded"
C-->>Sig : "sendoffer({connectionId, sdp})"
Sig->>WS : "转发 offer"
WS->>H : "onOffer"
H-->>R : "转发 offer(按模式/目标 participantId)"
R->>R : "setRemoteDescription(offer)"
R->>R : "setLocalDescription(answer)"
R-->>Sig : "sendanswer({connectionId, sdp})"
Sig->>WS : "转发 answer"
WS->>H : "onAnswer"
H-->>C : "转发 answer"
loop "ICE 收集循环"
C-->>Sig : "sendcandidate({connectionId, candidate, sdpMid, sdpMLineIndex})"
Sig->>WS : "转发 candidate"
WS->>H : "onCandidate"
H-->>R : "转发 candidate"
R->>R : "addIceCandidate(candidate)"
end
C->>C : "iceConnectionState 变化监控"
alt "失败"
C-->>C : "dispatchEvent(disconnect)"
end
图表来源
- client/src/peer.js:1-188
- client/src/signaling.js:1-292
- src/websocket.ts:1-118
- src/class/websockethandler.ts:208-338
详细组件分析
Peer 组件(RTCPeerConnection 管理)
- 关键职责
- 创建 RTCPeerConnection 并注册 ontrack/ondatachannel/onicecandidate/onnegotiationneeded/ice/信号状态等事件。
- 维护协商状态标志:makingOffer/waitingAnswer/ignoreOffer/srdAnswerPending,避免竞态。
- 周期性重发 Offer,保证对端及时收到未达的 SDP。
- 处理 onGotDescription(Offer/Answer)与 onGotCandidate,执行 setRemoteDescription/setLocalDescription/addIceCandidate。
- 暴露 addTrack/addTransceiver/createDataChannel/getTransceivers/getStats 等操作。
- 极性判断与角色
- 极性(polite)在客户端创建连接时由服务端返回;服务端在私有模式下区分 host 与 participant,从而决定 polite 标记。
- 状态监控
- iceConnectionState=failed 时派发 disconnect 事件;signalingState/iceGatheringState 变化被记录与观察。
classDiagram
class Peer {
+connectionId : string
+polite : boolean
+pc : RTCPeerConnection
+makingOffer : boolean
+waitingAnswer : boolean
+ignoreOffer : boolean
+srdAnswerPending : boolean
+constructor(connectionId, polite, config, resendIntervalMsec)
+addTrack(connectionId, track)
+addTransceiver(connectionId, trackOrKind, init)
+createDataChannel(connectionId, label)
+getTransceivers(connectionId)
+getStats(connectionId)
+onGotDescription(connectionId, description)
+onGotCandidate(connectionId, candidate)
+close()
}
图表来源
章节来源
- client/src/peer.js:1-188
- client/test/peerconnection.test.js:1-251
- client/test/peerconnectionmock.js:1-316
SDP 协商流程(Offer/Answer 模型)
- Offer/Answer 模式
- 客户端在 negotiationneeded 时 setLocalDescription 生成 Offer;若收到对端 Offer,则 setRemoteDescription 后 setLocalDescription 生成 Answer。
- 竞态条件处理
- 使用 makingOffer/waitingAnswer/ignoreOffer/srdAnswerPending 标志,避免同时进行多个协商或忽略冲突的 Offer。
- impolite 端在非稳定状态下收到 Offer 且自身正在制作 Offer 时,会标记 ignoreOffer 并丢弃,防止“光竞争”。
- 事件驱动
- Peer 在发送 Offer/Answer 时派发 sendoffer/sendanswer 事件,供信令层转发。
- 对端收到 Answer 后,Peer 派发 negotiated 事件,表示协商完成。
flowchart TD
Start(["开始: negotiationneeded"]) --> SetLD["setLocalDescription() 生成 Offer"]
SetLD --> WaitAnswer["waitingAnswer = true"]
WaitAnswer --> SendOffer["派发 sendoffer 事件"]
SendOffer --> RecvOffer{"收到对端 Offer?"}
RecvOffer --> |是| SetRD["setRemoteDescription(Offer)"]
SetRD --> GenAnswer["setLocalDescription() 生成 Answer"]
GenAnswer --> SendAnswer["派发 sendanswer 事件"]
SendAnswer --> RecvAnswer{"收到对端 Answer?"}
RecvAnswer --> |是| DispatchNegotiated["派发 negotiated 事件"]
RecvAnswer --> |否| LoopOffer["周期性重发 Offer"]
LoopOffer --> RecvAnswer
RecvOffer --> |否| End(["结束"])
DispatchNegotiated --> End
图表来源
章节来源
ICE 候选收集与处理
- 候选收集
- Peer 注册 onicecandidate,当本地 ICE 收集到候选或完成时,派发 sendcandidate 事件。
- 候选处理
- Peer 在收到对端候选后调用 addIceCandidate;若尚未设置远端描述则抛出异常,需等待 Answer 或 Offer 完成后再处理。
- 状态监控
- iceGatheringState/iceConnectionState 变化被记录,iceConnectionState=failed 触发断开事件。
sequenceDiagram
participant P as "本地 Peer"
participant Sig as "信令"
participant R as "远端 Peer"
P->>P : "onicecandidate 收集候选"
P-->>Sig : "sendcandidate({connectionId, candidate, sdpMid, sdpMLineIndex})"
Sig-->>R : "转发 candidate"
R->>R : "addIceCandidate(candidate)"
alt "未设置远端描述"
R->>R : "忽略或报错(等待 Answer/Offer)"
end
图表来源
章节来源
连接生命周期管理
- 建立
- 客户端创建连接(HTTP 或 WebSocket),服务端返回 connect(含 polite/role/participantId)。
- 客户端添加媒体轨道/数据通道,触发 negotiationneeded,发送 Offer。
- 维护
- 循环重发 Offer(默认间隔可配置),持续发送候选,监控 ICE 状态。
- 关闭
- 客户端调用 close() 关闭 RTCPeerConnection;服务端在连接断开时清理组并通知其他成员。
stateDiagram-v2
[*] --> 已连接
已连接 --> 协商中 : "negotiationneeded"
协商中 --> 已连接 : "negotiated/Answer"
已连接 --> 断开 : "iceConnectionState=failed/disconnect"
断开 --> 已连接 : "重新建立"
图表来源
章节来源
服务端 WebSocket 信令与连接组
- WSSignaling
- 监听 connection/message 事件,解析消息类型并交由 WebSocketHandler 处理。
- WebSocketHandler
- 私有模式下维护 connectionGroup(host + participants),按角色与 participantId 路由消息。
- 公共模式下广播 offer/answer/candidate。
- 提供广播、参与者加入/离开通知、心跳(可选)等能力。
graph TB
WS["WSSignaling"] --> WH["WebSocketHandler"]
WH --> CG["connectionGroup<br/>host + participants"]
WH --> DTO["Offer/Answer/Candidate DTO"]
WS --> |offer/answer/candidate| WH
WH --> |按角色/目标转发| 远端["远端客户端"]
图表来源
- src/websocket.ts:1-118
- src/class/websockethandler.ts:1-479
- src/class/offer.ts:1-11
- src/class/answer.ts:1-8
- src/class/candidate.ts:1-12
章节来源
依赖关系分析
- 客户端依赖
- Peer 依赖 RTCPeerConnection 事件模型与浏览器 WebRTC API。
- Signaling 依赖 fetch 或 WebSocket 与服务端交互。
- Logger 提供统一日志输出。
- 服务端依赖
- Express 提供 HTTP 服务;ws 提供 WebSocket 服务。
- WebSocketHandler 作为业务处理器,依赖 DTO 模型。
graph LR
Peer["client/src/peer.js"] --> RTCPeerConnection["浏览器 WebRTC API"]
Signaling["client/src/signaling.js"] --> HTTP["fetch"]
Signaling --> WS["WebSocket"]
WSServer["src/websocket.ts"] --> WSImpl["ws 库"]
WSHandler["src/class/websockethandler.ts"] --> DTO["Offer/Answer/Candidate"]
图表来源
- client/src/peer.js:1-188
- client/src/signaling.js:1-292
- src/websocket.ts:1-118
- src/class/websockethandler.ts:1-479
- src/class/offer.ts:1-11
- src/class/answer.ts:1-8
- src/class/candidate.ts:1-12
章节来源
性能考量
- 候选收集与传输
- 廀候选收集完成后发送 null 候选,避免重复传输;建议在高延迟网络下适当降低重发 Offer 的频率。
- 协商状态机
- 使用标志位严格控制并发协商,减少不必要的 SDP 交换与往返。
- 信令模式
- 公共模式广播可能带来额外带宽压力;私有模式按目标路由更高效。
- 统计与监控
- 客户端可定期获取 getStats 并渲染分辨率等指标,辅助性能优化。
[本节为通用指导,不直接分析具体文件]
故障排查指南
- 常见问题与定位
- ICE 失败:检查 iceConnectionState=failed 事件与网络连通性;确认 STUN/TURN 配置。
- 候选无法添加:确认已设置远端描述;查看 onGotCandidate 的异常分支。
- Offer/Answer 竞态:关注 ignoreOffer 标志与 waitingAnswer;必要时调整重发间隔。
- 日志启用:通过 Logger 工具开启调试日志,观察事件与状态变化。
- 单元测试参考
- 测试覆盖了 addTrack/addTransceiver/createDataChannel 触发协商、重发 Offer、候选处理、状态事件等场景,可据此验证实现。
章节来源
- client/src/logger.js:1-30
- client/test/peerconnection.test.js:1-251
- client/test/peerconnectionmock.js:1-316
结论
本模块通过客户端 Peer 与服务端 WebSocketHandler 的协作,实现了可靠的 RTCPeerConnection 生命周期管理与信令路由。其设计重点在于:
- 明确的极性与角色划分,保障 Offer/Answer 的一致性;
- 严格的协商状态机与标志位,消除竞态;
- 完整的 ICE 候选收集与处理流程;
- 可扩展的私有/公共模式与广播能力。
这些特性共同确保了在复杂网络环境下的稳定连接与良好的用户体验。
[本节为总结性内容,不直接分析具体文件]
附录
关键流程与事件路径
- Offer/Answer 协商
- ICE 候选
- 信令与连接组