基于 Socket.IO 实现 WebRTC 音视频通话与实时聊天系统(Spring Boot 后端实现)
技术栈:Spring Boot + Socket.IO (Netty-socketio) + WebRTC + Redis + MongoDB
一、引言
随着远程医疗、在线教育、即时通讯等应用的普及,实时音视频通话和文本聊天功能已成为现代 Web 应用的核心需求。本文将详细介绍如何使用 Java 的 Netty-socketio 框架,在 Spring Boot 项目中实现一个完整的 WebRTC 信令服务器 和 实时文本聊天系统。
我们将深入剖析两个核心服务类 WebRTCService 和 ChatService,并提供完整的功能流程图和前端交互示例。
二、系统架构概览
本系统采用典型的 B/S 架构:
- 前端 (Web/移动端):使用 WebRTC API 处理音视频流,使用 Socket.IO 客户端与服务器通信。
- 后端 (Java Spring Boot):使用
Netty-socketio作为 Socket.IO 服务器,处理所有信令和消息逻辑。 - 数据库:使用 MongoDB 存储聊天消息和用户联系人,使用 Redis 存储在线状态和未读计数。
+----------------+ +---------------------+ +----------------+
| | | | | |
| Web Client |<--->| Netty-socketio |<--->| MongoDB/Redis |
| (WebRTC + Chat)| | (Java Server) | | (Persistence) |
| | | | | |
+----------------+ +---------------------+ +----------------+
三、核心功能模块详解
3.1 文本聊天服务 (ChatService.java)
ChatService 负责处理用户连接、消息收发、状态更新和房间管理。
3.1.1 核心功能
-
用户连接与身份认证
- 客户端连接时,发送
connect_success事件,携带userId,role(patient/doctor),institutionId等信息。 - 服务端验证信息后,将用户加入对应的“通知房间”和“聊天房间”。
- 客户端连接时,发送
-
消息收发
- 客户端发送
send_msg事件。 - 服务端将消息存入 MongoDB,并广播给房间内其他成员。
- 客户端发送
-
消息状态管理
- 支持
msg_delivered(已送达) 和msg_read(已读) 状态。 - 通过 Redis 维护未读消息计数。
- 支持
-
房间成员管理
- 实时获取和广播房间内的在线成员列表。
3.1.2 关键代码解析
// 生成唯一的聊天室ID
private String generateChatRoom(String institutionId, String patientId, String doctorId) {
return "chat_room_" + institutionId + "_" + patientId + "_" + doctorId;
}
// 处理消息发送
private void handleSendMessage(SocketIOClient client, Map<String, Object> data) {
// ... (参数校验)
String chatRoom = generateChatRoom(institutionId, patientId, doctorId);
joinAndTrackMembership(client, chatRoom); // 确保在房间内
sendMessage(patientId, doctorId, institutionId, msgContent, role, msgType, client);
}
3.2 WebRTC 音视频通话服务 (WebRTCService.java)
WebRTCService 是 WebRTC 信令服务器的核心,负责交换 SDP Offer/Answer 和 ICE Candidate。
3.2.1 WebRTC 信令流程
WebRTC 本身不提供信令机制,需要开发者自行实现。本系统通过 Socket.IO 事件完成信令交换:
1. A (发起方) 2. Server (信令服务器) 3. B (接收方)
| | |
|---- CALL_REQUEST -------->| |
| | |
| |------ CALL_REQUEST --------->|
| | |
| |<----- CALL_ACCEPT -----------|
|<---- CALL_ACCEPT ----------| |
| | |
|------ OFFER ------------->| |
| | |
| |-------- OFFER -------------->|
| | |
| |<------- ANSWER ---------------|
|<------ ANSWER -------------| |
| | |
|------ ICE_CANDIDATE ----->| |
| | |
| |--- ICE_CANDIDATE ----------->|
| | |
| (建立P2P连接) | |
3.2.2 核心事件与处理
| 事件 (Event) | 说明 |
|---|---|
call_request | A 发起通话请求,服务器转发给 B。 |
call_accept | B 接受通话,服务器通知 A。 |
call_reject | B 拒绝通话,服务器通知 A 并清理会话。 |
call_end | 任一方结束通话,通知另一方。 |
offer | A 创建 Offer,通过服务器转发给 B。 |
answer | B 创建 Answer,通过服务器转发给 A。 |
ice-candidate | 双方收集到 ICE Candidate,通过服务器转发给对方。 |
call_timeout | 服务器在 30 秒内未收到 B 的回应,自动通知 A 通话超时。 |
3.2.3 关键代码解析
// 处理通话请求
socketServer.addEventListener(CALL_REQUEST, Map.class, (client, data, ackSender) -> {
String roomId = getRequiredString(data, "roomId");
String toUserId = getRequiredString(data, "toUserId");
// ... (校验)
callSessions.put(roomId, new CallSession(roomId, userId)); // 创建会话
sendToUser(toUserId, roomId, CALL_REQUEST, payload); // 转发给接收方
startTimeoutTimer(roomId, 30000); // 启动30秒超时定时器
});
// 处理 SDP 交换 (Offer/Answer)
private void handleSdpExchange(SocketIOClient client, Map<String, Object> data, String eventType) {
String roomId = getRequiredString(data, "roomId");
CallSession session = callSessions.get(roomId);
// ... (会话校验)
broadcastExceptSender(roomId, eventType, payload, client); // 转发给对方
}
3.2.4 会话管理与断线处理
- 会话缓存:使用
ConcurrentHashMap<String, CallSession>存储所有通话会话。 - 超时机制:使用
ScheduledExecutorService为每个call_request设置 30 秒超时。 - 断线清理:当用户断开连接时,
ChatService会调用webRTCService.cleanupSessionsOnDisconnect(),清理该用户作为发起者的所有通话。
public void cleanupSessionsOnDisconnect(SocketIOClient client) {
String userId = client.get("userId");
Iterator<Map.Entry<String, CallSession>> iterator = callSessions.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, CallSession> entry = iterator.next();
if (userId.equals(entry.getValue().getInitiator())) {
broadcast(entry.getKey(), CALL_END, endEventPayload); // 通知对方
iterator.remove(); // 安全移除
}
}
}
四、功能实例与交互图
4.1 文本聊天实例
场景:患者 (P1) 向医生 (D1) 发送一条文本消息。
<!DOCTYPE html>
<html>
<head>
<title>IM Chat Demo</title>
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
</head>
<body>
<h2>患者端 - 与医生 D1 聊天</h2>
<div id="messages"></div>
<input type="text" id="msgInput" placeholder="输入消息...">
<button onclick="sendMessage()">发送</button>
<script>
const socket = io('http://localhost:8080'); // 连接服务器
// 连接成功
socket.emit('connect_success', {
userId: 'P1',
role: '2', // 患者
institutionId: '1001',
doctorId: 'D1'
});
// 接收消息
socket.on('get_send_msg', function(msg) {
const div = document.createElement('div');
div.textContent = `[${msg.userType === 2 ? '患者' : '医生'}] ${msg.msgContent}`;
document.getElementById('messages').appendChild(div);
});
function sendMessage() {
const content = document.getElementById('msgInput').value;
socket.emit('send_msg', {
userId: 'P1',
role: '2',
institutionId: '1001',
doctorId: 'D1',
msgType: 1,
msg: { text: content }
});
document.getElementById('msgInput').value = '';
}
</script>
</body>
</html>
4.2 音视频通话实例图
五、Coturn 服务器搭建与 ICE 认证
5.1 为什么需要 Coturn?
WebRTC 使用 ICE (Interactive Connectivity Establishment) 框架来寻找最佳的网络路径。ICE 会收集多种类型的网络地址(称为 Candidate):
- Host Candidate: 设备自身的内网IP和端口。
- Server Reflexive Candidate (SRFLX): 通过 STUN 服务器获取的公网IP和端口。
- Relayed Candidate (RELAY): 当 P2P 连接无法建立时,通过 TURN 服务器中继的媒体流。
STUN 服务器用于发现设备的公网地址,而 TURN 服务器则在 STUN 失败时,作为媒体流的中继服务器。Coturn 是一个开源的、功能强大的 STUN/TURN 服务器实现。
5.2 Coturn 服务器搭建
以下是在 Ubuntu 20.04 系统上搭建 Coturn 服务器的完整步骤。
5.2.1 安装 Coturn
# 更新系统包
sudo apt update
sudo apt upgrade -y
# 安装 coturn
sudo apt install coturn -y
# 设置开机自启
sudo systemctl enable coturn
5.2.2 配置 Coturn
编辑 Coturn 的主配置文件 /etc/turnserver.conf。
sudo nano /etc/turnserver.conf
将以下配置复制到文件中,并根据你的服务器环境进行修改:
# Coturn 配置文件
# 外部 IP 地址 (你的服务器公网IP)
external-ip=YOUR_SERVER_PUBLIC_IP
# 监听端口
listening-port=3478
tls-listening-port=5349
# Realm (域名或标识符)
realm=your-domain.com
# 侦听所有接口
listening-ip=0.0.0.0
# 强制使用指定的 IP 作为服务器的 IP
# 通常设置为 external-ip
# 如果你的服务器有多个公网IP,可以在这里指定
# relay-ip=YOUR_SERVER_PUBLIC_IP
# 启用 STUN
stun-only=false
# 启用 TURN
# no-stun=true
# 传输协议
# 可以指定 udp, tcp, tls, dtls
# 通常建议都启用
# no-udp
# no-tcp
# no-tls
# no-dtls
# 转发协议
# 可以指定 udp, tcp, tls, dtls
# 通常建议都启用
# no-udp-relay
# no-tcp-relay
# no-tls-relay
# no-dtls-relay
# 证书文件路径 (用于 TLS/DTLS)
# cert=/etc/ssl/certs/turnserver.pem
# pkey=/etc/ssl/private/turnserver_pkey.pem
# 用户名和密码数据库
# 这里使用 long-term credential mechanism
# 用户名和密码将通过 TURN REST API 在应用层面生成
# 数据库文件路径
userdb=/var/lib/turn/turndb
# 日志文件
log-file=/var/log/turnserver.log
# 日志级别
# 0: DEBUG, 1: INFO, 2: WARNING, 3: ERROR
verbose
# log-level=1
# 限制每个用户的带宽 (kbps)
# total-quota=100000
# user-quota=100000
# 安全设置
# 禁用本地 IP 地址
# no-loopback-peers
# no-multicast-peers
# 允许来自任何域的 WebRTC 客户端
# 这在生产环境中可能需要更严格的限制
# web-admin-address=0.0.0.0
# web-admin-port=5766
# server-name=your-domain.com
# 为 REST API 设置共享密钥
# 这是实现动态凭证的关键
# shared-secret=your-shared-secret-here
# 禁用静态用户,强制使用 REST API (动态凭证)
# 你必须选择一种认证方式:
# 1. 静态用户: 在配置文件中定义 user=username:password
# 2. 动态凭证 (推荐): 使用 shared-secret 和 REST API
# 方法 1: 静态用户 (简单,但不安全,不推荐用于生产)
# user=static_user:static_password
# 方法 2: 动态凭证 (推荐)
# 注释掉或删除所有静态 user 行
# 确保 shared-secret 已设置
shared-secret=your-very-secure-shared-secret-here
# 设置监听端口的协议
# 这将强制 TURN 服务器在这些端口上监听指定的协议
# 例如,让 3478 只监听 UDP,5349 只监听 TLS
# 这有助于防火墙配置
# udp-port-range=49152-65535
# min-port=49152
# max-port=65535
重要参数说明:
external-ip: 必须设置为你的服务器公网 IP。realm: 可以是你的域名,如im.yourcompany.com。shared-secret: 一个非常安全的密钥,用于在应用层面生成临时凭证。务必修改为一个强密码。
5.2.3 启动 Coturn 服务
# 重启 Coturn 服务以应用配置
sudo systemctl restart coturn
# 检查服务状态
sudo systemctl status coturn
# 查看日志
sudo tail -f /var/log/turnserver.log
5.2.4 防火墙配置
确保服务器的防火墙开放了必要的端口。
# 使用 ufw
sudo ufw allow 3478/udp
sudo ufw allow 3478/tcp
sudo ufw allow 5349/tcp # TLS
sudo ufw allow 5349/udp # DTLS (可选)
# Coturn 会动态分配中继端口,通常在 49152-65535 范围内
sudo ufw allow 49152:65535/udp
sudo ufw allow 49152:65535/tcp
# 重新加载防火墙
sudo ufw reload
5.3 ICE 服务器与凭证
WebRTC 客户端需要知道如何连接到 STUN/TURN 服务器。这通过 RTCPeerConnection 构造函数的 iceServers 配置项完成。
5.3.1 ICE 服务器配置
const iceServers = [
// 1. 公共 STUN 服务器 (免费,但不可靠)
{ urls: "stun:stun.l.google.com:19302" },
{ urls: "stun:global.stun.twilio.com:3478?transport=udp" },
// 2. 自建 Coturn 服务器 (推荐)
{
urls: [
"turn:your-domain.com:3478?transport=udp",
"turn:your-domain.com:3478?transport=tcp",
"turn:your-domain.com:5349?transport=tcp" // TLS
],
username: "your-generated-username",
credential: "your-generated-credential"
}
];
5.3.2 动态凭证 (TURN REST API)
硬编码用户名和密码是不安全的。Coturn 支持通过共享密钥(shared-secret)动态生成临时凭证。
生成临时凭证的 Java 工具类:
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.time.Instant;
public class TurnCredentialGenerator {
private static final String SHARED_SECRET = "your-very-secure-shared-secret-here"; // 与 coturn.conf 一致
/**
* 生成用于 WebRTC ICE 的 TURN 临时凭证
* @param userId 用户ID,用于标识
* @param ttl 凭证有效期 (秒)
* @return 包含 username 和 credential 的 Map
*/
public static Map<String, String> generateTemporaryCredentials(String userId, int ttl) {
long timestamp = Instant.now().getEpochSecond() + ttl; // 过期时间戳
String username = timestamp + ":" + userId; // 格式: <expire-timestamp>:<username>
try {
// 使用 HMAC-SHA1 签名
Mac mac = Mac.getInstance("HmacSHA1");
SecretKeySpec keySpec = new SecretKeySpec(SHARED_SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
mac.init(keySpec);
byte[] digest = mac.doFinal(username.getBytes(StandardCharsets.UTF_8));
// 将摘要进行 Base64 编码,得到 credential
String credential = Base64.getEncoder().encodeToString(digest);
Map<String, String> result = new HashMap<>();
result.put("username", username);
result.put("credential", credential);
return result;
} catch (Exception e) {
throw new RuntimeException("生成 TURN 凭证失败", e);
}
}
// 使用示例
public static void main(String[] args) {
Map<String, String> credentials = generateTemporaryCredentials("user123", 3600); // 1小时有效
System.out.println("Username: " + credentials.get("username"));
System.out.println("Credential: " + credentials.get("credential"));
}
}
前端获取 ICE 服务器配置:
// 在用户连接或进入聊天页面时,从你的后端API获取ICE配置
async function getIceServers() {
const response = await fetch('/api/ice-servers'); // 你的Spring Boot API端点
const data = await response.json();
return data.iceServers;
}
// 在创建 RTCPeerConnection 前调用
const iceServers = await getIceServers();
const peerConnection = new RTCPeerConnection({ iceServers });
Spring Boot Controller 示例:
@RestController
@RequestMapping("/api")
public class IceServerController {
@GetMapping("/ice-servers")
public ResponseEntity<Map<String, Object>> getIceServers(@RequestParam String userId) {
Map<String, Object> response = new HashMap<>();
try {
// 生成临时凭证
Map<String, String> credentials = TurnCredentialGenerator.generateTemporaryCredentials(userId, 3600);
List<Map<String, Object>> iceServers = new ArrayList<>();
// 添加公共 STUN
iceServers.add(Map.of("urls", List.of("stun:stun.l.google.com:19302")));
// 添加自建 TURN
Map<String, Object> turnServer = new HashMap<>();
turnServer.put("urls", List.of(
"turn:your-domain.com:3478?transport=udp",
"turn:your-domain.com:3478?transport=tcp"
));
turnServer.put("username", credentials.get("username"));
turnServer.put("credential", credentials.get("credential"));
iceServers.add(turnServer);
response.put("iceServers", iceServers);
return ResponseEntity.ok(response);
} catch (Exception e) {
log.error("获取ICE服务器配置失败", e);
response.put("error", "Internal Server Error");
return ResponseEntity.status(500).body(response);
}
}
}
5.4 集成与测试
- 前端集成:确保前端在创建
RTCPeerConnection时,使用从后端获取的、包含动态凭证的iceServers配置。 - 信令流程:
WebRTCService负责交换 SDP 和 ICE Candidate。当RTCPeerConnection收集到 Candidate 时,会触发icecandidate事件,前端通过socket.emit('ice-candidate', ...)发送给服务端,服务端再转发给对方。 - 测试:
- 使用
chrome://webrtc-internals查看 ICE Candidate 收集情况。 - 如果看到
relay类型的 Candidate,说明 TURN 服务器正在工作。 - 模拟网络问题(如关闭一方的 WiFi),观察通话是否能通过 TURN 中继恢复。
- 使用
六、前后端对接示例
1. 架构概述
基于Socket.IO实现实时通信,包含以下核心功能:
- 用户连接管理
- WebRTC音视频通话
- 实时文本聊天
- 房间成员管理
2. 连接配置
2.1 Socket.IO连接参数
const socket = io("http://地址", {
path: "/socket.io/",
transports: ["websocket"],
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000
});
2.2 WebRTC配置
const configuration = {
iceServers: [
{
urls: "turn:地址",
username: "用户名",
credential: "密码"
},
{
urls: "stun:stun.l.google.com:19302"
}
],
iceTransportPolicy: "relay" // 强制使用TURN服务器
};
3. 用户身份定义
| 角色 | role值 | 说明 |
|---|---|---|
| 患者 | “1” | 发起咨询请求 |
| 医生 | “2” | 接受咨询请求 |
4. Socket.IO事件对接
4.1 用户连接事件
客户端发送 - connect_success
// 患者端连接
socket.emit("connect_success", {
institutionId: "1001", // 机构ID
userId: "3003", // 患者ID
role: "1", // 用户角色(患者)
doctorId: "2002", // 目标医生ID
sourceUrl: window.location.href // 页面URL
});
// 医生端连接
socket.emit("connect_success", {
institutionId: "1001", // 机构ID
userId: "2002", // 医生ID
role: "2", // 用户角色(医生)
patientId: "3003", // 目标患者ID
sourceUrl: window.location.href // 页面URL
});
服务端响应事件
// 连接成功
socket.on("connect", () => {
// 更新UI状态
});
// 连接断开
socket.on("disconnect", () => {
// 更新UI状态
});
// 连接错误
socket.on("connect_error", (error) => {
// 处理连接错误
});
4.2 房间成员管理事件
客户端发送 - get_room_members
// 获取房间成员列表
socket.emit("get_room_members", "chat_room_1001_3003_2002");
服务端响应事件
// 房间成员列表
socket.on("room_members", (members) => {
// members: [
// {
// sessionId: "uuid",
// userId: "3003",
// role: "1",
// institutionId: "1001"
// }
// ]
});
// 房间成员更新
socket.on("room_members_update", (members) => {
// 成员列表实时更新
});
// 用户连接/断开事件
socket.on("connection", (data) => {
// data: {
// userId: "3003",
// role: "1",
// action: "connection" | "disconnection"
// }
});
4.3 文本聊天事件
客户端发送 - send_msg
socket.emit("send_msg", {
institutionId: "1001",
userId: "3003", // 发送者ID
role: "1", // 发送者角色
doctorId: "2002", // 医生ID
patientId: "3003", // 患者ID
msgType: 1, // 消息类型(1:文本)
msg: {
content: "Hello World" // 消息内容
}
});
服务端响应事件
// 接收消息
socket.on("get_send_msg", (data) => {
// data: {
// msgContent: {content: "Hello World"},
// userType: "1",
// addTime: "时间戳"
// }
});
// 消息状态更新
socket.on("msg_state_update", (data) => {
// data: {
// msgId: "消息ID",
// msgState: 2 // 1:已发送 2:已读
// }
});
4.4 WebRTC通话事件
客户端发送 - call_request (发起通话)
socket.emit("call_request", {
toUserId: "2002", // 目标用户ID
roomId: "call_room_1001_3003_2002", // 通话房间ID
callType: "video", // 通话类型(audio/video)
offer: RTCSessionDescription // WebRTC Offer
});
服务端响应事件
// 收到通话请求
socket.on("call_request", (data) => {
// data: {
// fromUserId: "3003",
// roomId: "call_room_1001_3003_2002",
// callType: "video",
// offer: RTCSessionDescription
// }
});
// 通话接受
socket.on("call_accept", (data) => {
// data: {
// roomId: "call_room_1001_3003_2002",
// fromUserId: "2002",
// answer: RTCSessionDescription
// }
});
// 通话拒绝
socket.on("call_reject", (data) => {
// data: {
// roomId: "call_room_1001_3003_2002",
// fromUserId: "2002"
// }
});
// 通话结束
socket.on("call_end", (data) => {
// data: {
// roomId: "call_room_1001_3003_2002"
// }
});
// WebRTC Offer
socket.on("offer", (data) => {
// data: {
// roomId: "call_room_1001_3003_2002",
// offer: RTCSessionDescription
// }
});
// WebRTC Answer
socket.on("answer", (data) => {
// data: {
// roomId: "call_room_1001_3003_2002",
// answer: RTCSessionDescription
// }
});
// ICE候选
socket.on("ice-candidate", (data) => {
// data: {
// roomId: "call_room_1001_3003_2002",
// iceCandidate: RTCIceCandidate
// }
});
客户端发送 - 其他通话相关事件
// 接受通话
socket.emit("call_accept", {
roomId: "call_room_1001_3003_2002",
fromUserId: "3003",
answer: RTCSessionDescription
});
// 拒绝通话
socket.emit("call_reject", {
roomId: "call_room_1001_3003_2002",
fromUserId: "3003"
});
// 结束通话
socket.emit("call_end", {
roomId: "call_room_1001_3003_2002"
});
// ICE候选
socket.emit("ice-candidate", {
roomId: "call_room_1001_3003_2002",
iceCandidate: RTCIceCandidate
});
5. 房间ID生成规则
5.1 聊天房间
chat_room_{institutionId}_{patientId}_{doctorId}
示例: chat_room_1001_3003_2002
5.2 通话房间
call_room_{institutionId}_{发起方ID}_{接收方ID}
示例: call_room_1001_3003_2002 (患者发起)
call_room_1001_2002_3003 (医生发起)
6. 前端实现要点
6.1 连接流程
- 建立Socket.IO连接
- 发送connect_success事件
- 自动加入聊天房间
- 获取房间成员列表
6.2 WebRTC通话流程
- 获取本地媒体流
- 创建RTCPeerConnection
- 创建并发送Offer
- 等待Answer
- 交换ICE候选
- 建立P2P连接
6.3 消息处理
- 发送消息时立即显示在本地
- 接收远程消息时显示在聊天框
- 处理系统消息(用户加入/离开)
7. 错误处理
7.1 连接错误
- 网络连接失败
- 服务器地址错误
- 认证失败
7.2 WebRTC错误
- 媒体设备权限拒绝
- ICE连接失败
- 信令交换失败
7.3 消息错误
- 消息发送失败
- 消息格式错误
8. 最佳实践
8.1 资源管理
- 及时关闭RTCPeerConnection
- 停止媒体流轨道
- 断开Socket连接
8.2 状态同步
- 实时更新连接状态
- 同步通话状态
- 维护成员列表
8.3 用户体验
- 提供连接状态提示
- 通话请求弹窗
- 消息发送状态反馈
9. 测试要点
9.1 连接测试
- 正常连接流程
- 断线重连机制
- 多设备连接
9.2 通话测试
- 音频通话
- 视频通话
- 通话中断处理
9.3 聊天测试
- 文本消息发送/接收
- 成员列表更新
- 系统消息显示
10. 常见问题
10.1 连接问题
- 检查服务器地址和端口
- 确认网络连接正常
- 验证用户信息正确性
10.2 音视频问题
- 检查浏览器权限设置
- 确认TURN服务器配置
- 验证媒体设备可用性
10.3 消息问题
- 检查房间ID正确性
- 确认消息格式规范
- 验证事件监听器注册
七、总结
至此,构建了一个功能完整、生产可用的实时通讯系统。
系统核心组件:
- 信令服务器 (
WebRTCService): 基于Netty-socketio,处理通话的建立、协商和结束。 - 消息服务器 (
ChatService): 处理文本消息的收发、状态同步和用户在线管理。 - STUN/TURN 服务器 (
Coturn): 解决 NAT 穿透问题,确保全球范围内的连接成功率。 - 持久化层 (MongoDB/Redis): 存储消息、联系人和在线状态。
优势与最佳实践:
- 高可用:信令和 TURN 服务可以独立部署和扩展。
- 安全性:使用动态凭证避免了密钥硬编码。
- 可维护性:代码结构清晰,信令与业务逻辑分离。
注意事项:
- 生产环境需考虑集群部署,使用 Redis 存储
callSessions以实现多节点共享。 - 增加更完善的安全认证(如 JWT)。
- 对 SDP 和 ICE 数据进行更严格的校验。
希望本文能为你的实时通讯项目提供有价值的参考!

817

被折叠的 条评论
为什么被折叠?



