Files
file-transfer-go/chuan-next/src/hooks/connection/webrtc/useWebRTCDataChannelManager.ts
MatrixSeven 15d23de5a7 feat: 优化文件传输逻辑,添加断开连接回调和清除发送方数据功能
- 使用更宽松的条件检查连接状态,确保文件列表和文件请求的发送时机
- 添加清除发送方数据的逻辑,当接收方离开房间时重置文件传输状态
- 在文件传输业务中设置断开连接回调,确保在连接断开时清理相关数据
- 更新数据通道和P2P连接状态的处理,增强连接状态的监控和反馈
2025-09-15 19:39:57 +08:00

383 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useCallback, useEffect, useRef } from 'react';
import { IWebConnectStateManager } from '../state/useWebConnectStateManager';
import { DataHandler, IRegisterEventHandler, IWebMessage, MessageHandler, Role, WebRTCDataChannelManager } from '../types';
/**
* WebRTC 数据通道管理 Hook
* 负责数据通道的创建和管理,处理数据通道消息的发送和接收
*/
export function useWebRTCDataChannelManager(
stateManager: IWebConnectStateManager
): WebRTCDataChannelManager & IRegisterEventHandler {
const dcRef = useRef<RTCDataChannel | null>(null);
const stateManagerRef = useRef(stateManager);
stateManagerRef.current = stateManager;
// 多通道消息处理器
const messageHandlers = useRef<Map<string, MessageHandler>>(new Map());
const dataHandlers = useRef<Map<string, DataHandler>>(new Map());
// 创建数据通道
const createDataChannel = useCallback((
pc: RTCPeerConnection,
role: Role,
isReconnect: boolean = false
) => {
console.log('[DataChannelManager] 创建数据通道...', { role, isReconnect });
// 如果已经存在数据通道,先关闭它
if (dcRef.current) {
console.log('[DataChannelManager] 关闭已存在的数据通道');
dcRef.current.close();
dcRef.current = null;
}
// 数据通道处理
if (role === 'sender') {
const dataChannel = pc.createDataChannel('shared-channel', {
ordered: true,
maxRetransmits: 3
});
dcRef.current = dataChannel;
dataChannel.onopen = (event) => {
console.log('[DataChannelManager] 数据通道已打开 (发送方)');
// 确保所有连接状态都正确更新
stateManagerRef.current.updateState({
isDataChannelConnected: true,
isConnected: true,
isPeerConnected: true,
error: null,
isConnecting: false,
canRetry: false,
state: 'open'
});
// 如果是重新连接,触发数据同步
if (isReconnect) {
console.log('[DataChannelManager] 发送方重新连接,数据通道已打开,准备同步数据');
// 发送同步请求消息
setTimeout(() => {
if (dataChannel.readyState === 'open') {
dataChannel.send(JSON.stringify({
type: 'sync-request',
payload: { timestamp: Date.now() }
}));
console.log('[DataChannelManager] 发送方发送数据同步请求');
}
}, 300); // 等待数据通道完全稳定
}
};
dataChannel.onmessage = handleDataChannelMessage;
dataChannel.onerror = (error) => {
console.error('[DataChannelManager] 数据通道错误:', error);
// 获取更详细的错误信息
let errorMessage = '数据通道连接失败';
let shouldRetry = false;
// 根据数据通道状态提供更具体的错误信息
switch (dataChannel.readyState) {
case 'connecting':
errorMessage = '数据通道正在连接中,请稍候...';
shouldRetry = true;
break;
case 'closing':
errorMessage = '数据通道正在关闭,连接即将断开';
break;
case 'closed':
errorMessage = '数据通道已关闭P2P连接失败';
shouldRetry = true;
break;
default:
// 检查PeerConnection状态
if (pc) {
switch (pc.connectionState) {
case 'failed':
errorMessage = 'P2P连接失败可能是网络防火墙阻止了连接请尝试切换网络或使用VPN';
shouldRetry = true;
break;
case 'disconnected':
errorMessage = 'P2P连接已断开网络可能不稳定';
shouldRetry = true;
break;
default:
errorMessage = '数据通道连接失败,可能是网络环境受限';
shouldRetry = true;
}
}
}
console.error(`[DataChannelManager] 数据通道详细错误 - 状态: ${dataChannel.readyState}, 消息: ${errorMessage}, 建议重试: ${shouldRetry}`);
stateManagerRef.current.updateState({
error: errorMessage,
isConnecting: false,
isPeerConnected: false, // 数据通道出错时P2P连接肯定不可用
isDataChannelConnected: false,
canRetry: shouldRetry // 设置是否可以重试
});
};
} else {
pc.ondatachannel = (event) => {
const dataChannel = event.channel;
dcRef.current = dataChannel;
dataChannel.onopen = (event) => {
console.log('[DataChannelManager] 数据通道已打开 (接收方)');
// 确保所有连接状态都正确更新
stateManagerRef.current.updateState({
isWebSocketConnected: true,
isDataChannelConnected: true,
isConnected: true,
isPeerConnected: true,
error: null,
isConnecting: false,
canRetry: false,
state: 'open'
});
// 如果是重新连接,触发数据同步
if (isReconnect) {
console.log('[DataChannelManager] 接收方重新连接,数据通道已打开,准备同步数据');
// 发送同步请求消息
setTimeout(() => {
if (dataChannel.readyState === 'open') {
dataChannel.send(JSON.stringify({
type: 'sync-request',
payload: { timestamp: Date.now() }
}));
console.log('[DataChannelManager] 接收方发送数据同步请求');
}
}, 300); // 等待数据通道完全稳定
}
};
dataChannel.onmessage = handleDataChannelMessage;
dataChannel.onerror = (error) => {
console.error('[DataChannelManager] 数据通道错误 (接收方):', error);
// 获取更详细的错误信息
let errorMessage = '数据通道连接失败';
let shouldRetry = false;
// 根据数据通道状态提供更具体的错误信息
switch (dataChannel.readyState) {
case 'connecting':
errorMessage = '数据通道正在连接中,请稍候...';
shouldRetry = true;
break;
case 'closing':
errorMessage = '数据通道正在关闭,连接即将断开';
break;
case 'closed':
errorMessage = '数据通道已关闭P2P连接失败';
shouldRetry = true;
break;
default:
// 检查PeerConnection状态
if (pc) {
switch (pc.connectionState) {
case 'failed':
errorMessage = 'P2P连接失败可能是网络防火墙阻止了连接请尝试切换网络或使用VPN';
shouldRetry = true;
break;
case 'disconnected':
errorMessage = 'P2P连接已断开网络可能不稳定';
shouldRetry = true;
break;
default:
errorMessage = '数据通道连接失败,可能是网络环境受限';
shouldRetry = true;
}
}
}
console.error(`[DataChannelManager] 数据通道详细错误 (接收方) - 状态: ${dataChannel.readyState}, 消息: ${errorMessage}, 建议重试: ${shouldRetry}`);
stateManagerRef.current.updateState({
error: errorMessage,
isConnecting: false,
isPeerConnected: false, // 数据通道出错时P2P连接肯定不可用
isDataChannelConnected: false,
canRetry: shouldRetry // 设置是否可以重试
});
};
};
}
console.log('[DataChannelManager] 数据通道创建完成,角色:', role, '是否重新连接:', isReconnect);
}, [stateManager]);
// 处理数据通道消息
const handleDataChannelMessage = useCallback((event: MessageEvent) => {
console.log('[DataChannelManager] 收到数据通道消息,类型:', typeof event.data);
console.log('[DataChannelManager] 数据通道当前状态:', messageHandlers.current);
if (typeof event.data === 'string') {
try {
const message = JSON.parse(event.data) as IWebMessage;
console.log('[DataChannelManager] 收到消息:', message.type, message.channel || 'default');
// 根据通道分发消息
if (message.channel) {
const handler = messageHandlers.current.get(message.channel);
if (handler) {
handler(message);
}
} else {
// 兼容旧版本,广播给所有处理器
messageHandlers.current.forEach(handler => handler(message));
}
} catch (error) {
console.error('[DataChannelManager] 解析消息失败:', error);
}
} else if (event.data instanceof ArrayBuffer) {
console.log('[DataChannelManager] 收到数据:', event.data.byteLength, 'bytes');
// 数据优先发给文件传输处理器
const fileHandler = dataHandlers.current.get('file-transfer');
if (fileHandler) {
fileHandler(event.data);
} else {
// 如果没有文件处理器,发给第一个处理器
const firstHandler = dataHandlers.current.values().next().value;
if (firstHandler) {
firstHandler(event.data);
}
}
}
}, []);
// 注册消息处理器
const registerMessageHandler = useCallback((channel: string, handler: MessageHandler) => {
console.log('[DataChannelManager] 注册消息处理器:', channel);
messageHandlers.current.set(channel, handler);
return () => {
console.log('[DataChannelManager] 取消注册消息处理器:', channel);
messageHandlers.current.delete(channel);
};
}, []);
// 注册数据处理器
const registerDataHandler = useCallback((channel: string, handler: DataHandler) => {
console.log('[DataChannelManager] 注册数据处理器:', channel);
dataHandlers.current.set(channel, handler);
return () => {
console.log('[DataChannelManager] 取消注册数据处理器:', channel);
dataHandlers.current.delete(channel);
};
}, []);
// 发送消息
const sendMessage = useCallback((message: IWebMessage, channel?: string) => {
const dataChannel = dcRef.current;
if (!dataChannel || dataChannel.readyState !== 'open') {
console.error('[DataChannelManager] 数据通道未准备就绪');
return false;
}
try {
const messageWithChannel = channel ? { ...message, channel } : message;
dataChannel.send(JSON.stringify(messageWithChannel));
console.log('[DataChannelManager] 发送消息:', message.type, channel || 'default');
return true;
} catch (error) {
console.error('[DataChannelManager] 发送消息失败:', error);
return false;
}
}, []);
// 发送二进制数据
const sendData = useCallback((data: ArrayBuffer) => {
const dataChannel = dcRef.current;
if (!dataChannel || dataChannel.readyState !== 'open') {
console.error('[DataChannelManager] 数据通道未准备就绪');
return false;
}
try {
dataChannel.send(data);
console.log('[DataChannelManager] 发送数据:', data.byteLength, 'bytes');
return true;
} catch (error) {
console.error('[DataChannelManager] 发送数据失败:', error);
return false;
}
}, []);
// 获取数据通道状态
const getChannelState = useCallback(() => {
return stateManagerRef.current.getState();
}, []);
// 实时更新数据通道状态
useEffect(() => {
const updateChannelState = () => {
const readyState = dcRef.current?.readyState || 'closed';
console.log('[DataChannelManager] 数据通道状态更新:', readyState);
// 更新状态存储中的数据通道状态
stateManagerRef.current.updateState({
state: readyState,
isDataChannelConnected: readyState === 'open'
});
};
// 立即更新一次
updateChannelState();
// 如果数据通道存在,设置状态变化监听
if (dcRef.current) {
const dc = dcRef.current;
const originalOnOpen = dc.onopen;
const originalOnClose = dc.onclose;
const originalOnError = dc.onerror;
dc.onopen = (event) => {
console.log('[DataChannelManager] 数据通道打开事件触发');
updateChannelState();
if (originalOnOpen) originalOnOpen.call(dc, event);
};
dc.onclose = (event) => {
console.log('[DataChannelManager] 数据通道关闭事件触发');
updateChannelState();
if (originalOnClose) originalOnClose.call(dc, event);
};
dc.onerror = (error) => {
console.log('[DataChannelManager] 数据通道错误事件触发');
updateChannelState();
if (originalOnError) originalOnError.call(dc, error);
};
}
// 清理函数
return () => {
if (dcRef.current) {
// 恢复原始事件处理器
// 注意:在实际应用中,可能需要更复杂的事件处理器管理
}
};
}, []);
return {
createDataChannel,
sendMessage,
sendData,
getConnectState: getChannelState,
registerDataHandler,
registerMessageHandler,
handleDataChannelMessage,
};
}