# 信令客户端
**本文引用的文件**
- [client/src/signaling.js](file://client/src/signaling.js)
- [client/test/signaling.test.js](file://client/test/signaling.test.js)
- [client/test/mocksignaling.js](file://client/test/mocksignaling.js)
- [client/src/logger.js](file://client/src/logger.js)
- [src/signaling.ts](file://src/signaling.ts)
- [src/class/httphandler.ts](file://src/class/httphandler.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)
- [src/server.ts](file://src/server.ts)
- [src/index.ts](file://src/index.ts)
- [package.json](file://package.json)
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文件系统性地阐述信令客户端模块的实现机制与协议设计,覆盖以下主题:
- 与服务器的连接建立、消息收发与轮询拉取流程
- 信令协议的消息格式、事件类型与状态同步机制
- WebSocket 与 HTTP 轮询两种信令模式的实现差异与适用场景
- 客户端状态管理(连接状态监控、重连与错误处理)
- 实战示例(以“代码片段路径”形式给出,避免直接粘贴源码)
## 项目结构
前端信令客户端位于 client/src/signaling.js,提供两类信令适配器:
- HTTP 轮询适配器:基于 fetch 的 GET/PUT/POST/DELETE 接口,周期性拉取消息
- WebSocket 适配器:基于原生 WebSocket,实时推送消息
后端信令服务位于 src/signaling.ts 与 src/class/httphandler.ts(HTTP)及 src/websocket.ts 与 src/class/websockethandler.ts(WebSocket),二者均通过统一的事件模型驱动前端。
```mermaid
graph TB
subgraph "前端"
FE_HTTP["HTTP 信令客户端
client/src/signaling.js"]
FE_WS["WebSocket 信令客户端
client/src/signaling.js"]
FE_LOGGER["日志工具
client/src/logger.js"]
end
subgraph "后端"
BE_ROUTER["HTTP 路由
src/signaling.ts"]
BE_HTTP_HANDLER["HTTP 处理器
src/class/httphandler.ts"]
BE_WS_SERVER["WebSocket 服务器
src/websocket.ts"]
BE_WS_HANDLER["WebSocket 处理器
src/class/websockethandler.ts"]
end
FE_HTTP --> BE_ROUTER
FE_WS --> BE_WS_SERVER
BE_ROUTER --> BE_HTTP_HANDLER
BE_WS_SERVER --> BE_WS_HANDLER
FE_HTTP --- FE_LOGGER
FE_WS --- FE_LOGGER
```
**图表来源**
- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292)
- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25)
- [src/class/httphandler.ts:1-800](file://src/class/httphandler.ts#L1-L800)
- [src/websocket.ts:1-118](file://src/websocket.ts#L1-L118)
- [src/class/websockethandler.ts:1-479](file://src/class/websockethandler.ts#L1-L479)
**章节来源**
- [client/src/signaling.js:1-292](file://client/src/signaling.js#L1-L292)
- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25)
- [src/server.ts:1-90](file://src/server.ts#L1-L90)
- [src/index.ts:1-109](file://src/index.ts#L1-L109)
## 核心组件
- HTTP 信令客户端(Signaling 类)
- 负责会话创建/删除、连接建立/断开、offer/answer/candidate/on-message 的发送与轮询接收
- 通过自定义事件分发消息(connect/disconnect/offer/answer/candidate/on-message)
- WebSocket 信令客户端(WebSocketSignaling 类)
- 负责 WebSocket 连接生命周期、消息发送与事件分发
- 支持 participant-joined/participant-left/broadcast 等扩展事件
- 日志工具(Logger)
- 提供启用/禁用与多种级别输出,便于调试
**章节来源**
- [client/src/signaling.js:3-150](file://client/src/signaling.js#L3-L150)
- [client/src/signaling.js:152-292](file://client/src/signaling.js#L152-L292)
- [client/src/logger.js:1-30](file://client/src/logger.js#L1-L30)
## 架构总览
信令客户端与服务端通过两种模式协作:
- HTTP 轮询:客户端周期性 GET /signaling 拉取消息;服务端按会话聚合 offer/answer/candidate/connect/disconnect 并按时间戳排序返回
- WebSocket:客户端直连 WebSocket,服务端按连接组(host/participants)进行消息路由与广播
```mermaid
sequenceDiagram
participant C as "信令客户端"
participant S as "HTTP 服务端"
participant H as "HTTP 处理器"
C->>S : PUT /signaling (创建会话)
S->>H : createSession()
H-->>S : { sessionId }
S-->>C : { sessionId }
C->>S : PUT /signaling/connection (建立连接)
S->>H : createConnection()
H-->>S : { connectionId, polite }
S-->>C : { connect ... }
loop 轮询
C->>S : GET /signaling?fromtime=...
S->>H : getAll(fromtime)
H-->>S : { messages[], datetime }
S-->>C : { messages[], datetime }
C-->>C : 分发事件 (offer/answer/candidate/disconnect)
end
C->>S : DELETE /signaling (删除会话)
S->>H : deleteSession()
H-->>S : 200
S-->>C : 200
```
**图表来源**
- [client/src/signaling.js:30-97](file://client/src/signaling.js#L30-L97)
- [src/signaling.ts:15-18](file://src/signaling.ts#L15-L18)
- [src/class/httphandler.ts:615-641](file://src/class/httphandler.ts#L615-L641)
**章节来源**
- [client/src/signaling.js:30-149](file://client/src/signaling.js#L30-L149)
- [src/class/httphandler.ts:615-641](file://src/class/httphandler.ts#L615-L641)
## 详细组件分析
### HTTP 信令客户端(Signaling 类)
- 会话管理
- start():若未获取 sessionId,循环 PUT /signaling 创建会话,成功后进入轮询
- stop():DELETE /signaling 清理会话并重置 sessionId
- 连接管理
- createConnection()/deleteConnection():PUT/DELETE /signaling/connection
- 媒体协商
- sendOffer()/sendAnswer()/sendCandidate():POST /signaling/offer|answer|candidate
- 通用消息
- sendMessage():POST /signaling/on-message
- 消息拉取
- loopGetAll():GET /signaling?fromtime=lastTime,解析 messages 数组并分发事件
- 事件分发
- disconnect/offer/answer/candidate/on-message 等事件通过 CustomEvent 派发
```mermaid
classDiagram
class Signaling {
+running boolean
+interval number
+sessionId string
+headers() object
+url(method, parameter) string
+start() void
+stop() void
+createConnection(connectionId) Promise
+deleteConnection(connectionId) Promise
+sendOffer(connectionId, sdp) Promise
+sendAnswer(connectionId, sdp) Promise
+sendCandidate(connectionId, candidate, sdpMid, sdpMLineIndex) Promise
+sendMessage(connectionId, message) Promise
+getAll(fromTime) Promise
-loopGetAll() void
}
```
**图表来源**
- [client/src/signaling.js:3-150](file://client/src/signaling.js#L3-L150)
**章节来源**
- [client/src/signaling.js:30-149](file://client/src/signaling.js#L30-L149)
### WebSocket 信令客户端(WebSocketSignaling 类)
- 连接建立
- 构造函数根据协议选择 ws/wss,监听 open/close/message
- start():等待 isWsOpen 为真
- stop():关闭连接并等待 isWsOpen 为假
- 消息发送
- createConnection()/deleteConnection()
- sendOffer()/sendAnswer()/sendCandidate()
- sendMessage():封装 type:"on-message" 数据
- 消息接收与事件分发
- onmessage:解析 JSON,按 type 分派 connect/disconnect/offer/answer/candidate/on-message/participant-joined/participant-left/broadcast
```mermaid
sequenceDiagram
participant C as "WebSocket 客户端"
participant WS as "WebSocket 服务端"
participant WH as "WebSocket 处理器"
C->>WS : CONNECT ws : //host
WS-->>C : onopen
C->>WS : {"type" : "connect","connectionId" : id}
WS->>WH : onConnect(ws, id)
WH-->>WS : 广播/通知
WS-->>C : {"type" : "connect", ...}
C->>WS : {"type" : "offer","from" : id,"data" : {"sdp",...}}
WS->>WH : onOffer(ws, data)
WH-->>WS : 路由/广播
WS-->>C : {"type" : "offer", ...}
C->>WS : {"type" : "on-message", "data" : {...}}
WS->>WH : onMessage(ws, data)
WH-->>WS : 转发
WS-->>C : {"type" : "on-message", ...}
C->>WS : CLOSE
WS-->>C : onclose
```
**图表来源**
- [client/src/signaling.js:152-292](file://client/src/signaling.js#L152-L292)
- [src/websocket.ts:27-115](file://src/websocket.ts#L27-L115)
- [src/class/websockethandler.ts:145-338](file://src/class/websockethandler.ts#L145-L338)
**章节来源**
- [client/src/signaling.js:152-292](file://client/src/signaling.js#L152-L292)
- [src/websocket.ts:27-115](file://src/websocket.ts#L27-L115)
- [src/class/websockethandler.ts:145-338](file://src/class/websockethandler.ts#L145-L338)
### 信令协议与消息格式
- HTTP 模式
- 会话:PUT /signaling -> { sessionId }
- 连接:PUT /signaling/connection -> { connectionId, polite }
- 消息:GET /signaling?fromtime=... -> { messages[], datetime }
- 消息类型:connect/disconnect/offer/answer/candidate/on-message
- offer/answer 字段:connectionId, sdp, polite(可选)
- candidate 字段:connectionId, candidate, sdpMLineIndex, sdpMid
- WebSocket 模式
- 连接:{"type":"connect","connectionId":id}
- 信令:{"type":"offer|answer|candidate","from":id,"data":{...},"participantId":...}
- 普通消息:{"type":"on-message","data":{...}}
- 扩展事件:participant-joined/participant-left/broadcast
```mermaid
flowchart TD
A["HTTP 消息聚合"] --> B["按时间戳排序"]
B --> C["分发事件:offer/answer/candidate/disconnect"]
D["WebSocket 消息路由"] --> E["按连接组(host/participants)转发"]
E --> F["广播/定向转发"]
```
**图表来源**
- [src/class/httphandler.ts:615-641](file://src/class/httphandler.ts#L615-L641)
- [src/class/websockethandler.ts:97-109](file://src/class/websockethandler.ts#L97-L109)
**章节来源**
- [src/class/httphandler.ts:398-641](file://src/class/httphandler.ts#L398-L641)
- [src/class/websockethandler.ts:214-338](file://src/class/websockethandler.ts#L214-L338)
### 状态管理与重连策略
- HTTP 轮询
- start() 中若 sessionId 为空则持续尝试创建会话,间隔由 interval 控制
- loopGetAll() 中每次拉取后更新 lastTimeRequest,避免重复消费
- stop() 显式清理会话
- WebSocket
- 构造函数监听 open/close,start() 等待连接就绪
- 建议在 onclose 中实现指数退避重连(可在上层封装)
- 错误处理
- on-message 解析失败时记录错误日志
- HTTP 模式下 404/参数缺失等错误需上层捕获并提示
**章节来源**
- [client/src/signaling.js:30-97](file://client/src/signaling.js#L30-L97)
- [client/src/signaling.js:152-241](file://client/src/signaling.js#L152-L241)
- [client/src/logger.js:27-29](file://client/src/logger.js#L27-L29)
### 代码示例(以路径引用代替代码)
- 如何监听并处理 on-message 事件
- [client/src/signaling.js:74-83](file://client/src/signaling.js#L74-L83)
- 如何发送 offer/answer/candidate
- [client/src/signaling.js:117-138](file://client/src/signaling.js#L117-L138)
- [client/src/signaling.js:255-279](file://client/src/signaling.js#L255-L279)
- 如何启动/停止 HTTP 信令客户端
- [client/src/signaling.js:30-47](file://client/src/signaling.js#L30-L47)
- [client/src/signaling.js:93-97](file://client/src/signaling.js#L93-L97)
- 如何启动/停止 WebSocket 信令客户端
- [client/src/signaling.js:230-241](file://client/src/signaling.js#L230-L241)
- [client/src/signaling.js:236-241](file://client/src/signaling.js#L236-L241)
- 如何实现消息队列(建议思路)
- 使用内存队列暂存未处理的 on-message,按顺序消费并回调处理函数
- 参考事件分发位置:[client/src/signaling.js:74-83](file://client/src/signaling.js#L74-L83)
- 如何调试信令通信
- 启用日志:[client/src/logger.js:3-9](file://client/src/logger.js#L3-L9)
- 观察网络请求与响应:[client/src/signaling.js:30-149](file://client/src/signaling.js#L30-L149)
**章节来源**
- [client/src/signaling.js:30-149](file://client/src/signaling.js#L30-L149)
- [client/src/logger.js:3-9](file://client/src/logger.js#L3-L9)
## 依赖关系分析
- 前端依赖
- client/src/signaling.js 依赖 client/src/logger.js
- 测试依赖 client/test/signaling.test.js 与 client/test/mocksignaling.js
- 后端依赖
- src/server.ts 注册 /signaling 路由并挂载 HTTP 与 WebSocket 信令
- src/signaling.ts 定义路由与鉴权中间件(会话校验)
- HTTP 侧:src/class/httphandler.ts 负责会话/连接/消息持久化与聚合
- WebSocket 侧:src/websocket.ts 与 src/class/websockethandler.ts 负责连接组与消息路由
```mermaid
graph LR
P["package.json"] --> FE["client/src/signaling.js"]
P --> BE_S["src/server.ts"]
BE_S --> BE_R["src/signaling.ts"]
BE_R --> BE_H["src/class/httphandler.ts"]
BE_S --> BE_W["src/websocket.ts"]
BE_W --> BE_WH["src/class/websockethandler.ts"]
FE --> LOG["client/src/logger.js"]
```
**图表来源**
- [package.json:1-60](file://package.json#L1-L60)
- [src/server.ts:14-29](file://src/server.ts#L14-L29)
- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25)
- [src/class/httphandler.ts:1-120](file://src/class/httphandler.ts#L1-L120)
- [src/websocket.ts:1-21](file://src/websocket.ts#L1-L21)
- [src/class/websockethandler.ts:1-66](file://src/class/websockethandler.ts#L1-L66)
**章节来源**
- [package.json:1-60](file://package.json#L1-L60)
- [src/server.ts:14-29](file://src/server.ts#L14-L29)
- [src/signaling.ts:1-25](file://src/signaling.ts#L1-L25)
## 性能考量
- HTTP 轮询
- 通过 fromtime 参数避免重复拉取,提升效率
- 合理设置 interval,避免过于频繁导致带宽与 CPU 压力
- WebSocket
- 服务端支持心跳检测(注释中可见),可结合客户端 ping/pong 保持长连稳定
- 连接组广播需控制消息规模,避免风暴
- 会话超时
- HTTP 处理器定期清理长时间无请求的会话,降低资源占用
**章节来源**
- [src/class/httphandler.ts:218-232](file://src/class/httphandler.ts#L218-L232)
- [src/class/websockethandler.ts:404-430](file://src/class/websockethandler.ts#L404-L430)
## 故障排查指南
- 无法获取 sessionId
- 确认 start() 已调用且网络可达
- 查看控制台日志与网络面板
- 参考:[client/src/signaling.js:30-47](file://client/src/signaling.js#L30-L47)
- on-message 解析失败
- 客户端会记录错误日志,检查消息格式与编码
- 参考:[client/src/signaling.js:74-83](file://client/src/signaling.js#L74-L83)
- WebSocket 连接异常
- 检查协议(ws/wss)、主机与端口配置
- 关注 onclose 回调,必要时实现重连
- 参考:[client/src/signaling.js:169-176](file://client/src/signaling.js#L169-L176)
- 私有模式下未收到对端 offer/answer
- 确认双方已建立相同 connectionId 的连接
- 参考测试用例对私有模式行为的断言
- 参考:[client/test/signaling.test.js:214-434](file://client/test/signaling.test.js#L214-L434)
**章节来源**
- [client/src/signaling.js:30-97](file://client/src/signaling.js#L30-L97)
- [client/src/signaling.js:152-241](file://client/src/signaling.js#L152-L241)
- [client/test/signaling.test.js:214-434](file://client/test/signaling.test.js#L214-L434)
## 结论
本信令客户端模块提供了统一的事件接口,兼容 HTTP 轮询与 WebSocket 两种模式。HTTP 模式适合受限网络环境,WebSocket 模式具备更低延迟与更丰富的连接组能力。通过清晰的事件分发与日志工具,开发者可以快速集成并调试信令流程。
## 附录
- 数据模型(Offer/Answer/Candidate)
- Offer:sdp、datetime、polite
- Answer:sdp、datetime
- Candidate:candidate、sdpMLineIndex、sdpMid、datetime
- 参考:
- [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)