音视频呼叫完整时序图

客户端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/内存
   - 检查是否有内存泄漏
相关推荐
mseaspring4 小时前
35.7k Star的开源项目,用Claude Code 调用Remotion 以编程的方式自动生成视频
音视频
BryanGG5 小时前
[教程]通用稳定器运镜技巧
音视频·稳定器·运镜
YYDataV数据可视化6 小时前
【音视频通话系统】架构详解
音视频·webrtc·实时音视频
linux_cfan6 小时前
打造智慧校园视听新基建:高校与在线教育平台 Web 视频播放器选型指南 (2026版)
前端·学习·音视频·教育电商
YZ0991 天前
Sora2 AI视频去水印接口
人工智能·音视频·api·ai编程
硅谷秋水1 天前
mimic-video:机器人控制的可泛化视频-动作模型,超越VLA模型
人工智能·机器学习·计算机视觉·机器人·音视频
源代码•宸1 天前
简版抖音项目——项目需求、项目整体设计、Gin 框架使用、视频模块方案设计、用户与鉴权模块方案设计、JWT
经验分享·后端·golang·音视频·gin·jwt·gorm
REDcker1 天前
RTP、RTCP 与 SRTP 协议详解
网络·音视频·webrtc·实时音视频·rtp·rtcp
雪碧聊技术1 天前
什么是Seedance 2.0?字节自研多模态AI视频生成引擎全解析
人工智能·音视频·seedance2.0