Files
webRtc/WebApp/src/class/websockethandler.ts

448 lines
15 KiB
TypeScript
Raw Normal View History

2026-02-28 18:17:08 +08:00
/**
* WebSocket处理器
* WebSocket连接和信令消息处理
*/
2026-02-27 18:35:40 +08:00
import Offer from './offer';
import Answer from './answer';
import Candidate from './candidate';
2026-02-28 18:17:08 +08:00
/**
*
*/
2026-02-27 18:35:40 +08:00
let isPrivate: boolean;
2026-02-28 18:17:08 +08:00
/**
*
* : WebSocket实例
* : 该WebSocket的连接ID集合
*/
2026-02-27 18:35:40 +08:00
const clients: Map<WebSocket, Set<string>> = new Map<WebSocket, Set<string>>();
2026-02-28 18:17:08 +08:00
/**
2026-04-23 15:22:24 +08:00
*
* host: 主机WebSocket实例
* participants: 参与者WebSocket集合
*/
interface ConnectionGroup {
host: WebSocket;
participants: Set<WebSocket>;
}
/**
*
2026-02-28 18:17:08 +08:00
* : connectionId
2026-04-23 15:22:24 +08:00
* : ConnectionGroup1host + participants
2026-02-28 18:17:08 +08:00
*/
2026-04-23 15:22:24 +08:00
const connectionGroup: Map<string, ConnectionGroup> = new Map<string, ConnectionGroup>();
2026-02-27 18:35:40 +08:00
2026-02-28 18:17:08 +08:00
/**
* WebSocket会话的连接ID集合
* @param session WebSocket会话实例
* @returns ID的Set集合
*/
2026-02-27 18:35:40 +08:00
function getOrCreateConnectionIds(session: WebSocket): Set<string> {
let connectionIds = null;
2026-02-28 18:17:08 +08:00
// 检查客户端是否已存在
2026-02-27 18:35:40 +08:00
if (!clients.has(session)) {
2026-02-28 18:17:08 +08:00
// 如果不存在创建新的连接ID集合
2026-02-27 18:35:40 +08:00
connectionIds = new Set<string>();
2026-02-28 18:17:08 +08:00
// 将新的连接ID集合与客户端关联
2026-02-27 18:35:40 +08:00
clients.set(session, connectionIds);
}
2026-02-28 18:17:08 +08:00
// 获取客户端的连接ID集合
2026-02-27 18:35:40 +08:00
connectionIds = clients.get(session);
2026-02-28 18:17:08 +08:00
// 返回连接ID集合
2026-02-27 18:35:40 +08:00
return connectionIds;
}
2026-02-28 18:17:08 +08:00
/**
*
* @param mode public或private
*/
2026-02-27 18:35:40 +08:00
function reset(mode: string): void {
2026-02-28 18:17:08 +08:00
// 设置是否为私有模式
2026-02-27 18:35:40 +08:00
isPrivate = mode == "private";
}
2026-02-28 18:17:08 +08:00
/**
* WebSocket连接
* @param ws WebSocket连接实例
*/
2026-02-27 18:35:40 +08:00
function add(ws: WebSocket): void {
2026-02-28 18:17:08 +08:00
// 为新连接创建空的连接ID集合
2026-03-04 22:29:10 +08:00
const id = new Set<string>();
2026-03-04 17:55:55 +08:00
clients.set(ws, id);
2026-02-28 18:17:08 +08:00
// 记录添加WebSocket连接的日志
2026-03-04 17:55:55 +08:00
console.log(`Add WebSocket: ${id}`);
2026-02-27 18:35:40 +08:00
}
2026-04-23 15:22:24 +08:00
/**
* WebSocket是否为指定连接组的host
* @param ws WebSocket连接实例
* @param connectionId ID
* @returns host
*/
function isHost(ws: WebSocket, connectionId: string): boolean {
const group = connectionGroup.get(connectionId);
return group != null && group.host === ws;
}
/**
*
* @param connectionId ID
* @param senderWs WebSocket实例
* @param message
*/
function broadcastToGroup(connectionId: string, senderWs: WebSocket, message: any): void {
const group = connectionGroup.get(connectionId);
if (!group) return;
// 如果发送者是host转发给所有participants
if (senderWs === group.host) {
group.participants.forEach(participantWs => {
participantWs.send(JSON.stringify(message));
});
} else {
// 如果发送者是participant转发给host
group.host.send(JSON.stringify(message));
}
}
2026-02-28 18:17:08 +08:00
/**
* WebSocket连接
* @param ws WebSocket连接实例
*/
2026-02-27 18:35:40 +08:00
function remove(ws: WebSocket): void {
2026-02-28 18:17:08 +08:00
// 获取连接的所有连接ID
2026-02-27 18:35:40 +08:00
const connectionIds = clients.get(ws);
2026-04-23 15:22:24 +08:00
if (!connectionIds) return;
2026-02-28 18:17:08 +08:00
// 遍历所有连接ID
2026-02-27 18:35:40 +08:00
connectionIds.forEach(connectionId => {
2026-04-23 15:22:24 +08:00
const group = connectionGroup.get(connectionId);
if (group) {
if (group.host === ws) {
// host断开连接通知所有participants房间已关闭
group.participants.forEach(participantWs => {
participantWs.send(JSON.stringify({ type: "disconnect", connectionId: connectionId, reason: "host-left" }));
});
// 删除整个连接组
connectionGroup.delete(connectionId);
} else {
// participant断开连接从participants中移除并通知host
group.participants.delete(ws);
group.host.send(JSON.stringify({ type: "participant-left", connectionId: connectionId }));
2026-02-27 18:35:40 +08:00
}
}
2026-02-28 18:17:08 +08:00
// 记录删除连接ID的日志
console.log(`Remove connectionId: ${connectionId}`);
2026-02-27 18:35:40 +08:00
});
2026-02-28 18:17:08 +08:00
// 从客户端映射中删除
2026-02-27 18:35:40 +08:00
clients.delete(ws);
}
2026-02-28 18:17:08 +08:00
/**
2026-04-23 15:22:24 +08:00
* 1
* hostparticipants
2026-02-28 18:17:08 +08:00
* @param ws WebSocket连接实例
* @param connectionId ID
*/
2026-02-27 18:35:40 +08:00
function onConnect(ws: WebSocket, connectionId: string): void {
let polite = true;
2026-02-28 18:17:08 +08:00
// 处理私有模式
2026-02-27 18:35:40 +08:00
if (isPrivate) {
2026-04-23 15:22:24 +08:00
if (connectionGroup.has(connectionId)) {
const group = connectionGroup.get(connectionId);
// 已有host新连接作为participant加入
group.participants.add(ws);
console.log(`Participant joined connectionId: ${connectionId}, total participants: ${group.participants.size}`);
2026-02-27 18:35:40 +08:00
} else {
2026-04-23 15:22:24 +08:00
// 第一个连接成为host
connectionGroup.set(connectionId, { host: ws, participants: new Set<WebSocket>() });
2026-02-27 18:35:40 +08:00
polite = false;
2026-04-23 15:22:24 +08:00
console.log(`Host created connectionId: ${connectionId}`);
2026-02-27 18:35:40 +08:00
}
}
2026-02-28 18:17:08 +08:00
// 获取或创建连接ID集合
2026-02-27 18:35:40 +08:00
const connectionIds = getOrCreateConnectionIds(ws);
2026-02-28 18:17:08 +08:00
// 添加连接ID
2026-02-27 18:35:40 +08:00
connectionIds.add(connectionId);
2026-04-23 15:22:24 +08:00
// 发送连接成功消息(包含角色信息)
const role = polite ? 'participant' : 'host';
ws.send(JSON.stringify({ type: "connect", connectionId: connectionId, polite: polite, role: role }));
2026-03-01 23:41:42 +08:00
//启用心跳包
//AddHeartbeat(ws, connectionId);
2026-02-27 18:35:40 +08:00
}
2026-02-28 18:17:08 +08:00
/**
2026-04-23 15:22:24 +08:00
* 1
2026-02-28 18:17:08 +08:00
* @param ws WebSocket连接实例
* @param connectionId ID
*/
2026-02-27 18:35:40 +08:00
function onDisconnect(ws: WebSocket, connectionId: string): void {
2026-02-28 18:17:08 +08:00
// 获取连接的连接ID集合
2026-02-27 18:35:40 +08:00
const connectionIds = clients.get(ws);
2026-04-23 15:22:24 +08:00
if (connectionIds) {
// 从集合中删除连接ID
connectionIds.delete(connectionId);
}
2026-02-27 18:35:40 +08:00
2026-04-23 15:22:24 +08:00
// 处理连接组
const group = connectionGroup.get(connectionId);
if (group) {
if (group.host === ws) {
// host断开连接通知所有participants房间已关闭并删除连接组
group.participants.forEach(participantWs => {
participantWs.send(JSON.stringify({ type: "disconnect", connectionId: connectionId, reason: "host-left" }));
});
connectionGroup.delete(connectionId);
console.log(`Host disconnected, room ${connectionId} deleted, notified ${group.participants.size} participants`);
} else {
// participant断开连接从组中移除并通知host使用participant-left类型host不会关闭房间
group.participants.delete(ws);
group.host.send(JSON.stringify({ type: "participant-left", connectionId: connectionId }));
console.log(`Participant left connectionId: ${connectionId}, remaining participants: ${group.participants.size}`);
2026-02-27 18:35:40 +08:00
}
}
2026-04-23 15:22:24 +08:00
2026-02-28 18:17:08 +08:00
// 向当前连接发送断开连接消息
2026-02-27 18:35:40 +08:00
ws.send(JSON.stringify({ type: "disconnect", connectionId: connectionId }));
2026-03-01 23:41:42 +08:00
//RemoveHeartbeat(ws);
2026-03-04 17:55:55 +08:00
// 记录断开连接的日志
console.log(`Disconnect connectionId: ${connectionId}`);
2026-02-27 18:35:40 +08:00
}
2026-02-28 18:17:08 +08:00
/**
2026-04-23 15:22:24 +08:00
* offer信令1
* host的offer转发给所有participantsparticipant的offer转发给host
2026-02-28 18:17:08 +08:00
* @param ws WebSocket连接实例
* @param message
*/
2026-02-27 18:35:40 +08:00
function onOffer(ws: WebSocket, message: any): void {
2026-02-28 18:17:08 +08:00
// 获取连接ID
2026-02-27 18:35:40 +08:00
const connectionId = message.connectionId as string;
2026-02-28 18:17:08 +08:00
// 创建新的offer
2026-02-27 18:35:40 +08:00
const newOffer = new Offer(message.sdp, Date.now(), false);
2026-02-28 18:17:08 +08:00
// 处理私有模式
2026-02-27 18:35:40 +08:00
if (isPrivate) {
2026-04-23 15:22:24 +08:00
if (connectionGroup.has(connectionId)) {
const group = connectionGroup.get(connectionId);
if (group.host === ws) {
// host发送offer转发给所有participants
newOffer.polite = true;
group.participants.forEach(participantWs => {
participantWs.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer }));
});
} else {
// participant发送offer转发给host
2026-02-27 18:35:40 +08:00
newOffer.polite = true;
2026-04-23 15:22:24 +08:00
group.host.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer }));
2026-02-27 18:35:40 +08:00
}
}
return;
}
2026-04-23 15:22:24 +08:00
// 公共模式:创建新的连接组(如果不存在)
if (!connectionGroup.has(connectionId)) {
connectionGroup.set(connectionId, { host: ws, participants: new Set<WebSocket>() });
}
2026-02-28 18:17:08 +08:00
// 向所有其他客户端广播offer
2026-02-27 18:35:40 +08:00
clients.forEach((_v, k) => {
if (k == ws) {
return;
}
k.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer }));
});
}
2026-02-28 18:17:08 +08:00
/**
2026-04-23 15:22:24 +08:00
* answer信令1
* participant的answer转发给host
2026-02-28 18:17:08 +08:00
* @param ws WebSocket连接实例
* @param message
*/
2026-02-27 18:35:40 +08:00
function onAnswer(ws: WebSocket, message: any): void {
2026-02-28 18:17:08 +08:00
// 获取连接ID
2026-02-27 18:35:40 +08:00
const connectionId = message.connectionId as string;
2026-02-28 18:17:08 +08:00
// 获取或创建连接ID集合
2026-02-27 18:35:40 +08:00
const connectionIds = getOrCreateConnectionIds(ws);
2026-02-28 18:17:08 +08:00
// 添加连接ID
2026-02-27 18:35:40 +08:00
connectionIds.add(connectionId);
2026-02-28 18:17:08 +08:00
// 创建新的answer
2026-02-27 18:35:40 +08:00
const newAnswer = new Answer(message.sdp, Date.now());
2026-04-23 15:22:24 +08:00
// 检查连接组是否存在
if (!connectionGroup.has(connectionId)) {
2026-02-27 18:35:40 +08:00
return;
}
2026-04-23 15:22:24 +08:00
const group = connectionGroup.get(connectionId);
2026-02-27 18:35:40 +08:00
2026-04-23 15:22:24 +08:00
if (group.host === ws) {
// host发送answer转发给所有participants通常host不发送answer
group.participants.forEach(participantWs => {
participantWs.send(JSON.stringify({ from: connectionId, to: "", type: "answer", data: newAnswer }));
});
} else {
// participant发送answer转发给host
group.host.send(JSON.stringify({ from: connectionId, to: "", type: "answer", data: newAnswer }));
2026-02-27 18:35:40 +08:00
}
}
2026-02-28 18:17:08 +08:00
/**
2026-04-23 15:22:24 +08:00
* candidate信令1
* host的candidate转发给所有participantsparticipant的candidate转发给host
2026-02-28 18:17:08 +08:00
* @param ws WebSocket连接实例
* @param message
*/
2026-02-27 18:35:40 +08:00
function onCandidate(ws: WebSocket, message: any): void {
2026-02-28 18:17:08 +08:00
// 获取连接ID
2026-02-27 18:35:40 +08:00
const connectionId = message.connectionId;
2026-02-28 18:17:08 +08:00
// 创建新的candidate
2026-02-27 18:35:40 +08:00
const candidate = new Candidate(message.candidate, message.sdpMLineIndex, message.sdpMid, Date.now());
2026-02-28 18:17:08 +08:00
// 处理私有模式
2026-02-27 18:35:40 +08:00
if (isPrivate) {
2026-04-23 15:22:24 +08:00
if (connectionGroup.has(connectionId)) {
const group = connectionGroup.get(connectionId);
if (group.host === ws) {
// host发送candidate转发给所有participants
group.participants.forEach(participantWs => {
participantWs.send(JSON.stringify({ from: connectionId, to: "", type: "candidate", data: candidate }));
});
} else {
// participant发送candidate转发给host
group.host.send(JSON.stringify({ from: connectionId, to: "", type: "candidate", data: candidate }));
2026-02-27 18:35:40 +08:00
}
}
return;
}
}
2026-03-04 17:55:55 +08:00
function onCallConnectionId(ws: WebSocket, message: any): void {
// 获取连接ID
const connectionId = message.connectionId;
const clientId = message.clientId;
2026-04-23 15:22:24 +08:00
// 在1对多模式下通知host有新的呼叫请求
if (connectionGroup.has(connectionId)) {
const group = connectionGroup.get(connectionId);
if (group.host !== ws) {
// participant发起呼叫通知host
group.host.send(JSON.stringify({ from: connectionId, to: "", type: "call-request", data: connectionId }));
2026-03-04 17:55:55 +08:00
}
2026-04-23 15:22:24 +08:00
} else {
// 兼容旧的广播方式
clients.forEach((_v, k) => {
if (k === ws) {
return;
}
if (_v == clientId) {
k.send(JSON.stringify({ from: connectionId, to: "", type: "call-request", data: connectionId }));
}
});
}
2026-03-04 17:55:55 +08:00
}
2026-02-28 18:17:08 +08:00
2026-03-04 17:55:55 +08:00
/**
2026-04-23 15:22:24 +08:00
* 广1
2026-03-04 17:55:55 +08:00
* @param ws WebSocket连接实例
* @param message
*/
function onBroadcast(ws: WebSocket, message: any): void {
const broadcastMessage = message.message;
const targetConnectionId = message.targetConnectionId;
if (targetConnectionId) {
2026-04-23 15:22:24 +08:00
// 向指定连接组广播
if (connectionGroup.has(targetConnectionId)) {
const group = connectionGroup.get(targetConnectionId);
// 向组内所有成员发送消息
group.host.send(JSON.stringify({
type: "broadcast",
message: broadcastMessage,
from: "server"
}));
group.participants.forEach(participantWs => {
participantWs.send(JSON.stringify({
2026-03-04 17:55:55 +08:00
type: "broadcast",
message: broadcastMessage,
from: "server"
}));
2026-04-23 15:22:24 +08:00
});
2026-03-04 17:55:55 +08:00
}
} else {
// 全局广播:向所有客户端发送消息
clients.forEach((_v, k) => {
k.send(JSON.stringify({
type: "broadcast",
message: broadcastMessage,
from: "server"
}));
});
2026-02-28 18:17:08 +08:00
}
}
2026-03-04 17:55:55 +08:00
function AddHeartbeat(ws: WebSocket, connectionId: string) {
// 初始化心跳检测
2026-02-28 18:17:08 +08:00
(ws as any).lastActivity = Date.now();
// 设置心跳检测定时器每30秒发送一次ping
(ws as any).heartbeatTimer = setInterval(() => {
const now = Date.now();
// 检查上次活动时间如果超过60秒没有活动关闭连接
if (now - (ws as any).lastActivity > 10000) {
console.log('WebSocket connection timeout, closing...');
clearInterval((ws as any).heartbeatTimer);
2026-03-01 23:41:42 +08:00
//ws.close();
onDisconnect(ws, connectionId);
2026-02-28 18:17:08 +08:00
} else {
// 发送ping消息
2026-03-12 17:53:34 +08:00
ws.send(JSON.stringify({ from: connectionId, to: "", type: "on-message", data: { type: "ping"} }));
2026-02-28 18:17:08 +08:00
console.log('WebSocket connection heartbeat, lastActivity: ', (ws as any).lastActivity);
}
}, 3000);
2026-03-04 17:55:55 +08:00
}
function RemoveHeartbeat(ws: WebSocket) {
2026-02-28 18:17:08 +08:00
// 清除心跳检测定时器
if ((ws as any).heartbeatTimer) {
clearInterval((ws as any).heartbeatTimer);
}
2026-03-04 17:55:55 +08:00
}
2026-03-04 22:29:10 +08:00
/**
* ID的请求
* @param ws WebSocket连接实例
*/
function onGetAllConnectionIds(): string[] {
// 获取所有connectionId
2026-04-23 15:22:24 +08:00
const connectionIds = Array.from(connectionGroup.keys());
2026-03-04 22:29:10 +08:00
return connectionIds;
}
2026-03-10 19:09:41 +08:00
/**
2026-04-23 15:22:24 +08:00
* chat-message信令1
* host的消息转发给所有participantsparticipant的消息转发给host
2026-03-10 19:09:41 +08:00
* @param ws WebSocket连接实例
* @param message
*/
2026-03-12 12:10:40 +08:00
function onMessage(ws: WebSocket, message: any): void {
2026-03-10 19:09:41 +08:00
// 获取连接ID
const connectionId = message.connectionId;
const chatMessage = message.message;
2026-04-23 15:22:24 +08:00
if (connectionGroup.has(connectionId)) {
const group = connectionGroup.get(connectionId);
if (group.host === ws) {
// host发送消息转发给所有participants
group.participants.forEach(participantWs => {
participantWs.send(JSON.stringify({ from: connectionId, to: "", type: "on-message", data: chatMessage }));
});
} else {
// participant发送消息转发给host
group.host.send(JSON.stringify({ from: connectionId, to: "", type: "on-message", data: chatMessage }));
2026-03-10 19:09:41 +08:00
}
}
}
2026-03-04 17:55:55 +08:00
/**
* WebSocket处理器函数
*/
2026-04-23 15:22:24 +08:00
export { reset, add, remove, onConnect, onDisconnect, onOffer, onAnswer, onCandidate, onCallConnectionId, onBroadcast, onGetAllConnectionIds, AddHeartbeat, RemoveHeartbeat, onMessage, isHost, broadcastToGroup, connectionGroup };