Files
2026-05-16 13:37:04 +08:00

447 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 信令系统
<cite>
**本文引用的文件**
- [src/index.ts](file://src/index.ts)
- [src/server.ts](file://src/server.ts)
- [src/signaling.ts](file://src/signaling.ts)
- [src/websocket.ts](file://src/websocket.ts)
- [src/class/websockethandler.ts](file://src/class/websockethandler.ts)
- [src/class/httphandler.ts](file://src/class/httphandler.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)
- [src/class/options.ts](file://src/class/options.ts)
- [src/log.ts](file://src/log.ts)
- [src/服务端接口与WebSocket消息类型.md](file://src/服务端接口与WebSocket消息类型.md)
- [test/websockethandler.test.ts](file://test/websockethandler.test.ts)
- [test/httphandler.test.ts](file://test/httphandler.test.ts)
- [package.json](file://package.json)
</cite>
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考虑](#性能考虑)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本项目是一个支持 HTTP 轮询与 WebSocket 双协议的 WebRTC 信令服务器,提供 Offer/Answer SDP 协商与 ICE 候选者交换能力并内置公共模式与私有模式两种通信拓扑。系统通过会话Session与连接Connection抽象实现消息隔离与路由支持增量拉取、心跳检测与房间广播等特性。
## 项目结构
- 服务启动与配置:应用入口负责解析命令行参数、创建 HTTP/HTTPS 服务器、选择信令协议与通信模式,并初始化 WebSocket 信令服务。
- 服务器装配Express 应用注册日志中间件、CORS、静态资源、Swagger 文档,并挂载 /signaling 路由。
- 信令路由HTTP 路由统一转发至 HTTP 处理器WebSocket 服务器监听连接事件并分派消息到 WebSocket 处理器。
- 数据模型Offer/Answer/Candidate 作为不可变数据载体,承载 SDP 与 ICE 候选者元数据。
- 测试与文档:配套单元测试覆盖公共/私有模式行为与会话超时清理Markdown 提供完整接口与消息类型规范。
```mermaid
graph TB
A["应用入口<br/>src/index.ts"] --> B["Express 服务器<br/>src/server.ts"]
B --> C["HTTP 路由<br/>src/signaling.ts"]
B --> D["WebSocket 服务器<br/>src/websocket.ts"]
C --> E["HTTP 处理器<br/>src/class/httphandler.ts"]
D --> F["WebSocket 处理器<br/>src/class/websockethandler.ts"]
E --> G["数据模型<br/>offer.ts / answer.ts / candidate.ts"]
F --> G
A --> H["日志工具<br/>src/log.ts"]
A --> I["配置选项<br/>src/class/options.ts"]
```
**图表来源**
- [src/index.ts:1-109](file://src/index.ts#L1-L109)
- [src/server.ts:14-89](file://src/server.ts#L14-L89)
- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25)
- [src/websocket.ts:1-118](file://src/websocket.ts#L1-L118)
- [src/class/httphandler.ts:1-120](file://src/class/httphandler.ts#L1-L120)
- [src/class/websockethandler.ts:1-66](file://src/class/websockethandler.ts#L1-L66)
- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11)
- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8)
- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12)
- [src/log.ts:1-51](file://src/log.ts#L1-L51)
- [src/class/options.ts:1-10](file://src/class/options.ts#L1-L10)
**章节来源**
- [src/index.ts:13-109](file://src/index.ts#L13-L109)
- [src/server.ts:14-89](file://src/server.ts#L14-L89)
- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25)
- [src/websocket.ts:6-118](file://src/websocket.ts#L6-L118)
- [src/class/httphandler.ts:1-120](file://src/class/httphandler.ts#L1-L120)
- [src/class/websockethandler.ts:1-66](file://src/class/websockethandler.ts#L1-L66)
## 核心组件
- 应用入口与配置
- 解析端口、HTTPS、协议类型websocket/http、通信模式public/private、日志级别等参数。
- 根据配置创建 HTTP/HTTPS 服务器,并在 websocket 模式下初始化 WebSocket 信令服务。
- Express 服务器
- 注册 Morgan 日志、CORS、JSON/URL 编码中间件。
- 提供 /config、/signaling 路由、静态资源与 Swagger 文档。
- HTTP 信令处理器
- 会话管理:创建/删除会话、检查会话 ID、超时清理。
- 连接管理:创建/删除连接、维护连接对映射。
- SDP 与 ICE存储 offer/answer/candidate按模式进行路由与过滤。
- 增量拉取:支持 fromtime 参数按时间窗口获取消息。
- WebSocket 信令处理器
- 会话与连接组:支持公共模式全连通与私有模式 1 对多房间。
- 消息路由connect/disconnect/offer/answer/candidate/broadcast/on-message/call-request/ping/pong。
- 心跳检测:可选的 ping/pong 心跳(默认未启用)。
- 数据模型
- OfferSDP、时间戳、polite 标志。
- AnswerSDP、时间戳。
- CandidateICE 候选者字符串、sdpMLineIndex、sdpMid、时间戳。
**章节来源**
- [src/index.ts:14-109](file://src/index.ts#L14-L109)
- [src/server.ts:14-89](file://src/server.ts#L14-L89)
- [src/class/httphandler.ts:31-120](file://src/class/httphandler.ts#L31-L120)
- [src/class/websockethandler.ts:10-66](file://src/class/websockethandler.ts#L10-L66)
- [src/class/offer.ts:1-11](file://src/class/offer.ts#L1-L11)
- [src/class/answer.ts:1-8](file://src/class/answer.ts#L1-L8)
- [src/class/candidate.ts:1-12](file://src/class/candidate.ts#L1-L12)
## 架构总览
系统采用“协议无关”的信令处理层设计HTTP 与 WebSocket 分别通过各自的处理器对接同一套业务逻辑与数据模型,从而实现公共/私有模式的统一语义。
```mermaid
graph TB
subgraph "客户端"
WS["WebSocket 客户端"]
HTTP["HTTP 客户端"]
end
subgraph "服务器"
Srv["Express 服务器"]
WSrv["WebSocket 服务器"]
HR["HTTP 处理器"]
WR["WebSocket 处理器"]
DM["数据模型"]
end
WS --> WSrv --> WR
HTTP --> Srv --> HR
WR --> DM
HR --> DM
```
**图表来源**
- [src/server.ts:14-29](file://src/server.ts#L14-L29)
- [src/websocket.ts:15-118](file://src/websocket.ts#L15-L118)
- [src/class/httphandler.ts:107-120](file://src/class/httphandler.ts#L107-L120)
- [src/class/websockethandler.ts:63-66](file://src/class/websockethandler.ts#L63-L66)
## 详细组件分析
### WebSocket 信令处理器
- 会话与连接组
- 使用 Map<WebSocket, Set<string>> 维护每个连接的连接 ID 集合。
- 使用 Map<string, ConnectionGroup> 维护连接组host 与 participants。
- 模式差异
- 公共模式:任意客户端可向其他所有客户端广播消息。
- 私有模式host 与 participants 之间双向路由,支持单播/广播。
- 关键消息处理
- connect/disconnect建立/断开连接维护连接组与角色host/participant
- offer/answer/candidate根据模式与 participantId 进行路由。
- broadcast/on-message组内广播与点对点消息传递。
- ping/pong心跳检测可选
- 广播与路由
- host → 所有 participantsparticipant → host。
- 私有模式支持按 participantId 单播。
```mermaid
sequenceDiagram
participant C1 as "客户端1"
participant C2 as "客户端2"
participant WS as "WebSocket服务器"
participant WH as "WebSocket处理器"
C1->>WS : "connect {connectionId}"
WS->>WH : "onConnect(ws, connectionId)"
WH-->>C1 : "{type : 'connect', polite : false, role : 'host', participantId}"
C2->>WS : "connect {connectionId}"
WS->>WH : "onConnect(ws, connectionId)"
WH-->>C2 : "{type : 'connect', polite : true, role : 'participant', participantId}"
C1->>WS : "offer {connectionId, sdp}"
WS->>WH : "onOffer(ws, data)"
WH-->>C2 : "{type : 'offer', data : {sdp, connectionId, participantId}, participantId}"
C2->>WS : "answer {connectionId, sdp}"
WS->>WH : "onAnswer(ws, data)"
WH-->>C1 : "{type : 'answer', data : {sdp, connectionId, participantId}, participantId}"
C1->>WS : "candidate {connectionId, candidate, sdpMLineIndex, sdpMid}"
WS->>WH : "onCandidate(ws, data)"
WH-->>C2 : "{type : 'candidate', data : {candidate, sdpMLineIndex, sdpMid, connectionId, participantId}, participantId}"
```
**图表来源**
- [src/websocket.ts:27-115](file://src/websocket.ts#L27-L115)
- [src/class/websockethandler.ts:145-338](file://src/class/websockethandler.ts#L145-L338)
**章节来源**
- [src/class/websockethandler.ts:139-338](file://src/class/websockethandler.ts#L139-L338)
- [src/websocket.ts:15-118](file://src/websocket.ts#L15-L118)
### HTTP 信令处理器
- 会话与连接
- 会话 ID 通过请求头 Session-Id 传递;处理器内部维护 clients、connectionPair、offers、answers、candidates、disconnections 映射。
- 私有模式下通过 connectionPair 实现 1 对 1 或 1 对多配对。
- 超时与清理
- lastRequestedTime 记录会话最后请求时间超过阈值10 秒)自动清理会话及其关联资源。
- SDP 与 ICE
- postOffer/postAnswer/postCandidate 存储消息getOffer/getAnswer/getCandidate 支持 fromtime 增量拉取。
- answer 到来时更新对应连接的 candidate 时间戳,确保时序一致性。
- 房间与连接 ID
- 提供 /rooms、/connection-ids 等辅助接口,便于监控与调试。
```mermaid
flowchart TD
Start(["HTTP 请求进入"]) --> CheckSession["校验 Session-Id"]
CheckSession --> Route{"路由到哪类操作?"}
Route --> |创建会话| CreateSession["PUT / -> createSession"]
Route --> |删除会话| DeleteSession["DELETE / -> deleteSession"]
Route --> |创建连接| CreateConn["PUT /connection -> createConnection"]
Route --> |删除连接| DeleteConn["DELETE /connection -> deleteConnection"]
Route --> |发送SDP| PostMsg["POST /offer|answer|candidate -> 存储消息"]
Route --> |拉取消息| GetMsg["GET /offer|answer|candidate| -> 增量返回"]
CreateSession --> End(["返回 sessionId"])
DeleteSession --> End
CreateConn --> End
DeleteConn --> End
PostMsg --> End
GetMsg --> End
```
**图表来源**
- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145)
- [src/class/httphandler.ts:661-696](file://src/class/httphandler.ts#L661-L696)
- [src/class/httphandler.ts:739-783](file://src/class/httphandler.ts#L739-L783)
- [src/class/httphandler.ts:815-828](file://src/class/httphandler.ts#L815-L828)
- [src/class/httphandler.ts:855-886](file://src/class/httphandler.ts#L855-L886)
- [src/class/httphandler.ts:913-952](file://src/class/httphandler.ts#L913-L952)
- [src/class/httphandler.ts:985-998](file://src/class/httphandler.ts#L985-L998)
**章节来源**
- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145)
- [src/class/httphandler.ts:218-232](file://src/class/httphandler.ts#L218-L232)
- [src/class/httphandler.ts:274-297](file://src/class/httphandler.ts#L274-L297)
- [src/class/httphandler.ts:305-318](file://src/class/httphandler.ts#L305-L318)
- [src/class/httphandler.ts:326-356](file://src/class/httphandler.ts#L326-L356)
- [src/class/httphandler.ts:661-696](file://src/class/httphandler.ts#L661-L696)
- [src/class/httphandler.ts:739-783](file://src/class/httphandler.ts#L739-L783)
- [src/class/httphandler.ts:815-828](file://src/class/httphandler.ts#L815-L828)
- [src/class/httphandler.ts:855-886](file://src/class/httphandler.ts#L855-L886)
- [src/class/httphandler.ts:913-952](file://src/class/httphandler.ts#L913-L952)
- [src/class/httphandler.ts:985-998](file://src/class/httphandler.ts#L985-L998)
### Offer/Answer SDP 协商与 ICE 候选者交换
- Offer/Answer
- 公共模式Peer → 所有其他 Peers。
- 私有模式Host ↔ ParticipantsHost 可单播给特定 participant 或广播给所有 participants。
- Candidate
- 与 answer 路由规则一致,支持按 participantId 单播。
- Polite 标志
- 私有模式下host 为 `polite=false`participants 为 `polite=true`,避免并发 offer 冲突。
- 增量拉取
- HTTP 模式支持 fromtime 参数,客户端可增量获取消息,减少网络与存储压力。
```mermaid
sequenceDiagram
participant P1 as "参与者1"
participant P2 as "参与者2"
participant S as "信令服务器"
P1->>S : "POST /signaling/offer {connectionId, sdp}"
S-->>P2 : "GET /signaling/offer?fromtime=... -> {offers : [{connectionId,sdp,polite,...}]}"
P2->>S : "POST /signaling/answer {connectionId, sdp}"
S-->>P1 : "GET /signaling/answer?fromtime=... -> {answers : [{connectionId,sdp,...}]}"
loop "ICE 候选者收集"
P1->>S : "POST /signaling/candidate {connectionId, candidate, sdpMLineIndex, sdpMid}"
S-->>P2 : "GET /signaling/candidate?fromtime=... -> {candidates : [{connectionId,candidate,sdpMLineIndex,sdpMid,...}]}"
P2->>S : "POST /signaling/candidate {connectionId, candidate, sdpMLineIndex, sdpMid}"
S-->>P1 : "GET /signaling/candidate?fromtime=... -> {candidates : [{connectionId,candidate,sdpMLineIndex,sdpMid,...}]}"
end
```
**图表来源**
- [src/class/httphandler.ts:274-297](file://src/class/httphandler.ts#L274-L297)
- [src/class/httphandler.ts:305-318](file://src/class/httphandler.ts#L305-L318)
- [src/class/httphandler.ts:326-356](file://src/class/httphandler.ts#L326-L356)
- [src/class/httphandler.ts:855-886](file://src/class/httphandler.ts#L855-L886)
- [src/class/httphandler.ts:913-952](file://src/class/httphandler.ts#L913-L952)
- [src/class/httphandler.ts:985-998](file://src/class/httphandler.ts#L985-L998)
**章节来源**
- [src/class/httphandler.ts:274-356](file://src/class/httphandler.ts#L274-L356)
- [src/class/httphandler.ts:855-998](file://src/class/httphandler.ts#L855-L998)
### 连接组管理机制(公共模式 vs 私有模式)
- 公共模式
- 任意客户端可向其他所有客户端广播消息。
- 适合全连通场景,消息广播范围广但复杂度低。
- 私有模式
- 以 connectionId 为房间标识host 与 participants 之间双向路由。
- 支持单播与广播host 离开房间即解散participant 离开房间保留。
- 通过 participantId 区分发送者身份,便于路由与 UI 展示。
```mermaid
classDiagram
class WebSocket处理器 {
+reset(mode)
+add(ws)
+remove(ws)
+onConnect(ws, connectionId)
+onDisconnect(ws, connectionId)
+onOffer(ws, message)
+onAnswer(ws, message)
+onCandidate(ws, message)
+onBroadcast(ws, message)
+onMessage(ws, message)
}
class 连接组 {
+host : WebSocket
+participants : Set~WebSocket~
}
WebSocket处理器 --> 连接组 : "维护/路由"
```
**图表来源**
- [src/class/websockethandler.ts:63-66](file://src/class/websockethandler.ts#L63-L66)
- [src/class/websockethandler.ts:27-37](file://src/class/websockethandler.ts#L27-L37)
**章节来源**
- [src/class/websockethandler.ts:139-338](file://src/class/websockethandler.ts#L139-L338)
### 信令消息格式规范
- HTTP REST API
- /signaling、/signaling/offer、/signaling/answer、/signaling/candidate、/signaling/connection、/signaling/connection-ids、/signaling/rooms 等接口的请求与响应结构详见接口文档。
- WebSocket 消息类型
- connect/disconnect/participant-joined/participant-left连接生命周期消息。
- offer/answer/candidateSDP 与 ICE 候选者消息,支持 participantId 与单播路由。
- on-message通用消息传递支持文本/对象。
- broadcast组内广播或全局广播。
- call-request发起呼叫请求。
- ping/pong心跳检测可选
**章节来源**
- [src/服务端接口与WebSocket消息类型.md:35-260](file://src/服务端接口与WebSocket消息类型.md#L35-L260)
- [src/服务端接口与WebSocket消息类型.md:262-487](file://src/服务端接口与WebSocket消息类型.md#L262-L487)
### 错误处理策略与重连机制
- 会话超时
- HTTP 模式下,若 10 秒内无请求,自动清理会话及其资源,避免内存泄漏。
- 会话校验
- HTTP 路由中间件检查 Session-Id不存在则返回 404。
- 私有模式约束
- 连接 ID 在私有模式下需唯一配对;重复使用会返回 400。
- 重连建议
- 客户端应在断开后重新创建会话HTTP或重新建立 WebSocket 连接WebSocket并再次执行 connect 流程。
- 增量拉取 fromtime 可帮助客户端恢复丢失的消息。
**章节来源**
- [src/class/httphandler.ts:218-232](file://src/class/httphandler.ts#L218-L232)
- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145)
- [src/class/httphandler.ts:362-368](file://src/class/httphandler.ts#L362-L368)
### 实际信令流程示例与调试技巧
- 公共模式典型流程
- 客户端 A/B 同时 connect 同一 connectionId服务器广播 offer/answer/candidate。
- 增量拉取 fromtime 可避免重复处理。
- 私有模式典型流程
- 客户端 A 作为 hostB/C 作为 participantsA 发送 offer 给 BB 回答后 A 再发送 answer 给 BICE 候选者双向交换。
- 调试技巧
- 使用 /config 检查服务器配置(是否启用 WebSocket、启动模式、日志级别
- 使用 /signaling/connection-ids 查看活跃连接 ID。
- 使用 /signaling/rooms 查看房间与用户状态。
- 使用 /signaling?fromtime=... 增量拉取消息,定位异常时间窗。
- 开启更详细日志级别以观察消息流转。
**章节来源**
- [src/server.ts:25-41](file://src/server.ts#L25-L41)
- [src/class/httphandler.ts:1041-1076](file://src/class/httphandler.ts#L1041-L1076)
- [src/class/httphandler.ts:1103-1108](file://src/class/httphandler.ts#L1103-L1108)
- [src/服务端接口与WebSocket消息类型.md:508-542](file://src/服务端接口与WebSocket消息类型.md#L508-L542)
## 依赖关系分析
- 外部依赖
- Express、ws、cors、morgan、multer、uuid、swagger 等。
- 内部模块
- index.ts 依赖 server.ts、websocket.ts、websockethandler.ts、httphandler.ts。
- server.ts 依赖 signaling.ts、httphandler.ts。
- signaling.ts 依赖 httphandler.ts。
- websockethandler.ts 依赖 offer.ts、answer.ts、candidate.ts、log.ts。
- httphandler.ts 依赖 offer.ts、answer.ts、candidate.ts、log.ts。
```mermaid
graph LR
IDX["index.ts"] --> SRV["server.ts"]
IDX --> WSS["websocket.ts"]
SRV --> SIG["signaling.ts"]
SIG --> HTH["httphandler.ts"]
WSS --> WSH["websockethandler.ts"]
WSH --> OFF["offer.ts"]
WSH --> ANS["answer.ts"]
WSH --> CAN["candidate.ts"]
HTH --> OFF
HTH --> ANS
HTH --> CAN
```
**图表来源**
- [src/index.ts:7-10](file://src/index.ts#L7-L10)
- [src/server.ts:1-12](file://src/server.ts#L1-L12)
- [src/signaling.ts:1-3](file://src/signaling.ts#L1-L3)
- [src/websocket.ts:1-4](file://src/websocket.ts#L1-L4)
- [src/class/websockethandler.ts:5-8](file://src/class/websockethandler.ts#L5-L8)
- [src/class/httphandler.ts:5-11](file://src/class/httphandler.ts#L5-L11)
**章节来源**
- [package.json:14-27](file://package.json#L14-L27)
- [src/index.ts:1-12](file://src/index.ts#L1-L12)
- [src/server.ts:1-12](file://src/server.ts#L1-L12)
- [src/signaling.ts:1-3](file://src/signaling.ts#L1-L3)
- [src/websocket.ts:1-4](file://src/websocket.ts#L1-L4)
- [src/class/websockethandler.ts:5-8](file://src/class/websockethandler.ts#L5-L8)
- [src/class/httphandler.ts:5-11](file://src/class/httphandler.ts#L5-L11)
## 性能考虑
- HTTP 轮询
- fromtime 增量拉取减少无效数据传输。
- 会话超时清理避免长期占用内存。
- WebSocket
- 按组广播与单播路由降低广播风暴。
- 可选心跳检测防止长连接空闲断开。
- 存储与序列化
- 使用轻量级对象承载 SDP 与 ICE 元数据,避免冗余字段。
- 建议客户端侧缓存最近消息,减少重复请求。
## 故障排查指南
- 无法获取会话
- 确认请求头是否包含正确的 Session-Id。
- 检查会话是否因超时被清理。
- 私有模式连接冲突
- 确认 connectionId 未被重复使用。
- 检查 host/participant 角色与 polite 标志。
- 消息未到达
- 使用 /signaling?fromtime=... 增量拉取确认消息是否已存储。
- 检查模式配置public/private与路由规则。
- 日志与监控
- 提升日志级别以观察消息流转。
- 使用 /signaling/rooms 与 /signaling/connection-ids 快速定位问题。
**章节来源**
- [src/class/httphandler.ts:128-145](file://src/class/httphandler.ts#L128-L145)
- [src/class/httphandler.ts:218-232](file://src/class/httphandler.ts#L218-L232)
- [src/class/httphandler.ts:362-368](file://src/class/httphandler.ts#L362-L368)
- [src/log.ts:15-24](file://src/log.ts#L15-L24)
## 结论
本信令系统通过统一的数据模型与路由策略,在 HTTP 与 WebSocket 两种协议下实现了公共/私有两种通信模式的一致语义。Offer/Answer 与 ICE 候选者交换流程清晰,配合增量拉取与会话超时清理,满足实时性与稳定性需求。建议在生产环境中结合日志与监控接口进行持续观测与优化。
## 附录
- 启动参数与脚本
- 通过命令行参数或环境变量配置端口、HTTPS、协议类型、通信模式与日志级别。
- 提供 npm scripts 用于开发与打包。
**章节来源**
- [src/index.ts:20-42](file://src/index.ts#L20-L42)
- [package.json:5-13](file://package.json#L5-L13)