【m】增加多用户连接

This commit is contained in:
2026-04-23 15:22:24 +08:00
parent cb32582c98
commit 852a169c30
8 changed files with 797 additions and 155 deletions

View File

@@ -19,11 +19,21 @@ let isPrivate: boolean;
const clients: Map<WebSocket, Set<string>> = new Map<WebSocket, Set<string>>();
/**
* 连接对映射
* 键: connectionId
* 值: [WebSocket实例1, WebSocket实例2]
* 连接组结构
* host: 主机WebSocket实例第一个连接的客户端
* participants: 参与者WebSocket集合后续连接的客户端
*/
const connectionPair: Map<string, [WebSocket, WebSocket]> = new Map<string, [WebSocket, WebSocket]>();
interface ConnectionGroup {
host: WebSocket;
participants: Set<WebSocket>;
}
/**
* 连接组映射
* 键: connectionId
* 值: ConnectionGroup1个host + 多个participants
*/
const connectionGroup: Map<string, ConnectionGroup> = new Map<string, ConnectionGroup>();
/**
* 获取或创建WebSocket会话的连接ID集合
@@ -66,6 +76,37 @@ function add(ws: WebSocket): void {
console.log(`Add WebSocket: ${id}`);
}
/**
* 判断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));
}
}
/**
* 移除WebSocket连接
* @param ws WebSocket连接实例
@@ -73,20 +114,25 @@ function add(ws: WebSocket): void {
function remove(ws: WebSocket): void {
// 获取连接的所有连接ID
const connectionIds = clients.get(ws);
if (!connectionIds) return;
// 遍历所有连接ID
connectionIds.forEach(connectionId => {
// 获取连接对
const pair = connectionPair.get(connectionId);
if (pair) {
// 找到另一个WebSocket实例
const otherSessionWs = pair[0] == ws ? pair[1] : pair[0];
if (otherSessionWs) {
// 向另一个连接发送断开连接消息
otherSessionWs.send(JSON.stringify({ type: "disconnect", connectionId: connectionId }));
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 }));
}
}
// 从连接对映射中删除
connectionPair.delete(connectionId);
// 记录删除连接ID的日志
console.log(`Remove connectionId: ${connectionId}`);
});
@@ -96,7 +142,8 @@ function remove(ws: WebSocket): void {
}
/**
* 处理连接请求
* 处理连接请求1对多模式
* 第一个连接的客户端成为host后续连接的客户端成为participants
* @param ws WebSocket连接实例
* @param connectionId 连接ID
*/
@@ -104,21 +151,16 @@ function onConnect(ws: WebSocket, connectionId: string): void {
let polite = true;
// 处理私有模式
if (isPrivate) {
if (connectionPair.has(connectionId)) {
const pair = connectionPair.get(connectionId);
if (pair[0] != null && pair[1] != null) {
// 连接ID已被使用
ws.send(JSON.stringify({ type: "error", message: `${connectionId}: This connection id is already used.` }));
return;
} else if (pair[0] != null) {
// 找到配对连接
connectionPair.set(connectionId, [pair[0], ws]);
}
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}`);
} else {
// 创建新的连接对
connectionPair.set(connectionId, [ws, null]);
// 第一个连接成为host
connectionGroup.set(connectionId, { host: ws, participants: new Set<WebSocket>() });
polite = false;
console.log(`Host created connectionId: ${connectionId}`);
}
}
@@ -126,35 +168,44 @@ function onConnect(ws: WebSocket, connectionId: string): void {
const connectionIds = getOrCreateConnectionIds(ws);
// 添加连接ID
connectionIds.add(connectionId);
// 发送连接成功消息
ws.send(JSON.stringify({ type: "connect", connectionId: connectionId, polite: polite }));
// 发送连接成功消息(包含角色信息)
const role = polite ? 'participant' : 'host';
ws.send(JSON.stringify({ type: "connect", connectionId: connectionId, polite: polite, role: role }));
//启用心跳包
//AddHeartbeat(ws, connectionId);
}
/**
* 处理断开连接请求
* 处理断开连接请求1对多模式
* @param ws WebSocket连接实例
* @param connectionId 连接ID
*/
function onDisconnect(ws: WebSocket, connectionId: string): void {
// 获取连接的连接ID集合
const connectionIds = clients.get(ws);
// 从集合中删除连接ID
connectionIds.delete(connectionId);
if (connectionIds) {
// 从集合中删除连接ID
connectionIds.delete(connectionId);
}
// 处理连接
if (connectionPair.has(connectionId)) {
const pair = connectionPair.get(connectionId);
// 找到另一个WebSocket实例
const otherSessionWs = pair[0] == ws ? pair[1] : pair[0];
if (otherSessionWs) {
// 向另一个连接发送断开连接消息
otherSessionWs.send(JSON.stringify({ type: "disconnect", connectionId: connectionId }));
// 处理连接
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}`);
}
}
// 从连接对映射中删除
connectionPair.delete(connectionId);
// 向当前连接发送断开连接消息
ws.send(JSON.stringify({ type: "disconnect", connectionId: connectionId }));
//RemoveHeartbeat(ws);
@@ -163,7 +214,8 @@ function onDisconnect(ws: WebSocket, connectionId: string): void {
}
/**
* 处理offer信令
* 处理offer信令1对多模式
* host的offer转发给所有participantsparticipant的offer转发给host
* @param ws WebSocket连接实例
* @param message 消息数据
*/
@@ -175,22 +227,27 @@ function onOffer(ws: WebSocket, message: any): void {
// 处理私有模式
if (isPrivate) {
if (connectionPair.has(connectionId)) {
const pair = connectionPair.get(connectionId);
// 找到另一个WebSocket实例
const otherSessionWs = pair[0] == ws ? pair[1] : pair[0];
if (otherSessionWs) {
// 设置为polite模式
if (connectionGroup.has(connectionId)) {
const group = connectionGroup.get(connectionId);
if (group.host === ws) {
// host发送offer转发给所有participants
newOffer.polite = true;
// 发送offer消息
otherSessionWs.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer }));
group.participants.forEach(participantWs => {
participantWs.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer }));
});
} else {
// participant发送offer转发给host
newOffer.polite = true;
group.host.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer }));
}
}
return;
}
// 公共模式:创建新的连接
connectionPair.set(connectionId, [ws, null]);
// 公共模式:创建新的连接组(如果不存在)
if (!connectionGroup.has(connectionId)) {
connectionGroup.set(connectionId, { host: ws, participants: new Set<WebSocket>() });
}
// 向所有其他客户端广播offer
clients.forEach((_v, k) => {
if (k == ws) {
@@ -201,7 +258,8 @@ function onOffer(ws: WebSocket, message: any): void {
}
/**
* 处理answer信令
* 处理answer信令1对多模式
* participant的answer转发给host
* @param ws WebSocket连接实例
* @param message 消息数据
*/
@@ -215,27 +273,27 @@ function onAnswer(ws: WebSocket, message: any): void {
// 创建新的answer
const newAnswer = new Answer(message.sdp, Date.now());
// 检查连接是否存在
if (!connectionPair.has(connectionId)) {
// 检查连接是否存在
if (!connectionGroup.has(connectionId)) {
return;
}
// 获取连接对
const pair = connectionPair.get(connectionId);
// 找到另一个WebSocket实例
const otherSessionWs = pair[0] == ws ? pair[1] : pair[0];
const group = connectionGroup.get(connectionId);
// 公共模式:更新连接对
if (!isPrivate) {
connectionPair.set(connectionId, [otherSessionWs, ws]);
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 }));
}
// 发送answer消息
otherSessionWs.send(JSON.stringify({ from: connectionId, to: "", type: "answer", data: newAnswer }));
}
/**
* 处理candidate信令
* 处理candidate信令1对多模式
* host的candidate转发给所有participantsparticipant的candidate转发给host
* @param ws WebSocket连接实例
* @param message 消息数据
*/
@@ -247,13 +305,16 @@ function onCandidate(ws: WebSocket, message: any): void {
// 处理私有模式
if (isPrivate) {
if (connectionPair.has(connectionId)) {
const pair = connectionPair.get(connectionId);
// 找到另一个WebSocket实例
const otherSessionWs = pair[0] == ws ? pair[1] : pair[0];
if (otherSessionWs) {
// 发送candidate消息
otherSessionWs.send(JSON.stringify({ from: connectionId, to: "", type: "candidate", data: candidate }));
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 }));
}
}
return;
@@ -263,26 +324,29 @@ function onCandidate(ws: WebSocket, message: any): void {
// 获取连接ID
const connectionId = message.connectionId;
const clientId = message.clientId;
clients.forEach((_v, k) => {
if (k === ws) {
return;
// 在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 }));
}
if (_v == clientId) {
k.send(JSON.stringify({ from: connectionId, to: "", type: "call-request", data: connectionId }));
}
});
} else {
// 兼容旧的广播方式
clients.forEach((_v, k) => {
if (k === ws) {
return;
}
if (_v == clientId) {
k.send(JSON.stringify({ from: connectionId, to: "", type: "call-request", data: connectionId }));
}
});
}
}
/**
* 处理广播消息请求
* @param ws WebSocket连接实例
* @param message 消息数据
*/
/**
* 处理广播消息请求
* 处理广播消息请求1对多模式
* @param ws WebSocket连接实例
* @param message 消息数据
*/
@@ -291,24 +355,22 @@ function onCandidate(ws: WebSocket, message: any): void {
const targetConnectionId = message.targetConnectionId;
if (targetConnectionId) {
// 向指定连接广播
if (connectionPair.has(targetConnectionId)) {
const pair = connectionPair.get(targetConnectionId);
// 向连接对中的两个WebSocket实例发送消息
if (pair[0]) {
pair[0].send(JSON.stringify({
// 向指定连接广播
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({
type: "broadcast",
message: broadcastMessage,
from: "server"
}));
}
if (pair[1]) {
pair[1].send(JSON.stringify({
type: "broadcast",
message: broadcastMessage,
from: "server"
}));
}
});
}
} else {
// 全局广播:向所有客户端发送消息
@@ -353,16 +415,12 @@ function onCandidate(ws: WebSocket, message: any): void {
*/
function onGetAllConnectionIds(): string[] {
// 获取所有connectionId
const connectionIds = Array.from(connectionPair.keys());
// 发送连接ID列表给客户端
// ws.send(JSON.stringify({
// type: "connection-ids",
// connectionIds: connectionIds
// }));
const connectionIds = Array.from(connectionGroup.keys());
return connectionIds;
}
/**
* 处理chat-message信令
* 处理chat-message信令1对多模式
* host的消息转发给所有participantsparticipant的消息转发给host
* @param ws WebSocket连接实例
* @param message 消息数据
*/
@@ -370,17 +428,20 @@ function onCandidate(ws: WebSocket, message: any): void {
// 获取连接ID
const connectionId = message.connectionId;
const chatMessage = message.message;
if (connectionPair.has(connectionId)) {
const pair = connectionPair.get(connectionId);
// 找到另一个WebSocket实例
const otherSessionWs = pair[0] == ws ? pair[1] : pair[0];
if (otherSessionWs) {
// 发送chat-message消息
otherSessionWs.send(JSON.stringify({ from: connectionId, to: "", type: "on-message", data: chatMessage }));
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 }));
}
}
}
/**
* 导出WebSocket处理器函数
*/
export { reset, add, remove, onConnect, onDisconnect, onOffer, onAnswer, onCandidate, onCallConnectionId, onBroadcast, onGetAllConnectionIds, AddHeartbeat, RemoveHeartbeat ,onMessage};
export { reset, add, remove, onConnect, onDisconnect, onOffer, onAnswer, onCandidate, onCallConnectionId, onBroadcast, onGetAllConnectionIds, AddHeartbeat, RemoveHeartbeat, onMessage, isHost, broadcastToGroup, connectionGroup };