客户端A 呼叫 客户端B 完整时序图
0. 用户注册流程
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 用户注册流程 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
客户端 A TCP 服务器 客户端 B
│ │ │
│ 1. 用户登录成功 │ │
│ MainActivity.startTcpClientForCurrentUser()│ │
│ │ │
│ 2. 启动 TcpSignalingClient │ │
│ - DNS 解析 │ │
│ - 建立 TCP 连接 │ │
│ │ │
│ 3. 启动接收线程 │ │
│ Thread { startReceiving() }.start() │ │
│ receivingStartedLatch.countDown() │ │
│ │ │
│ 4. 发送 register 消息 │ │
│ {"type":"register", │ │
│ "sender_id":"A", │ │
│ "data":""} │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ │ 5. 记录用户连接 │
│ │ - 存储 client_id = "A" │
│ │ - 更新在线状态 │
│ │ - 记录连接时间 │
│ │ │
│ 6. register_response │ │
│ {"type":"register_response", │ │
│ "sender_id":"server", │ │
│ "receiver_id":"A", │ │
│ "data":"Registration successful"} │ │
│ <───────────────────────────────────────────────│ │
│ │ │
│ 7. 注册成功后操作 │ │
│ - queryOnlineUsers() │ │
│ - heartbeatManager.start(90) │ │
│ - getStunTurnConfig() │ │
│ │ │
│ 8. query_online_users │ │
│ {"type":"query_online_users", │ │
│ "sender_id":"A"} │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ 9. query_online_users_response │ │
│ {"type":"query_online_users_response", │ │
│ "data":["A", "B", "C"]} │ │
│ <───────────────────────────────────────────────│ │
│ │ │
│ 10. 更新用户列表显示 │ │
│ │ │
▼ ▼ ▼
1. 呼叫建立流程
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ A 呼叫 B - 建立连接 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
客户端 A (主叫) 服务器 客户端 B (被叫)
│ │ │
│ 1. 用户点击呼叫B │ │
│ UserListFragment.callUser(B) │ │
│ ────────────────────> │ │
│ │ │
│ 2. 创建 VideoCallActivity │ │
│ intent.putExtra("callee_name", "B") │ │
│ intent.putExtra("isIncomingCall", false) │ │
│ │ │
│ 3. VideoCallActivity.onCreate() │ │
│ - isCallEnded = false │ │
│ - isCleanedUp = false │ │
│ - isRemoteHangup = false │ │
│ - initWebRTC() │ │
│ │ │
│ 4. WebRTC 预初始化检查 │ │
│ WebRTCPreInitializer.isPreInitialized() │ │
│ - 已初始化: 直接调用 setupWebRTC() │ │
│ - 未初始化: 等待初始化完成 │ │
│ │ │
│ 5. setupWebRTC() │ │
│ - initSurfaceViewRenderers() │ │
│ - 创建 WebRTCClient │ │
│ - setupTcpListeners() │ │
│ │ │
│ 6. get_stun_turn_config │ │
│ {"type":"get_stun_turn_config", │ │
│ "sender_id":"A"} │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ 7. stun/turn 配置响应 │ │
│ {"type":"get_stun_turn_config_response", │ │
│ "data":"{\"stun\":{...},\"turn\":{...}}"} │ │
│ <───────────────────────────────────────────────│ │
│ │ │
│ 8. 创建 PeerConnection │ │
│ webRTCClient.createPeerConnection(config) │ │
│ - 配置 ICE 服务器 │ │
│ - 设置回调监听 │ │
│ │ │
│ 9. 创建本地媒体流 │ │
│ webRTCClient.createLocalMediaStream() │ │
│ - 创建音频轨道 │ │
│ - 创建视频轨道 │ │
│ - 触发 onLocalMediaStreamReady 回调 │ │
│ │ │
│ 10. 显示本地视频 │ │
│ videoTrack.addSink(localSurfaceViewRenderer) │
│ │ │
│ 11. 创建 Offer (主叫方) │ │
│ webRTCClient.createOffer() │ │
│ - setLocalDescription(offer) │ │
│ - 开始收集 ICE Candidates │ │
│ │ │
│ 12. webrtc_sdp (offer) │ │
│ {"type":"webrtc_sdp", │ │
│ "sender_id":"A", │ │
│ "receiver_id":"B", │ │
│ "data_type":"offer", │ │
│ "data":"v=0..."} │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ │ 13. 转发 webrtc_sdp (offer) │
│ │ ────────────────────────────────────────>│
│ │ │
│ │ │ 14. 手机响铃
│ │ │ 收到 call_request
│ │ │
│ │ │ 15. 用户点击接听
│ │ │
│ │ │ 16. 创建 VideoCallActivity
│ │ │ - isIncomingCall = true
│ │ │ - isCallEnded = false
│ │ │ - initWebRTC()
│ │ │
│ │ 17. get_stun_turn_config │ │
│ │ <───────────────────────────────────────│
│ │ │
│ │ 18. stun/turn 配置响应 │ │
│ │ ────────────────────────────────────────>│
│ │ │
│ │ │ 19. 创建 PeerConnection
│ │ │ 创建本地媒体流
│ │ │
│ │ │ 20. 处理远程 Offer
│ │ │ handleRemoteSdp(A, "offer", sdp)
│ │ │ - setRemoteDescription(offer)
│ │ │
│ │ │ 21. 创建 Answer (被叫方)
│ │ │ webRTCClient.createAnswer()
│ │ │ - setLocalDescription(answer)
│ │ │
│ │ 22. webrtc_sdp (answer) │
│ │ {"type":"webrtc_sdp", │
│ │ "sender_id":"B", │
│ │ "receiver_id":"A", │
│ │ "data_type":"answer", │
│ │ "data":"v=0..."} │
│ │ <───────────────────────────────────────│
│ │ │
│ 23. 转发 webrtc_sdp (answer) │ │
│ <───────────────────────────────────────────────│ │
│ │ │
│ 24. 处理远程 Answer │ │
│ handleRemoteSdp(B, "answer", sdp) │ │
│ - setRemoteDescription(answer) │ │
│ │ │
▼ ▼ ▼
2. ICE Candidate 交换
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ ICE Candidate 交换 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
客户端 A 服务器 客户端 B
│ │ │
│ ═════════════════════════════════════════════════════════════════════════════════════│
│ ICE Candidate 收集触发条件: │
│ - setLocalDescription() 调用后开始收集 │
│ - 收集类型: host (本地), srflx (反射), relay (中继) │
│ ═════════════════════════════════════════════════════════════════════════════════════│
│ │ │
│ 1. 收集到 ICE Candidate │ │
│ PeerConnection.Observer.onIceCandidate() │ │
│ │ │
│ 2. 检查 SDP 监听器是否就绪 │ │
│ if (isSdpListenerReady) { │ │
│ 直接发送 │ │
│ } else { │ │
│ 缓存到 pendingIceCandidates │ │
│ } │ │
│ │ │
│ 3. webrtc_ice_candidate │ │
│ {"type":"webrtc_ice_candidate", │ │
│ "sender_id":"A", │ │
│ "receiver_id":"B", │ │
│ "data":"{\"sdpMid\":\"0\", │ │
│ \"sdpMLineIndex\":0, │ │
│ \"candidate\":\"candidate:...\"}"} │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ │ 4. 转发 ICE Candidate │
│ │ ────────────────────────────────────────>│
│ │ │
│ │ │ 5. 检查远程描述是否已设置
│ │ │ if (isRemoteDescriptionSet) {
│ │ │ 直接添加
│ │ │ } else {
│ │ │ 缓存到 pendingIceCandidates
│ │ │ }
│ │ │
│ │ │ 6. 添加 ICE Candidate
│ │ │ peerConnection.addIceCandidate(candidate)
│ │ │
│ │ 7. B 的 ICE Candidate │
│ │ <───────────────────────────────────────│
│ │ │
│ 8. 转发 B 的 ICE Candidate │ │
│ <───────────────────────────────────────────────│ │
│ │ │
│ 9. 添加 ICE Candidate │ │
│ │ │
│ ═════════════════════════════════════════════════════════════════════════════════════│
│ 重复步骤 1-9,直到所有 ICE Candidate 交换完成 │
│ 收集完成标志: IceGatheringState = COMPLETE │
│ ═════════════════════════════════════════════════════════════════════════════════════│
│ │ │
▼ ▼ ▼
3. 连接建立成功
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ WebRTC 连接建立成功 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
客户端 A 服务器 客户端 B
│ │ │
│ ═════════════════════════════════════════════════════════════════════════════════════│
│ ICE 连接状态变化: │
│ checking → connected → completed │
│ ═════════════════════════════════════════════════════════════════════════════════════│
│ │ │
│ 1. ICE 连接状态变化 │ │
│ onIceConnectionChange(CONNECTED) │ │
│ │ │
│ 2. 更新 UI 状态 │ │
│ tvCallStatus.text = "通话中" │ │
│ │ │
│ 3. 接收远程媒体流 │ │
│ onAddStream(stream) │ │
│ stream.videoTracks[0].addSink(remoteSurfaceViewRenderer) │
│ │ │
│ ╔═══════════════════════════════════════════════════════════════════════════════════╗│
│ ║ ║│
│ ║ PeerConnection.IceConnectionState = CONNECTED ║│
│ ║ ║│
│ ║ - 音视频通道建立成功 ║│
│ ║ - 双方可以进行实时通话 ║│
│ ║ - 本地视频显示在 localSurfaceViewRenderer ║│
│ ║ - 远程视频显示在 remoteSurfaceViewRenderer ║│
│ ║ ║│
│ ╚═══════════════════════════════════════════════════════════════════════════════════╝│
│ │ │
│ │ │
│ <══════════════════════ 音视频数据传输 ══════════════════════════════════════════> │
│ │ │
│ (P2P 直连,不经过服务器) │ │
│ │ │
▼ ▼ ▼
4. 挂断流程
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 挂断流程 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
客户端 A (挂断方) 服务器 客户端 B (被叫方)
│ │ │
│ 1. 用户点击挂断按钮 │ │
│ btnEndCall.setOnClickListener │ │
│ ────────────────────> │ │
│ │ │
│ 2. endCall() │ │
│ if (isCallEnded) return // 防重复 │ │
│ isCallEnded = true │ │
│ │ │
│ 3. 检查是否远程挂断 │ │
│ if (!isRemoteHangup) { │ │
│ sendCallStop(otherUserId) │ │
│ } │ │
│ │ │
│ 4. call_stop (仅本地挂断时发送) │ │
│ {"type":"call_stop", │ │
│ "sender_id":"A", │ │
│ "receiver_id":"B", │ │
│ "data":""} │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ │ 5. 转发 call_stop │
│ │ ────────────────────────────────────────>│
│ │ │
│ 6. cleanup() │ │ 7. 收到 call_stop
│ if (isCleanedUp) return │ │ isRemoteHangup = true
│ isCleanedUp = true │ │ endCall()
│ │ │
│ 7. 清理 TCP 回调 │ │ 8. cleanup()
│ tcpClient.onWebRTCSdpReceived = null │ │ (同左侧步骤)
│ tcpClient.onWebRTCIceCandidateReceived = null │
│ tcpClient.onCallStopReceived = null │ │
│ tcpClient.setSdpListenerReady(false) │ │
│ │ │
│ 8. 清理视频渲染器 │ │
│ videoTrack.removeSink(localSurfaceViewRenderer) │
│ localSurfaceViewRenderer.clearImage() │ │
│ localSurfaceViewRenderer.release() │ │
│ remoteSurfaceViewRenderer.release() │ │
│ │ │
│ 9. 关闭 WebRTC 客户端 │ │
│ webRTCClient.close() │ │
│ - videoCapturer.stopCapture() │ │
│ - videoCapturer.dispose() │ │
│ - videoSource.dispose() │ │
│ - audioSource.dispose() │ │
│ - peerConnection.close() │ │
│ - iceCandidateManager.clear() │ │
│ - sdpManager.clear() │ │
│ - 所有回调设为 null │ │
│ │ │
│ 10. 恢复音频设置 │ │
│ audioManager.mode = MODE_NORMAL │ │
│ audioManager.isSpeakerphoneOn = false │ │
│ │ │
│ 11. finish() 返回主界面 │ │
│ │ │
▼ ▼ ▼
5. 重复呼叫流程
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 重复呼叫流程 (第二次呼叫) │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
客户端 A 服务器 客户端 B
│ │ │
│ 1. 第一次通话结束 │ │
│ cleanup() 已完成 │ │
│ - isCleanedUp = true │ │
│ - webRTCClient = null │ │
│ - SurfaceViewRenderer 已 release │ │
│ │ │
│ 2. 用户再次点击呼叫 B │ │
│ ────────────────────> │ │
│ │ │
│ 3. 创建新的 VideoCallActivity 实例 │ │
│ - 新的 hashCode │ │
│ - isCallEnded = false (新实例) │ │
│ - isCleanedUp = false (新实例) │ │
│ - isRemoteHangup = false (新实例) │ │
│ │ │
│ 4. WebRTC 预初始化 (单例复用) │ │
│ WebRTCPreInitializer.isPreInitialized() │ │
│ = true (已初始化,直接使用) │ │
│ - PeerConnectionFactory 复用 │ │
│ - EglBase 复用 │ │
│ │ │
│ 5. initSurfaceViewRenderers() │ │
│ try { localSurfaceViewRenderer.release() } │ │
│ try { remoteSurfaceViewRenderer.release() }│ │
│ localSurfaceViewRenderer.init(eglBaseContext) │
│ remoteSurfaceViewRenderer.init(eglBaseContext) │
│ │ │
│ 6. 创建新的 WebRTCClient 实例 │ │
│ - 新的 hashCode │ │
│ - 新的 executor │ │
│ - 新的 PeerConnectionManager │ │
│ │ │
│ 7. 正常执行呼叫流程 (同第1节) │ │
│ - createPeerConnection() │ │
│ - createLocalMediaStream() │ │
│ - createOffer() │ │
│ ... │ │
│ │ │
▼ ▼ ▼
6. 状态标志说明
6.1 VideoCallActivity 状态标志
| 标志 | 初始值 | 说明 |
|---|---|---|
isCallEnded |
false |
通话是否已结束,防止重复处理挂断 |
isCleanedUp |
false |
资源是否已清理,防止重复清理 |
isRemoteHangup |
false |
是否为远程挂断,用于判断是否发送 call_stop |
isIncomingCall |
intent | 是否为来电,决定 SDP 角色 (offer/answer) |
6.2 WebRTCClient 状态标志
| 标志 | 说明 |
|---|---|
peerConnectionFactory |
来自单例,不应在 close() 中清空 |
eglBase |
来自单例,不应在 close() 中清空 |
executor |
每个实例独立,自动管理生命周期 |
6.3 SdpManager 状态标志
| 标志 | 说明 |
|---|---|
isPeerConnectionReady |
PeerConnection 是否已创建 |
pendingSdpMessages |
缓存未处理的 SDP 消息 |
6.4 IceCandidateManager 状态标志
| 标志 | 说明 |
|---|---|
isRemoteDescriptionSet |
远程描述是否已设置 |
pendingIceCandidates |
缓存未添加的 ICE 候选者 |
7. 消息类型汇总
| 消息类型 | 发送方 | 接收方 | 说明 |
|---|---|---|---|
register |
客户端 | 服务器 | 用户注册 |
register_response |
服务器 | 客户端 | 注册响应 |
heartbeat |
客户端 | 服务器 | 心跳保持 |
heartbeat_response |
服务器 | 客户端 | 心跳响应 |
query_online_users |
客户端 | 服务器 | 查询在线用户 |
query_online_users_response |
服务器 | 客户端 | 在线用户列表 |
get_stun_turn_config |
客户端 | 服务器 | 获取 STUN/TURN 配置 |
get_stun_turn_config_response |
服务器 | 客户端 | STUN/TURN 配置 |
call_request |
客户端 | 客户端 | 呼叫请求 |
call_stop |
客户端 | 客户端 | 挂断通知 |
webrtc_sdp |
客户端 | 客户端 | SDP 交换 (offer/answer) |
webrtc_ice_candidate |
客户端 | 客户端 | ICE 候选者交换 |
8. 服务器端处理详解
8.1 服务器架构概述
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 服务器架构 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ main.go │
│ - 启动 TCP 监听器 (端口 3480) │
│ - 加载配置文件 (config.json) │
│ - 初始化全局状态 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
│
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐ ┌─────────────────────────┐
│ client.go │ │ message.go │ │ protocol.go │
│ - handleRegister() │ │ - handleMessage() │ │ - Message 结构体 │
│ - handleHeartbeat() │ │ - isValidMessage() │ │ - TcpPacket 封装 │
│ - cleanupInactive() │ │ - handleSignalForward()│ │ - 消息类型常量 │
│ - reportStatistics() │ │ - handleQueryOnline() │ │ │
└─────────────────────────┘ └─────────────────────────┘ └─────────────────────────┘
全局状态管理:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ gClients map[string]*Client // client_id -> Client │
│ gConnToUsername map[string]string // conn_addr -> client_id │
│ gUsernameToClientID map[string]string // username -> client_id │
│ gClientsMux sync.RWMutex // 读写锁保护全局状态 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
8.2 消息处理流程
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 服务器消息处理流程 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
TCP 连接 消息处理器 响应处理
│ │ │
│ 1. 接收 TCP 数据包 │ │
│ conn.Read(buffer) │ │
│ │ │
│ 2. 解析 TcpPacket │ │
│ - 读取 4 字节长度头 │ │
│ - 读取 JSON 数据体 │ │
│ │ │
│ 3. JSON 反序列化 │ │
│ json.Unmarshal(data, &msg) │ │
│ │ │
│ 4. 消息验证 │ │
│ isValidMessage(&msg) │ │
│ - 检查 type 字段 │ │
│ - 检查 sender_id 长度 │ │
│ - 检查 receiver_id 长度 │ │
│ - 检查 data 长度 │ │
│ │ │
│ 5. 路由到对应处理器 │ │
│ handleMessageType(conn, &msg) │ │
│ ──────────────────────────────────────────>│ │
│ │ │
│ │ 6. 根据消息类型分发 │
│ │ switch msg.Type { │
│ │ case "register": │
│ │ handleRegister() │
│ │ case "heartbeat": │
│ │ handleHeartbeat() │
│ │ case "webrtc_sdp": │
│ │ handleSignalForwardMsg() │
│ │ ... │
│ │ } │
│ │ │
│ │ 7. 执行业务逻辑 │
│ │ - 更新全局状态 │
│ │ - 转发消息 │
│ │ - 构造响应 │
│ │ │
│ 8. 发送响应 │ │
│ <──────────────────────────────────────────│ sendMessage(conn, response) │
│ - 序列化 JSON │ │
│ - 封装 TcpPacket │ │
│ - 写入 TCP 连接 │ │
│ │ │
▼ ▼ ▼
8.3 注册处理详解 (handleRegister)
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 服务器注册处理流程 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
客户端 服务器处理流程 数据存储
│ │ │
│ 1. 发送 register 消息 │ │
│ {"type":"register", │ │
│ "sender_id":"A"} │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ │ 2. 获取全局锁 │
│ │ gClientsMux.Lock() │
│ │ │
│ │ 3. 验证 client_id │
│ │ if clientID == "" { │
│ │ return error │
│ │ } │
│ │ │
│ │ 4. 解析客户端地址 │
│ │ host, port = SplitHostPort() │
│ │ - 提取公网 IP │
│ │ - 提取端口号 │
│ │ │
│ │ 5. 创建 Client 对象 │ ┌─────────────────────┐
│ │ client := &Client{ │ ───> │ gClients["A"] │
│ │ ID: clientID, │ │ ID: "A" │
│ │ Username: clientID, │ │ Username: "A" │
│ │ Conn: conn, │ │ Conn: net.Conn │
│ │ PublicIP: host, │ │ PublicIP: x.x.x.x │
│ │ PublicPort: port, │ │ LastActivity: now │
│ │ LastActivity: time.Now(), │ └─────────────────────┘
│ │ Stats: ClientStats{...}, │
│ │ } │
│ │ │
│ │ 6. 处理重复登录 │ ┌─────────────────────┐
│ │ if exists, old := gClients[id]; │ ───> │ 断开旧连接 │
│ │ old.Conn.Close() │ │ 删除旧映射 │
│ │ delete(gConnToUsername, ...) │ └─────────────────────┘
│ │ } │
│ │ │
│ │ 7. 存储新客户端 │ ┌─────────────────────┐
│ │ gClients[clientID] = client │ ───> │ gClients["A"] │
│ │ gConnToUsername[addr] = clientID │ │ gConnToUsername │
│ │ gUsernameToClientID[name] = id │ │ gUsernameToClientID │
│ │ │ └─────────────────────┘
│ │ │
│ │ 8. 释放全局锁 │
│ │ gClientsMux.Unlock() │
│ │ │
│ │ 9. 记录日志 │
│ │ serverLog("[REGISTER] Client: A") │
│ │ │
│ 10. register_response │ │
│ {"type":"register_response", │ │
│ "sender_id":"server", │ │
│ "receiver_id":"A", │ │
│ "data":"Registration successful"} │ │
│ <──────────────────────────────────────────────│ │
│ │ │
▼ ▼ ▼
8.4 心跳处理详解 (handleHeartbeat)
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 服务器心跳处理流程 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
客户端 服务器处理流程 状态更新
│ │ │
│ 1. 发送 heartbeat 消息 │ │
│ {"type":"heartbeat", │ │
│ "sender_id":"A"} │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ │ 2. 获取读锁 │
│ │ gClientsMux.Lock() │
│ │ │
│ │ 3. 查找客户端 │
│ │ client, exists := gClients["A"] │
│ │ │
│ │ 4a. 客户端存在 │ ┌─────────────────────┐
│ │ client.LastActivity = time.Now()│ ───> │ 更新最后活动时间 │
│ │ client.Stats.RecordHeartbeat() │ │ 记录心跳统计 │
│ │ gClientsMux.Unlock() │ └─────────────────────┘
│ │ │
│ │ 构造并发送响应: │
│ 5. heartbeat_response │ │
│ {"type":"heartbeat_response", │ │
│ "sender_id":"server", │ │
│ "receiver_id":"A", │ │
│ "data":"Heartbeat received"} │ │
│ <──────────────────────────────────────────────│ │
│ │ │
│ │ 4b. 客户端不存在 │
│ │ gClientsMux.Unlock() │
│ │ 记录错误日志 │
│ │ (不发送响应) │
│ │ │
▼ ▼ ▼
8.5 消息转发处理详解 (handleSignalForwardMsg)
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 服务器消息转发流程 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
适用消息类型: call_request, call_stop, webrtc_sdp, webrtc_ice_candidate
客户端 A 服务器处理流程 客户端 B
│ │ │
│ 1. 发送信令消息 │ │
│ {"type":"webrtc_sdp", │ │
│ "sender_id":"A", │ │
│ "receiver_id":"B", │ │
│ "data":"..."} │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ │ 2. 获取读锁 │
│ │ gClientsMux.RLock() │
│ │ │
│ │ 3. 查找目标客户端 │
│ │ targetClient, exists := │
│ │ gClients["B"] │
│ │ │
│ │ 4. 释放读锁 │
│ │ gClientsMux.RUnlock() │
│ │ │
│ │ ┌─────────────────────────────────────┤
│ │ │ 5a. 目标客户端在线 │
│ │ │ if exists && targetClient.Conn │
│ │ │ │
│ │ │ 直接转发原始消息: │
│ │ │ sendMessage(targetClient.Conn, msg)│
│ │ │ │
│ │ │ 记录日志: │
│ │ │ "Signal forwarded | type: sdp" │
│ │ └─────────────────────────────────────┤
│ │ │
│ │ │ 6. 收到转发消息
│ │ ───────────────────────────────────────>│
│ │ │
│ │ ┌─────────────────────────────────────┤
│ │ │ 5b. 目标客户端不在线 │
│ │ │ else │
│ │ │ │
│ │ │ 发送错误响应给发送方: │
│ │ │ errorResponse := Message{ │
│ │ │ Type: "error", │
│ │ │ Data: "User B is not online" │
│ │ │ } │
│ │ │ sendMessage(conn, errorResponse) │
│ │ │ │
│ │ │ 记录日志: │
│ │ │ "Signal forward failed: target B" │
│ │ └─────────────────────────────────────┤
│ │ │
│ 7. 收到错误响应 (如果目标不在线) │ │
│ {"type":"error", │ │
│ "data":"User B is not online"} │ │
│ <──────────────────────────────────────────────│ │
│ │ │
▼ ▼ ▼
8.6 在线用户查询处理 (handleQueryOnlineUsers)
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 在线用户查询处理流程 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
客户端 服务器处理流程
│ │
│ 1. 发送 query_online_users │
│ {"type":"query_online_users", │
│ "sender_id":"A"} │
│ ───────────────────────────────────────────────>│
│ │
│ │ 2. 获取读锁
│ │ gClientsMux.RLock()
│ │ defer gClientsMux.RUnlock()
│ │
│ │ 3. 遍历所有客户端
│ │ onlineUsers := []UserInfo{}
│ │ for _, client := range gClients { │
│ │ onlineUsers = append( │
│ │ UserInfo{ │
│ │ Username: client.Username, │
│ │ PublicIP: client.PublicIP, │
│ │ PublicPort: client.PublicPort,
│ │ }) │
│ │ } │
│ │
│ │ 4. 构造响应数据
│ │ responseData := { │
│ │ "users": onlineUsers │
│ │ } │
│ │
│ 5. query_online_users_response │
│ {"type":"query_online_users_response", │
│ "sender_id":"server", │
│ "data":"{\"users\":[...]}"} │
│ <──────────────────────────────────────────────│
│ │
▼ ▼
8.7 STUN/TURN 配置处理 (handleGetStunTurnConfig)
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ STUN/TURN 配置处理流程 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
客户端 服务器处理流程
│ │
│ 1. 发送 get_stun_turn_config │
│ {"type":"get_stun_turn_config", │
│ "sender_id":"A"} │
│ ───────────────────────────────────────────────>│
│ │
│ │ 2. 读取配置文件
│ │ stunTurnConfig := { │
│ │ "stun": { │
│ │ "enabled": gConfig.Stun.Enabled,
│ │ "server": gConfig.Stun.Server,│
│ │ "port": gConfig.Stun.Port │
│ │ }, │
│ │ "turn": { │
│ │ "enabled": gConfig.Turn.Enabled,
│ │ "server": gConfig.Turn.Server,│
│ │ "port": gConfig.Turn.Port, │
│ │ "username": gConfig.Turn.Username,
│ │ "password": gConfig.Turn.Password
│ │ } │
│ │ } │
│ │
│ │ 3. JSON 序列化
│ │ configData, _ := json.Marshal()
│ │
│ 4. get_stun_turn_config_response │
│ {"type":"get_stun_turn_config_response", │
│ "sender_id":"server", │
│ "data":"{\"stun\":{...},\"turn\":{...}}"} │
│ <──────────────────────────────────────────────│
│ │
▼ ▼
8.8 不活跃客户端清理 (cleanupInactiveClients)
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 不活跃客户端清理流程 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
定时器 (每30秒) 服务器处理流程
│ │
│ 1. 定时器触发 │
│ ticker.C │
│ ───────────────────────────────────────────────>│
│ │
│ │ 2. 获取写锁
│ │ gClientsMux.Lock()
│ │
│ │ 3. 遍历所有客户端
│ │ now := time.Now()
│ │ timeout := gConfig.Client.InactiveTimeout
│ │ for clientID, client := range gClients {
│ │
│ │ 4. 检查超时
│ │ if now.Sub(client.LastActivity) > timeout {
│ │
│ │ 5. 清理超时客户端 ┌─────────────────────┐
│ │ - 记录统计信息 │ 输出客户端统计 │
│ │ serverLog(client.Stats.GetSummary()) │
│ │ - 关闭连接 │ conn.Close() │
│ │ - 删除映射 │ delete maps │
│ │ - 从 gClients 移除 │ delete gClients │
│ │ } └─────────────────────┘
│ │
│ │ 6. 释放写锁
│ │ gClientsMux.Unlock()
│ │
▼ ▼
8.9 统计报告 (reportStatistics)
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 统计报告流程 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
定时器 (每60秒) 服务器处理流程
│ │
│ 1. 定时器触发 │
│ ticker.C │
│ ───────────────────────────────────────────────>│
│ │
│ │ 2. 获取读锁
│ │ gClientsMux.RLock()
│ │
│ │ 3. 输出统计报告
│ │ serverLog("========== Statistics ==========")
│ │ for clientID, client := range gClients {
│ │ serverLog("Client: %s | %s", ┌─────────────────────┐
│ │ clientID, │ Client: A │
│ │ client.Stats.GetSummary()) │ Duration: 5m30s │
│ │ } │ Sent: 10 msgs │
│ │ │ Received: 8 msgs │
│ │ 4. 输出汇总信息 │ Heartbeat: 5 sent │
│ │ serverLog("Online clients: %d", │ ... │
│ │ len(gClients)) └─────────────────────┘
│ │ serverLog("Total connections: %d",
│ │ gTotalConnections)
│ │
│ │ 5. 释放读锁
│ │ gClientsMux.RUnlock()
│ │
▼ ▼
8.10 服务器数据结构
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 服务器核心数据结构 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
Client 结构体 (client.go):
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ type Client struct { │
│ ID string // 客户端唯一标识 (client_id) │
│ Username string // 用户名 (与 ID 相同) │
│ Conn net.Conn // TCP 连接对象 │
│ PublicIP string // 客户端公网 IP │
│ PublicPort int // 客户端公网端口 │
│ LastActivity time.Time // 最后活动时间 (用于超时检测) │
│ Stats ClientStats // 连接统计信息 │
│ } │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
ClientStats 结构体:
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ type ClientStats struct { │
│ ConnectTime time.Time // 连接建立时间 │
│ MessagesSent int64 // 发送消息数 │
│ MessagesReceived int64 // 接收消息数 │
│ BytesSent int64 // 发送字节数 │
│ BytesReceived int64 // 接收字节数 │
│ HeartbeatSent int64 // 发送心跳数 │
│ HeartbeatReceived int64 // 接收心跳数 │
│ Errors int64 // 错误数 │
│ CallRequestSent int64 // 发送呼叫请求数 │
│ CallRequestReceived int64 // 接收呼叫请求数 │
│ CallStopSent int64 // 发送挂断数 │
│ CallStopReceived int64 // 接收挂断数 │
│ WebRTCSdpSent int64 // 发送 SDP 数 │
│ WebRTCSdpReceived int64 // 接收 SDP 数 │
│ WebRTCIceCandidateSent int64 // 发送 ICE 候选数 │
│ WebRTCIceCandidateReceived int64 // 接收 ICE 候选数 │
│ } │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
Message 结构体 (protocol.go):
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ type Message struct { │
│ Type string `json:"type"` // 消息类型 │
│ SenderID string `json:"sender_id"` // 发送方 ID │
│ ReceiverID string `json:"receiver_id"` // 接收方 ID (可选) │
│ DataType string `json:"data_type"` // 数据类型 (SDP: offer/answer) │
│ Data string `json:"data"` // 消息内容 │
│ } │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
TcpPacket 结构体 (protocol.go):
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ type TcpPacket struct { │
│ JsonLength int // JSON 数据长度 (4 字节) │
│ JsonData []byte // JSON 数据内容 │
│ } │
│ │
│ 封装格式: [4字节长度][JSON数据] │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
8.11 服务器配置项 (config.json)
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 服务器配置说明 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
{
"server": {
"host": "0.0.0.0", // 监听地址 (0.0.0.0 表示所有网卡)
"port": 3480 // 监听端口
},
"worker_pool_size": 100, // Worker 池大小 (并发处理能力)
"client": {
"inactive_timeout": 300 // 客户端不活跃超时时间 (秒)
},
"stun": {
"enabled": true, // 是否启用 STUN
"server": "stun.l.google.com",// STUN 服务器地址
"port": 19302 // STUN 服务器端口
},
"turn": {
"enabled": false, // 是否启用 TURN
"server": "", // TURN 服务器地址
"port": 3478, // TURN 服务器端口
"username": "", // TURN 用户名
"password": "" // TURN 密码
}
}
9. 关键时序要点
9.1 注册时序
正确顺序:
1. startMessageSender() // 启动消息发送线程
2. Thread { startReceiving() }.start() // 启动接收线程
3. receivingStartedLatch.await() // 等待接收线程就绪
4. register() // 发送注册消息
9.2 SDP 交换时序
主叫方 (Caller):
1. createPeerConnection()
2. createLocalMediaStream()
3. createOffer()
4. setLocalDescription(offer)
5. 发送 offer 给对方
被叫方 (Callee):
1. createPeerConnection()
2. createLocalMediaStream()
3. 收到 offer
4. setRemoteDescription(offer)
5. createAnswer()
6. setLocalDescription(answer)
7. 发送 answer 给对方
主叫方:
8. 收到 answer
9. setRemoteDescription(answer)
9.3 ICE Candidate 时序
收集时机: setLocalDescription() 调用后
发送条件: isSdpListenerReady == true
添加条件: isRemoteDescriptionSet == true
9.4 挂断时序
主动挂断:
1. isCallEnded = true
2. sendCallStop() (如果 !isRemoteHangup)
3. cleanup()
4. finish()
被动挂断:
1. 收到 call_stop
2. isRemoteHangup = true
3. endCall()
4. cleanup() (不发送 call_stop)
5. finish()
10. 异常处理流程
10.1 ICE 连接失败处理
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ ICE 连接失败处理 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
客户端 A 服务器 客户端 B
│ │ │
│ 1. ICE 连接状态变化 │ │
│ onIceConnectionChange(FAILED) │ │
│ │ │
│ 2. 检查失败原因 │ │
│ - ICE 候选者不足 │ │
│ - NAT 穿透失败 │ │
│ - 网络不可达 │ │
│ │ │
│ 3. 尝试 ICE Restart (可选) │ │
│ peerConnection.restartIce() │ │
│ - 重新收集 ICE 候选者 │ │
│ - 重新交换 SDP │ │
│ │ │
│ 4. 如果 ICE Restart 失败 │ │
│ - 显示错误提示 │ │
│ - "网络连接失败,请检查网络设置" │ │
│ - 执行挂断流程 │ │
│ │ │
│ 5. 清理资源 │ │
│ cleanup() │ │
│ finish() │ │
│ │ │
▼ ▼ ▼
10.2 网络断开重连处理
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 网络断开重连处理 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
客户端 A 服务器 客户端 B
│ │ │
│ 1. 检测到网络断开 │ │
│ NetworkMonitor.onNetworkLost() │ │
│ │ │
│ 2. 心跳超时检测 │ │
│ HeartbeatManager 检测到连续3次心跳无响应 │ │
│ │ │
│ 3. 标记连接状态 │ │
│ isConnectionLost = true │ │
│ 显示 "网络断开,正在重连..." │ │
│ │ │
│ 4. 尝试重连 TCP │ │
│ while (retryCount < maxRetry) { │ │
│ tcpClient.reconnect() │ │
│ if (success) break │ │
│ Thread.sleep(retryInterval) │ │
│ retryCount++ │ │
│ } │ │
│ │ │
│ 5a. 重连成功 │ │
│ - 重新发送 register │ │
│ - 恢复心跳 │ │
│ - 如果在通话中,重新建立 WebRTC 连接 │ │
│ │ │
│ 5b. 重连失败 │ │
│ - 显示 "网络连接失败" │ │
│ - 结束当前通话 │ │
│ - 返回登录界面 │ │
│ │ │
▼ ▼ ▼
10.3 SDP 时序问题处理
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ SDP 时序问题处理 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
问题场景: ICE Candidate 在远程描述设置之前到达
客户端 A 服务器 客户端 B
│ │ │
│ 1. A 发送 offer │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ 2. A 开始收集 ICE Candidates │ │
│ onIceCandidate(candidate1) │ │
│ │ │
│ 3. A 发送 ICE Candidate (B还未收到offer) │ │
│ ───────────────────────────────────────────────>│ │
│ │ ────────────────────────────────────────>│
│ │ │
│ │ │ 4. B 收到 ICE Candidate
│ │ │ 但远程描述未设置
│ │ │
│ │ │ 5. 缓存 ICE Candidate
│ │ │ pendingIceCandidates.add(candidate1)
│ │ │
│ │ 6. B 收到 offer │
│ │ <───────────────────────────────────────│
│ │ │
│ │ │ 7. 设置远程描述
│ │ │ setRemoteDescription(offer)
│ │ │ isRemoteDescriptionSet = true
│ │ │
│ │ │ 8. 处理缓存的 ICE Candidates
│ │ │ for (candidate in pendingIceCandidates) {
│ │ │ peerConnection.addIceCandidate(candidate)
│ │ │ }
│ │ │ pendingIceCandidates.clear()
│ │ │
▼ ▼ ▼
10.4 呼叫超时处理
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 呼叫超时处理 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
客户端 A (主叫) 服务器 客户端 B (被叫)
│ │ │
│ 1. 发送 call_request │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ 2. 启动呼叫超时计时器 │ │
│ handler.postDelayed(callTimeout, 60000) │ │
│ │ │
│ 3. 60秒内无响应 │ │
│ - 对方离线 │ │
│ - 对方未接听 │ │
│ - 网络问题 │ │
│ │ │
│ 4. 超时处理 │ │
│ handler.removeCallbacks(callTimeout) │ │
│ showTimeoutDialog() │ │
│ "对方无应答,请稍后重试" │ │
│ │ │
│ 5. 清理资源 │ │
│ cleanup() │ │
│ finish() │ │
│ │ │
▼ ▼ ▼
10.5 心跳超时处理
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 心跳超时处理 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
客户端 A 服务器 客户端 B
│ │ │
│ 1. 心跳管理器启动 │ │
│ heartbeatManager.start(interval=90s) │ │
│ │ │
│ 2. 定期发送心跳 │ │
│ 每 90 秒发送一次 heartbeat │ │
│ ───────────────────────────────────────────────>│ │
│ │ │
│ 3. 服务器响应 │ │
│ <───────────────────────────────────────────────│ │
│ heartbeat_response │ │
│ │ │
│ 4. 如果连续 3 次心跳无响应 │ │
│ missedHeartbeats++ │ │
│ if (missedHeartbeats >= 3) { │ │
│ // 认为连接已断开 │ │
│ } │ │
│ │ │
│ 5. 执行断线处理 │ │
│ - 停止心跳 │ │
│ - 清理资源 │ │
│ - 尝试重连 │ │
│ │ │
▼ ▼ ▼
11. WebRTC 连接状态机
11.1 ICE 连接状态
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ ICE 连接状态机 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
┌──────────────┐
│ NEW │ 初始状态
└──────┬───────┘
│
│ setLocalDescription() 调用
▼
┌──────────────┐
┌────────│ CHECKING │◄────────┐
│ └──────┬───────┘ │
│ │ │
│ │ 找到有效候选者 │ ICE Restart
│ ▼ │
│ ┌──────────────┐ │
│ │ CONNECTED │─────────┘
│ └──────┬───────┘
│ │
│ │ 所有候选者检查完成
│ ▼
│ ┌──────────────┐
│ │ COMPLETED │ 连接成功
│ └──────────────┘
│
│ 连接失败
▼
┌──────────────┐
│ FAILED │ 需要处理错误
└──────┬───────┘
│
│ 连接断开
▼
┌──────────────┐
│ DISCONNECTED│ 可能需要重连
└──────────────┘
│
│ 连接关闭
▼
┌──────────────┐
│ CLOSED │ 最终状态
└──────────────┘
11.2 信令状态
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ SDP 信令状态机 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
主叫方 (Caller):
┌──────────────┐
│ STABLE │ 初始状态
└──────┬───────┘
│
│ createOffer()
│ setLocalDescription(offer)
▼
┌──────────────┐
│ HAVE-LOCAL- │
│ OFFER │ 已创建本地 Offer
└──────┬───────┘
│
│ 收到 Answer
│ setRemoteDescription(answer)
▼
┌──────────────┐
│ STABLE │ SDP 交换完成
└──────────────┘
被叫方 (Callee):
┌──────────────┐
│ STABLE │ 初始状态
└──────┬───────┘
│
│ 收到 Offer
│ setRemoteDescription(offer)
▼
┌──────────────┐
│ HAVE-REMOTE- │
│ OFFER │ 已收到远程 Offer
└──────┬───────┘
│
│ createAnswer()
│ setLocalDescription(answer)
▼
┌──────────────┐
│ STABLE │ SDP 交换完成
└──────────────┘
12. 线程安全与并发控制
12.1 关键锁机制
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 线程安全机制 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
1. TcpSignalingClient 消息发送:
- synchronized(messageQueue) 保证消息顺序
- 发送线程和接收线程分离
2. IceCandidateManager 候选者缓存:
- synchronized(pendingIceCandidates) 保护候选者列表
- 避免并发添加/清空冲突
3. SdpManager SDP 消息缓存:
- synchronized(pendingSdpMessages) 保护 SDP 消息列表
- 确保 SDP 处理顺序正确
4. VideoCallActivity 状态标志:
- isCallEnded, isCleanedUp, isRemoteHangup 使用 volatile
- 确保多线程可见性
5. WebRTC 回调:
- 所有 WebRTC 回调在后台线程执行
- UI 更新通过 runOnUiThread() 切换到主线程
12.2 线程模型
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 线程模型 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
│ 主线程 (UI Thread) │
│ - Activity 生命周期管理 │
│ - UI 更新和事件处理 │
│ - 用户交互响应 │
└─────────────────────────────────────────────────────────────────────────────────────────────┘
│
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐ ┌─────────────────────────┐
│ TCP 接收线程 │ │ WebRTC 线程池 │ │ 心跳线程 │
│ - 接收服务器消息 │ │ - ICE 候选者收集 │ │ - 定时发送心跳 │
│ - 解析消息类型 │ │ - SDP 协商 │ │ - 检测超时 │
│ - 分发到对应处理器 │ │ - 媒体流处理 │ │ - 管理重连 │
└─────────────────────────┘ └─────────────────────────┘ └─────────────────────────┘
13. 性能优化建议
13.1 WebRTC 初始化优化
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 性能优化建议 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
1. WebRTC 预初始化:
- 在 Application 启动时预初始化 PeerConnectionFactory
- 预创建 EglBase 上下文
- 减少首次呼叫延迟 (从 ~2s 降至 ~200ms)
2. ICE 候选者优先级:
- 优先使用 host 候选者 (本地网络)
- 其次使用 srflx 候选者 (STUN)
- 最后使用 relay 候选者 (TURN)
3. 视频编码优化:
- 使用硬件编码 (H.264/VP8)
- 根据网络状况动态调整分辨率和帧率
- 实现带宽自适应
4. 网络类型检测:
- WiFi: 使用较高分辨率 (720p/1080p)
- 4G: 使用中等分辨率 (480p/720p)
- 3G/2G: 使用较低分辨率 (240p/360p)
5. 连接池管理:
- 复用 TCP 连接
- 避免频繁创建/销毁连接
- 实现连接保活机制
13.2 资源管理最佳实践
1. 视频渲染器管理:
- 及时 release() 不再使用的 SurfaceViewRenderer
- 避免内存泄漏
- 正确处理 Activity 生命周期
2. WebRTC 资源清理顺序:
a. 停止视频采集: videoCapturer.stopCapture()
b. 移除视频接收器: videoTrack.removeSink()
c. 关闭 PeerConnection: peerConnection.close()
d. 释放视频源: videoSource.dispose()
e. 释放音频源: audioSource.dispose()
f. 释放视频采集器: videoCapturer.dispose()
g. 清空回调引用: 所有回调设为 null
3. 内存优化:
- 使用 WeakReference 持有 Activity 引用
- 避免在回调中持有强引用
- 及时清理不再使用的对象
14. 错误码与错误信息
14.1 服务器响应错误码
| 错误类型 | 错误信息 | 处理建议 |
|---|---|---|
| 注册失败 | "Client ID already registered" | 更换 client_id 或等待旧连接超时 |
| 注册失败 | "Client ID does not match username" | 确保 client_id 与 username 一致 |
| 消息转发失败 | "Receiver not found" | 目标用户离线,提示用户 |
| 心跳超时 | 无响应 | 检查网络连接,尝试重连 |
14.2 WebRTC 错误处理
| 错误状态 | 可能原因 | 处理方式 |
|---|---|---|
| ICE_FAILED | NAT 穿透失败 | 检查 STUN/TURN 配置 |
| ICE_DISCONNECTED | 网络中断 | 尝试 ICE Restart 或重连 |
| SDP_PARSE_ERROR | SDP 格式错误 | 检查 SDP 内容,重新协商 |
| MEDIA_DEVICE_ERROR | 设备权限问题 | 检查摄像头/麦克风权限 |
15. 日志与调试
15.1 关键日志点
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│ 关键日志点 │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
1. TCP 连接:
- [tcp] 连接服务器: {host}:{port}
- [tcp] 注册成功: {client_id}
- [tcp][recv] <<<< 收到: {message}
- [tcp][send] >>>> 发送: {message}
2. WebRTC:
- [webrtc] 创建 PeerConnection
- [webrtc] 创建本地媒体流
- [webrtc] ICE 状态变化: {state}
- [webrtc] 添加 ICE 候选者: {candidate}
- [webrtc] SDP 创建成功: {type}
3. 呼叫流程:
- [call] 发起呼叫: {callee_id}
- [call] 收到呼叫请求: {caller_id}
- [call] 通话结束
- [call] 清理资源完成
4. 错误日志:
- [error] TCP 连接失败: {reason}
- [error] WebRTC 错误: {reason}
- [error] 心跳超时
15.2 调试建议
1. 开启详细日志:
- 在 Logger 中设置 DEBUG 级别
- 查看完整的消息交换过程
2. 网络抓包:
- 使用 Wireshark 抓取 TCP 流量
- 分析 STUN/TURN 数据包
3. WebRTC 内部状态:
- 启用 WebRTC 内部日志
- 查看 ICE 连接详情
4. 性能分析:
- 使用 Android Profiler 分析 CPU/内存
- 检查是否有内存泄漏