Using a simple backpressure mechanism

This commit is contained in:
david_bai
2025-09-07 23:38:15 +08:00
parent 230a06b3fb
commit 5ca911d1e1
8 changed files with 222 additions and 426 deletions
@@ -205,31 +205,33 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
): Promise<void> { ): Promise<void> {
const fileId = generateFileId(file); const fileId = generateFileId(file);
const peerState = this.stateManager.getPeerState(peerId); const peerState = this.stateManager.getPeerState(peerId);
const transferStartTime = performance.now();
// 1. 初始化流式文件读取器 // 1. 初始化流式文件读取器
const streamReader = new StreamingFileReader(file, peerState.readOffset || 0); const streamReader = new StreamingFileReader(file, peerState.readOffset || 0);
postLogToBackend( postLogToBackend(
`[DEBUG] 🚀 STREAMING_SEND start - file: ${file.name}, size: ${file.size}, startOffset: ${peerState.readOffset || 0}` `[PERF] 🚀 TRANSFER_START - file: ${file.name}, size: ${(file.size/1024/1024).toFixed(1)}MB, startOffset: ${peerState.readOffset || 0}`
); );
// 初始化网络性能监控
this.stateManager.initializeNetworkPerformance(peerId);
try { try {
let totalBytesSent = 0; let totalBytesSent = 0;
let networkChunkIndex = 0; let networkChunkIndex = 0;
let totalReadTime = 0;
let totalSendTime = 0;
let totalProgressTime = 0;
let lastProgressTime = performance.now();
// 2. 流式处理:逐个获取64KB网络块并发送 // 2. 流式处理:逐个获取64KB网络块并发送
while (peerState.isSending) { while (peerState.isSending) {
// 获取下一个网络块 // 获取下一个网络块
const readStartTime = performance.now();
const chunkInfo = await streamReader.getNextNetworkChunk(); const chunkInfo = await streamReader.getNextNetworkChunk();
const readTime = performance.now() - readStartTime;
totalReadTime += readTime;
// 检查是否已完成 // 检查是否已完成
if (chunkInfo.chunk === null) { if (chunkInfo.chunk === null) {
postLogToBackend(
`[DEBUG] 🏁 STREAMING_SEND completed - totalChunks: ${networkChunkIndex}, totalBytes: ${totalBytesSent}`
);
break; break;
} }
@@ -245,6 +247,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
// 发送带嵌入元数据的网络块 // 发送带嵌入元数据的网络块
let sendSuccessful = false; let sendSuccessful = false;
const sendStartTime = performance.now();
try { try {
sendSuccessful = await this.networkTransmitter.sendEmbeddedChunk( sendSuccessful = await this.networkTransmitter.sendEmbeddedChunk(
chunkInfo.chunk, chunkInfo.chunk,
@@ -254,16 +257,13 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
if (sendSuccessful) { if (sendSuccessful) {
totalBytesSent += chunkInfo.chunk.byteLength; totalBytesSent += chunkInfo.chunk.byteLength;
postLogToBackend(
`[DEBUG] ✓ STREAMING_CHUNK sent #${chunkInfo.chunkIndex}/${chunkInfo.totalChunks} - size: ${chunkInfo.chunk.byteLength}, isLast: ${chunkInfo.isLastChunk}`
);
} }
} catch (error) { } catch (error) {
postLogToBackend( this.log("warn", `Chunk send failed #${chunkInfo.chunkIndex}: ${error}`);
`[DEBUG] ❌ STREAMING_CHUNK failed #${chunkInfo.chunkIndex}: ${error}`
);
sendSuccessful = false; sendSuccessful = false;
} }
const sendTime = performance.now() - sendStartTime;
totalSendTime += sendTime;
// 更新状态和进度 // 更新状态和进度
if (sendSuccessful) { if (sendSuccessful) {
@@ -271,6 +271,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
readOffset: chunkInfo.fileOffset + chunkInfo.chunk.byteLength readOffset: chunkInfo.fileOffset + chunkInfo.chunk.byteLength
}); });
const progressStartTime = performance.now();
await this.progressTracker.updateFileProgress( await this.progressTracker.updateFileProgress(
chunkInfo.chunk.byteLength, chunkInfo.chunk.byteLength,
fileId, fileId,
@@ -278,40 +279,57 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
peerId, peerId,
true true
); );
} else { const progressTime = performance.now() - progressStartTime;
this.log("warn", `Send failed, continuing with next chunk...`, { totalProgressTime += progressTime;
chunkIndex: chunkInfo.chunkIndex,
fileId,
peerId
});
} }
networkChunkIndex++; networkChunkIndex++;
// 每100个chunk输出一次进度
if (networkChunkIndex % 100 === 0 || chunkInfo.isLastChunk) {
const currentTime = performance.now();
const intervalTime = currentTime - lastProgressTime;
const recentChunks = Math.min(100, networkChunkIndex);
const recentMB = (recentChunks * 64) / 1024; // 假设每个chunk 64KB
const speedMBps = recentMB / (intervalTime / 1000);
postLogToBackend(
`[PERF] 📊 PROGRESS #${networkChunkIndex}/${chunkInfo.totalChunks} - speed: ${speedMBps.toFixed(1)}MB/s, read: ${readTime.toFixed(1)}ms, send: ${sendTime.toFixed(1)}ms`
);
lastProgressTime = currentTime;
}
// 检查是否为最后一块 // 检查是否为最后一块
if (chunkInfo.isLastChunk) { if (chunkInfo.isLastChunk) {
postLogToBackend(
`[DEBUG] 🏁 Last chunk sent, waiting for receiver confirmation...`
);
break; break;
} }
} }
const totalTime = performance.now() - transferStartTime;
const avgSpeedMBps = (totalBytesSent / 1024 / 1024) / (totalTime / 1000);
const avgReadTimePer100 = totalReadTime / Math.max(1, networkChunkIndex / 100);
const avgSendTimePer100 = totalSendTime / Math.max(1, networkChunkIndex / 100);
const avgProgressTimePer100 = totalProgressTime / Math.max(1, networkChunkIndex / 100);
postLogToBackend( postLogToBackend(
`[DEBUG] ✅ File send completed - ${file.name}, totalChunks: ${networkChunkIndex}, totalBytes: ${totalBytesSent}` `[PERF] ✅ TRANSFER_COMPLETE - file: ${file.name}, time: ${(totalTime/1000).toFixed(1)}s, speed: ${avgSpeedMBps.toFixed(1)}MB/s, chunks: ${networkChunkIndex}`
);
postLogToBackend(
`[PERF] 🖼️ TIME_BREAKDOWN - read: ${avgReadTimePer100.toFixed(1)}ms/100chunks, send: ${avgSendTimePer100.toFixed(1)}ms/100chunks, progress: ${avgProgressTimePer100.toFixed(1)}ms/100chunks`
); );
} catch (error: any) { } catch (error: any) {
const errorMessage = `Streaming send error: ${error.message}`; const errorMessage = `Streaming send error: ${error.message}`;
const totalTime = performance.now() - transferStartTime;
postLogToBackend( postLogToBackend(
`[DEBUG] ❌ STREAMING_ERROR: ${errorMessage}` `[PERF] ❌ TRANSFER_ERROR after ${(totalTime/1000).toFixed(1)}s: ${errorMessage}`
); );
this.fireError(errorMessage, { fileId, peerId, offset: peerState.readOffset }); this.fireError(errorMessage, { fileId, peerId, offset: peerState.readOffset });
throw error; throw error;
} finally { } finally {
// 清理资源 // 清理资源
streamReader.cleanup(); streamReader.cleanup();
postLogToBackend(`[DEBUG] 🧹 StreamingFileReader cleaned up`);
} }
} }
+4 -8
View File
@@ -29,7 +29,7 @@ export class MessageHandler {
* 🎯 处理接收到的信令消息 * 🎯 处理接收到的信令消息
*/ */
handleSignalingMessage(message: WebRTCMessage, peerId: string): void { handleSignalingMessage(message: WebRTCMessage, peerId: string): void {
postLogToBackend(`[DEBUG] 📨 Message received - type: ${message.type}, peerId: ${peerId}`); // 删除频繁的消息接收日志
switch (message.type) { switch (message.type) {
case "fileRequest": case "fileRequest":
@@ -83,7 +83,7 @@ export class MessageHandler {
peerId: string peerId: string
): void { ): void {
postLogToBackend( postLogToBackend(
`[DEBUG] 📥 Received fileReceiveComplete - fileId: ${message.fileId}, receivedSize: ${message.receivedSize}, receivedChunks: ${message.receivedChunks}, storeUpdated: ${message.storeUpdated}` `[PERF] ✅ FILE_COMPLETE - fileId: ${message.fileId}, size: ${(message.receivedSize/1024/1024).toFixed(1)}MB, chunks: ${message.receivedChunks}`
); );
// 清理发送状态 // 清理发送状态
@@ -94,14 +94,10 @@ export class MessageHandler {
// 触发单文件100%进度(只有非文件夹情况) // 触发单文件100%进度(只有非文件夹情况)
if (!peerState.currentFolderName) { if (!peerState.currentFolderName) {
postLogToBackend( // 删除频繁的进度日志
`[DEBUG] 🎯 Setting single file progress to 100% - ${message.fileId}`
);
peerState.progressCallback?.(message.fileId, 1, 0); peerState.progressCallback?.(message.fileId, 1, 0);
} else { } else {
postLogToBackend( // 删除频繁的文件夹进度日志
`[DEBUG] 📁 File in folder completed, not setting progress yet - ${message.fileId} (folder: ${peerState.currentFolderName})`
);
} }
this.delegate.log("log", `File reception confirmed by peer ${peerId}`, { this.delegate.log("log", `File reception confirmed by peer ${peerId}`, {
+98 -159
View File
@@ -1,17 +1,11 @@
import { EmbeddedChunkMeta } from "@/types/webrtc"; import { EmbeddedChunkMeta } from "@/types/webrtc";
import { StateManager } from "./StateManager"; import { StateManager } from "./StateManager";
import { TransferConfig } from "./TransferConfig";
import WebRTC_Initiator from "../webrtc_Initiator"; import WebRTC_Initiator from "../webrtc_Initiator";
import { postLogToBackend } from "@/app/config/api"; import { postLogToBackend } from "@/app/config/api";
/** /**
* 🚀 发送策略枚举 * 🚀 网络传输器 - 简化版
*/ * 使用WebRTC原生bufferedAmountLowThreshold进行背压控制
type SendStrategy = "AGGRESSIVE" | "NORMAL" | "CAUTIOUS" | "WAIT";
/**
* 🚀 网络传输器
* 负责所有WebRTC数据传输、背压控制、自适应性能调整
*/ */
export class NetworkTransmitter { export class NetworkTransmitter {
constructor( constructor(
@@ -27,21 +21,48 @@ export class NetworkTransmitter {
metadata: EmbeddedChunkMeta, metadata: EmbeddedChunkMeta,
peerId: string peerId: string
): Promise<boolean> { ): Promise<boolean> {
const startTime = performance.now();
try { try {
// 1. 构建融合数据包 // 1. 构建融合数据包
const embeddedPacket = this.createEmbeddedChunkPacket(chunkData, metadata); const createStartTime = performance.now();
const embeddedPacket = this.createEmbeddedChunkPacket(
chunkData,
metadata
);
const createTime = performance.now() - createStartTime;
// 2. 发送完整的融合数据包(不可分片) // 2. 发送完整的融合数据包(不可分片)
const sendStartTime = performance.now();
await this.sendSingleData(embeddedPacket, peerId); await this.sendSingleData(embeddedPacket, peerId);
const sendTime = performance.now() - sendStartTime;
postLogToBackend( const totalTime = performance.now() - startTime;
`[DEBUG] ✓ EMBEDDED chunk #${metadata.chunkIndex}/${metadata.totalChunks} sent - size: ${chunkData.byteLength}, packet: ${embeddedPacket.byteLength} bytes, isLast: ${metadata.isLastChunk}`
); // 只在关键节点或耗时较长时输出日志
if (
metadata.chunkIndex % 100 === 0 ||
metadata.isLastChunk ||
totalTime > 50
) {
postLogToBackend(
`[PERF] ✓ CHUNK #${metadata.chunkIndex}/${
metadata.totalChunks
} - total: ${totalTime.toFixed(1)}ms, create: ${createTime.toFixed(
1
)}ms, send: ${sendTime.toFixed(1)}ms, size: ${(
chunkData.byteLength / 1024
).toFixed(1)}KB`
);
}
return true; return true;
} catch (error) { } catch (error) {
const totalTime = performance.now() - startTime;
postLogToBackend( postLogToBackend(
`[DEBUG] ❌ EMBEDDED chunk #${metadata.chunkIndex} send failed: ${error}` `[PERF] ❌ CHUNK #${
metadata.chunkIndex
} FAILED after ${totalTime.toFixed(1)}ms: ${error}`
); );
return false; return false;
} }
@@ -72,10 +93,6 @@ export class NetworkTransmitter {
finalPacket.set(metaBytes, 4); finalPacket.set(metaBytes, 4);
finalPacket.set(new Uint8Array(chunkData), 4 + metaBytes.length); finalPacket.set(new Uint8Array(chunkData), 4 + metaBytes.length);
postLogToBackend(
`[DEBUG] 📦 EMBEDDED packet created - chunkIndex: ${chunkMeta.chunkIndex}, metaSize: ${metaBytes.length}, chunkSize: ${chunkData.byteLength}, totalSize: ${totalLength}`
);
return finalPacket.buffer; return finalPacket.buffer;
} }
@@ -91,25 +108,72 @@ export class NetworkTransmitter {
throw new Error("Data channel not found"); throw new Error("Data channel not found");
} }
// 调试信息 // 简化背压控制
const dataType = typeof data === "string" ? "string" : data instanceof ArrayBuffer ? "ArrayBuffer" : "unknown"; await this.simpleBufferControl(dataChannel, peerId);
const dataSize = typeof data === "string" ? data.length : data instanceof ArrayBuffer ? data.byteLength : 0;
// 智能背压控制
await this.smartBufferControl(dataChannel, peerId);
// 直接发送,不分片 // 直接发送,不分片
const sendResult = this.webrtcConnection.sendData(data, peerId); const sendResult = this.webrtcConnection.sendData(data, peerId);
if (!sendResult) { if (!sendResult) {
const dataType =
typeof data === "string"
? "string"
: data instanceof ArrayBuffer
? "ArrayBuffer"
: "unknown";
const dataSize =
typeof data === "string"
? data.length
: data instanceof ArrayBuffer
? data.byteLength
: 0;
const errorMessage = `sendData failed for ${dataType} data of size ${dataSize}`; const errorMessage = `sendData failed for ${dataType} data of size ${dataSize}`;
postLogToBackend(`[DEBUG] ❌ ${errorMessage}`); postLogToBackend(`[PERF] ❌ ${errorMessage}`);
throw new Error(errorMessage); throw new Error(errorMessage);
} }
}
postLogToBackend( /**
`[DEBUG] 📤 Data sent successfully - type: ${dataType}, size: ${dataSize}` * 🎯 原生背压控制 - 使用WebRTC标准机制
); */
private async simpleBufferControl(
dataChannel: RTCDataChannel,
peerId: string
): Promise<void> {
const maxBuffer = 3 * 1024 * 1024; // 3MB最大缓冲
const lowThreshold = 512 * 1024; // 512KB低阈值
// 设置原生低阈值
if (dataChannel.bufferedAmountLowThreshold !== lowThreshold) {
dataChannel.bufferedAmountLowThreshold = lowThreshold;
}
// 如果缓冲区超过最大值,等待降到低阈值
if (dataChannel.bufferedAmount > maxBuffer) {
const startTime = performance.now();
const initialBuffered = dataChannel.bufferedAmount;
await new Promise<void>((resolve) => {
const onLow = () => {
dataChannel.removeEventListener("bufferedamountlow", onLow);
resolve();
};
dataChannel.addEventListener("bufferedamountlow", onLow);
// 添加超时保护,避免无限等待
setTimeout(() => {
dataChannel.removeEventListener("bufferedamountlow", onLow);
resolve();
}, 5000); // 5秒超时
});
const waitTime = performance.now() - startTime;
postLogToBackend(
`[PERF] 🚀 BACKPRESSURE - wait: ${waitTime.toFixed(
1
)}ms, buffered: ${initialBuffered} -> ${dataChannel.bufferedAmount}`
);
}
} }
/** /**
@@ -133,7 +197,7 @@ export class NetworkTransmitter {
} }
} catch (error) { } catch (error) {
const errorMessage = `sendWithBackpressure failed: ${error}`; const errorMessage = `sendWithBackpressure failed: ${error}`;
postLogToBackend(`[DEBUG] ${errorMessage}`); postLogToBackend(`[PERF] ❌ ${errorMessage}`);
throw new Error(errorMessage); throw new Error(errorMessage);
} }
} }
@@ -145,7 +209,7 @@ export class NetworkTransmitter {
data: ArrayBuffer, data: ArrayBuffer,
peerId: string peerId: string
): Promise<void> { ): Promise<void> {
const networkChunkSize = TransferConfig.FILE_CONFIG.NETWORK_CHUNK_SIZE; const networkChunkSize = 65536; // 64KB
const totalSize = data.byteLength; const totalSize = data.byteLength;
// 如果数据小于64KB,直接发送 // 如果数据小于64KB,直接发送
@@ -164,146 +228,23 @@ export class NetworkTransmitter {
// 发送分片 // 发送分片
await this.sendSingleData(chunk, peerId); await this.sendSingleData(chunk, peerId);
postLogToBackend(
`[DEBUG] 📦 Fragment sent #${fragmentIndex} - size: ${chunkSize}`
);
offset += chunkSize; offset += chunkSize;
fragmentIndex++; fragmentIndex++;
} }
} }
/**
* 🎯 智能缓冲控制策略
*/
private async smartBufferControl(
dataChannel: RTCDataChannel,
peerId: string
): Promise<void> {
const strategy = await this.intelligentSendControl(dataChannel, peerId);
switch (strategy) {
case "AGGRESSIVE":
// 积极模式:立即发送
return;
case "NORMAL":
// 正常模式:轻微等待
await new Promise<void>((resolve) => setTimeout(resolve, 5));
return;
case "CAUTIOUS":
// 谨慎模式:短暂等待
await new Promise<void>((resolve) => setTimeout(resolve, 10));
return;
case "WAIT":
// 等待模式:主动轮询等待
await this.activePollingWait(dataChannel, peerId);
return;
}
}
/**
* 🎯 自适应智能发送控制策略
*/
private async intelligentSendControl(
dataChannel: RTCDataChannel,
peerId: string
): Promise<SendStrategy> {
const bufferedAmount = dataChannel.bufferedAmount;
const adaptiveThreshold = this.stateManager.getAdaptiveThreshold(peerId);
const utilizationRate = bufferedAmount / adaptiveThreshold;
// 根据网络性能动态调整策略阈值
const perf = this.stateManager.getNetworkPerformance(peerId);
const networkQuality = this.getNetworkQuality(perf?.avgClearingRate || 0);
let thresholds = TransferConfig.getAdaptiveThresholds(perf?.avgClearingRate || 0).strategy;
if (utilizationRate < thresholds.aggressive) {
return "AGGRESSIVE";
} else if (utilizationRate < thresholds.normal) {
return "NORMAL";
} else if (utilizationRate < (thresholds.cautious || TransferConfig.SEND_STRATEGY_CONFIG.CAUTIOUS_THRESHOLD)) {
return "CAUTIOUS";
} else {
return "WAIT";
}
}
/**
* 🔍 获取网络质量评级
*/
private getNetworkQuality(avgClearingRate: number): "good" | "average" | "poor" {
const config = TransferConfig.QUALITY_CONFIG;
if (avgClearingRate > config.GOOD_NETWORK_SPEED) {
return "good";
} else if (avgClearingRate > config.AVERAGE_NETWORK_SPEED) {
return "average";
} else {
return "poor";
}
}
/**
* 🔄 主动轮询等待(WAIT模式)
*/
private async activePollingWait(
dataChannel: RTCDataChannel,
peerId: string
): Promise<void> {
const config = TransferConfig.SEND_STRATEGY_CONFIG;
const startTime = Date.now();
const adaptiveThreshold = this.stateManager.getAdaptiveThreshold(peerId);
const threshold_low = adaptiveThreshold * 0.3;
const initialBuffered = dataChannel.bufferedAmount;
let pollCount = 0;
while (dataChannel.bufferedAmount > threshold_low) {
pollCount++;
if (Date.now() - startTime > config.MAX_WAIT_TIME) {
postLogToBackend(
`[DEBUG] ⚠️ Buffer wait timeout - buffered: ${dataChannel.bufferedAmount}, threshold: ${adaptiveThreshold}, waitTime: ${Date.now() - startTime}ms`
);
break;
}
await new Promise<void>((resolve) =>
setTimeout(resolve, config.POLLING_INTERVAL)
);
}
// 记录等待结束状态并更新网络性能
const waitTime = Date.now() - startTime;
const finalBuffered = dataChannel.bufferedAmount;
const clearedBytes = initialBuffered - finalBuffered;
const clearingRate = waitTime > 0 ? clearedBytes / 1024 / (waitTime / 1000) : 0;
// 更新网络性能学习
if (clearingRate > 0) {
this.stateManager.updateNetworkPerformance(peerId, clearingRate, waitTime);
}
postLogToBackend(
`[DEBUG] 📊 Wait completed - cleared: ${clearedBytes} bytes, rate: ${clearingRate.toFixed(2)} KB/s, time: ${waitTime}ms, polls: ${pollCount}`
);
}
/** /**
* 📊 获取传输统计信息 * 📊 获取传输统计信息
*/ */
public getTransmissionStats(peerId: string) { public getTransmissionStats(peerId: string) {
const networkPerf = this.stateManager.getNetworkPerformance(peerId);
const dataChannel = this.webrtcConnection.dataChannels.get(peerId); const dataChannel = this.webrtcConnection.dataChannels.get(peerId);
return { return {
peerId, peerId,
networkPerformance: networkPerf || null,
currentBufferedAmount: dataChannel?.bufferedAmount || 0, currentBufferedAmount: dataChannel?.bufferedAmount || 0,
adaptiveThreshold: this.stateManager.getAdaptiveThreshold(peerId), bufferedAmountLowThreshold: dataChannel?.bufferedAmountLowThreshold || 0,
channelState: dataChannel?.readyState || 'unknown', channelState: dataChannel?.readyState || "unknown",
}; };
} }
@@ -311,8 +252,6 @@ export class NetworkTransmitter {
* 🧹 清理资源 * 🧹 清理资源
*/ */
public cleanup(): void { public cleanup(): void {
// NetworkTransmitter本身没有需要清理的资源 postLogToBackend("[PERF] 🧹 NetworkTransmitter cleaned up");
// 实际的清理工作由StateManager和WebRTC_Initiator处理
postLogToBackend("[DEBUG] 🧹 NetworkTransmitter cleaned up");
} }
} }
+7 -12
View File
@@ -31,9 +31,6 @@ export class ProgressTracker {
// 重要修复:只有成功发送的数据才更新统计 // 重要修复:只有成功发送的数据才更新统计
if (!wasActuallySent) { if (!wasActuallySent) {
postLogToBackend(
`[DEBUG] ⚠️ Data send failed, not updating progress - fileId: ${fileId}, size: ${byteLength}`
);
return; return;
} }
@@ -57,9 +54,7 @@ export class ProgressTracker {
// 这对于断点续传更加健壮和正确 // 这对于断点续传更加健壮和正确
currentBytes = this.stateManager.getFolderBytesSent(peerId, folderName); currentBytes = this.stateManager.getFolderBytesSent(peerId, folderName);
postLogToBackend( // 删除频繁的文件夹进度日志
`[DEBUG] 📁 Folder progress update - folder: ${folderName}, file: ${fileId}, currentBytes: ${currentBytes}, totalSize: ${totalSize}`
);
} }
// 更新速度计算器 // 更新速度计算器
@@ -67,15 +62,15 @@ export class ProgressTracker {
const speed = this.speedCalculator.getSendSpeed(peerId); const speed = this.speedCalculator.getSendSpeed(peerId);
const progress = totalSize > 0 ? currentBytes / totalSize : 0; const progress = totalSize > 0 ? currentBytes / totalSize : 0;
// 持续更新网络性能(从传输速度学习)
this.stateManager.updateNetworkFromSpeed(peerId, speed);
// 触发进度回调 // 触发进度回调
this.triggerProgressCallback(peerId, progressFileId, progress, speed); this.triggerProgressCallback(peerId, progressFileId, progress, speed);
postLogToBackend( // 只在关键进度节点输出日志(每10%)
`[DEBUG] 📊 Progress updated - ${progressFileId}: ${(progress * 100).toFixed(2)}%, speed: ${speed.toFixed(2)} KB/s, bytes: ${currentBytes}/${totalSize}` if (Math.floor(progress * 10) !== Math.floor((progress - 0.1) * 10)) {
); postLogToBackend(
`[PERF] 📊 PROGRESS ${progressFileId}: ${(progress * 100).toFixed(0)}%, speed: ${speed.toFixed(1)} KB/s`
);
}
} }
/** /**
+1 -91
View File
@@ -1,5 +1,5 @@
import { PeerState, CustomFile, FolderMeta } from "@/types/webrtc"; import { PeerState, CustomFile, FolderMeta } from "@/types/webrtc";
import { TransferConfig } from "./TransferConfig"; // 简化版不再依赖TransferConfig的复杂配置
/** /**
* 🚀 网络性能监控指标接口 * 🚀 网络性能监控指标接口
@@ -129,96 +129,6 @@ export class StateManager {
public getAllFolderMeta(): Record<string, FolderMeta> { public getAllFolderMeta(): Record<string, FolderMeta> {
return { ...this.pendingFolderMeta }; return { ...this.pendingFolderMeta };
} }
// ===== 网络性能监控管理 =====
/**
* 初始化网络性能监控
*/
public initializeNetworkPerformance(peerId: string): void {
if (!this.networkPerformance.has(peerId)) {
this.networkPerformance.set(peerId, {
avgClearingRate: TransferConfig.PERFORMANCE_CONFIG.INITIAL_CLEARING_RATE,
optimalThreshold: TransferConfig.NETWORK_CONFIG.BUFFER_THRESHOLD,
avgWaitTime: TransferConfig.PERFORMANCE_CONFIG.INITIAL_WAIT_TIME,
sampleCount: 0,
});
}
}
/**
* 更新网络性能指标
*/
public updateNetworkPerformance(
peerId: string,
clearingRate: number,
waitTime: number
): void {
const perf = this.getNetworkPerformance(peerId);
if (!perf) return;
perf.sampleCount++;
// 指数移动平均,对新数据给予更高权重
const alpha = 0.3;
perf.avgClearingRate = perf.avgClearingRate * (1 - alpha) + clearingRate * alpha;
perf.avgWaitTime = perf.avgWaitTime * (1 - alpha) + waitTime * alpha;
// 调整最优阈值
this.adjustOptimalThreshold(perf);
}
/**
* 从传输速度更新网络性能
*/
public updateNetworkFromSpeed(peerId: string, currentSpeed: number): void {
if (currentSpeed <= 0) return;
const perf = this.getNetworkPerformance(peerId);
if (!perf) return;
perf.avgClearingRate = currentSpeed;
perf.sampleCount++;
// 每10次速度更新调整一次阈值
if (perf.sampleCount % 10 === 0) {
this.adjustOptimalThreshold(perf);
}
}
/**
* 获取网络性能指标
*/
public getNetworkPerformance(peerId: string): NetworkPerformanceMetrics | undefined {
return this.networkPerformance.get(peerId);
}
/**
* 获取自适应阈值
*/
public getAdaptiveThreshold(peerId: string): number {
const perf = this.networkPerformance.get(peerId);
return perf ? perf.optimalThreshold : TransferConfig.NETWORK_CONFIG.BUFFER_THRESHOLD;
}
/**
* 调整最优阈值(私有方法)
*/
private adjustOptimalThreshold(perf: NetworkPerformanceMetrics): void {
const config = TransferConfig.QUALITY_CONFIG;
const bufferThreshold = TransferConfig.NETWORK_CONFIG.BUFFER_THRESHOLD;
if (perf.avgClearingRate > config.GOOD_NETWORK_SPEED) {
// >8MB/s 好网络
perf.optimalThreshold = Math.max(bufferThreshold, 6291456); // 6MB
} else if (perf.avgClearingRate > config.AVERAGE_NETWORK_SPEED) {
// >4MB/s 平均网络
perf.optimalThreshold = bufferThreshold; // 3MB
} else {
// 差网络
perf.optimalThreshold = Math.min(bufferThreshold, 1572864); // 1.5MB
}
}
// ===== 进度跟踪相关状态 ===== // ===== 进度跟踪相关状态 =====
/** /**
+68 -36
View File
@@ -20,8 +20,11 @@ export interface NetworkChunk {
*/ */
export class StreamingFileReader { export class StreamingFileReader {
// 配置参数 // 配置参数
private readonly BATCH_SIZE = TransferConfig.FILE_CONFIG.CHUNK_SIZE * TransferConfig.FILE_CONFIG.BATCH_SIZE; // 32MB批次 private readonly BATCH_SIZE =
private readonly NETWORK_CHUNK_SIZE = TransferConfig.FILE_CONFIG.NETWORK_CHUNK_SIZE; // 64KB网络块 TransferConfig.FILE_CONFIG.CHUNK_SIZE *
TransferConfig.FILE_CONFIG.BATCH_SIZE; // 32MB批次
private readonly NETWORK_CHUNK_SIZE =
TransferConfig.FILE_CONFIG.NETWORK_CHUNK_SIZE; // 64KB网络块
private readonly CHUNKS_PER_BATCH = this.BATCH_SIZE / this.NETWORK_CHUNK_SIZE; // 512块 private readonly CHUNKS_PER_BATCH = this.BATCH_SIZE / this.NETWORK_CHUNK_SIZE; // 512块
// 文件状态 // 文件状态
@@ -44,7 +47,7 @@ export class StreamingFileReader {
this.totalFileSize = file.size; this.totalFileSize = file.size;
this.totalFileOffset = startOffset; this.totalFileOffset = startOffset;
this.fileReader = new FileReader(); this.fileReader = new FileReader();
postLogToBackend( postLogToBackend(
`[DEBUG] 📖 StreamingFileReader created - file: ${file.name}, size: ${this.totalFileSize}, startOffset: ${startOffset}` `[DEBUG] 📖 StreamingFileReader created - file: ${file.name}, size: ${this.totalFileSize}, startOffset: ${startOffset}`
); );
@@ -66,7 +69,7 @@ export class StreamingFileReader {
chunkIndex: this.calculateGlobalChunkIndex(), chunkIndex: this.calculateGlobalChunkIndex(),
totalChunks: this.calculateTotalNetworkChunks(), totalChunks: this.calculateTotalNetworkChunks(),
fileOffset: this.totalFileOffset, fileOffset: this.totalFileOffset,
isLastChunk: true isLastChunk: true,
}; };
} }
@@ -78,16 +81,21 @@ export class StreamingFileReader {
// 4. 更新状态 // 4. 更新状态
this.updateChunkState(networkChunk); this.updateChunkState(networkChunk);
postLogToBackend( // 只在关键节点输出日志
`[DEBUG] ✂️ NETWORK_CHUNK extracted #${globalChunkIndex}/${this.calculateTotalNetworkChunks()} - size: ${networkChunk.byteLength}, isLast: ${isLast}` if (globalChunkIndex % 100 === 0 || isLast) {
); postLogToBackend(
`[PERF] ✂️ CHUNK progress #${globalChunkIndex}/${this.calculateTotalNetworkChunks()} - size: ${
networkChunk.byteLength
}, isLast: ${isLast}`
);
}
return { return {
chunk: networkChunk, chunk: networkChunk,
chunkIndex: globalChunkIndex, chunkIndex: globalChunkIndex,
totalChunks: this.calculateTotalNetworkChunks(), totalChunks: this.calculateTotalNetworkChunks(),
fileOffset: this.totalFileOffset - networkChunk.byteLength, fileOffset: this.totalFileOffset - networkChunk.byteLength,
isLastChunk: isLast isLastChunk: isLast,
}; };
} }
@@ -96,9 +104,9 @@ export class StreamingFileReader {
*/ */
private needsNewBatch(): boolean { private needsNewBatch(): boolean {
return ( return (
this.currentBatch === null || // 还未加载任何批次 this.currentBatch === null || // 还未加载任何批次
this.currentChunkIndexInBatch >= this.CHUNKS_PER_BATCH || // 当前批次用完 this.currentChunkIndexInBatch >= this.CHUNKS_PER_BATCH || // 当前批次用完
this.isCurrentBatchEmpty() // 当前批次已无数据 this.isCurrentBatchEmpty() // 当前批次已无数据
); );
} }
@@ -107,7 +115,7 @@ export class StreamingFileReader {
*/ */
private isCurrentBatchEmpty(): boolean { private isCurrentBatchEmpty(): boolean {
if (!this.currentBatch) return true; if (!this.currentBatch) return true;
const usedBytes = this.currentChunkIndexInBatch * this.NETWORK_CHUNK_SIZE; const usedBytes = this.currentChunkIndexInBatch * this.NETWORK_CHUNK_SIZE;
return usedBytes >= this.currentBatch.byteLength; return usedBytes >= this.currentBatch.byteLength;
} }
@@ -119,12 +127,13 @@ export class StreamingFileReader {
if (this.isReading) { if (this.isReading) {
// 防止并发读取 // 防止并发读取
while (this.isReading) { while (this.isReading) {
await new Promise(resolve => setTimeout(resolve, 10)); await new Promise((resolve) => setTimeout(resolve, 10));
} }
return; return;
} }
this.isReading = true; this.isReading = true;
const startTime = performance.now();
try { try {
// 1. 清理旧批次内存 // 1. 清理旧批次内存
@@ -140,27 +149,39 @@ export class StreamingFileReader {
} }
// 3. 执行大块文件读取 // 3. 执行大块文件读取
const sliceStartTime = performance.now();
const fileSlice = this.file.slice( const fileSlice = this.file.slice(
this.totalFileOffset, this.totalFileOffset,
this.totalFileOffset + batchSize this.totalFileOffset + batchSize
); );
const sliceTime = performance.now() - sliceStartTime;
postLogToBackend(
`[DEBUG] 📖 BATCH_READ start - offset: ${this.totalFileOffset}, size: ${batchSize}, remaining: ${remainingFileSize}`
);
// 4. 异步读取文件数据 // 4. 异步读取文件数据
const readStartTime = performance.now();
this.currentBatch = await this.readFileSlice(fileSlice); this.currentBatch = await this.readFileSlice(fileSlice);
const readTime = performance.now() - readStartTime;
this.currentBatchStartOffset = this.totalFileOffset; this.currentBatchStartOffset = this.totalFileOffset;
this.currentChunkIndexInBatch = 0; this.currentChunkIndexInBatch = 0;
const expectedNetworkChunks = Math.ceil(this.currentBatch.byteLength / this.NETWORK_CHUNK_SIZE); const totalTime = performance.now() - startTime;
postLogToBackend( const speedMBps = batchSize / 1024 / 1024 / (totalTime / 1000);
`[DEBUG] ✅ BATCH_LOADED - ${this.currentBatch.byteLength} bytes, networkChunks: ${expectedNetworkChunks}`
);
postLogToBackend(
`[PERF] 📖 BATCH_READ - size: ${(batchSize / 1024 / 1024).toFixed(
1
)}MB, total: ${totalTime.toFixed(1)}ms, slice: ${sliceTime.toFixed(
1
)}ms, read: ${readTime.toFixed(1)}ms, speed: ${speedMBps.toFixed(
1
)}MB/s`
);
} catch (error) { } catch (error) {
postLogToBackend(`[DEBUG] ❌ BATCH_READ failed: ${error}`); postLogToBackend(
`[PERF] ❌ BATCH_READ failed after ${(
performance.now() - startTime
).toFixed(1)}ms: ${error}`
);
throw new Error(`Failed to load file batch: ${error}`); throw new Error(`Failed to load file batch: ${error}`);
} finally { } finally {
this.isReading = false; this.isReading = false;
@@ -180,11 +201,17 @@ export class StreamingFileReader {
reject(new Error("FileReader result is null")); reject(new Error("FileReader result is null"));
} }
}; };
this.fileReader.onerror = () => { this.fileReader.onerror = () => {
reject(new Error(`File reading failed: ${this.fileReader.error?.message || 'Unknown error'}`)); reject(
new Error(
`File reading failed: ${
this.fileReader.error?.message || "Unknown error"
}`
)
);
}; };
this.fileReader.readAsArrayBuffer(fileSlice); this.fileReader.readAsArrayBuffer(fileSlice);
}); });
} }
@@ -197,7 +224,8 @@ export class StreamingFileReader {
throw new Error("No current batch available for slicing"); throw new Error("No current batch available for slicing");
} }
const chunkStartInBatch = this.currentChunkIndexInBatch * this.NETWORK_CHUNK_SIZE; const chunkStartInBatch =
this.currentChunkIndexInBatch * this.NETWORK_CHUNK_SIZE;
const remainingInBatch = this.currentBatch.byteLength - chunkStartInBatch; const remainingInBatch = this.currentBatch.byteLength - chunkStartInBatch;
const chunkSize = Math.min(this.NETWORK_CHUNK_SIZE, remainingInBatch); const chunkSize = Math.min(this.NETWORK_CHUNK_SIZE, remainingInBatch);
@@ -210,10 +238,7 @@ export class StreamingFileReader {
chunkStartInBatch + chunkSize chunkStartInBatch + chunkSize
); );
postLogToBackend( // 删除频繁的slice日志,只在需要时输出
`[DEBUG] ✂️ SLICE_CHUNK batch[${this.currentChunkIndexInBatch}/${Math.ceil(this.currentBatch.byteLength / this.NETWORK_CHUNK_SIZE)}] - size: ${chunkSize}, remaining: ${remainingInBatch - chunkSize}`
);
return networkChunk; return networkChunk;
} }
@@ -221,7 +246,9 @@ export class StreamingFileReader {
* 📊 计算全局网络块索引 * 📊 计算全局网络块索引
*/ */
private calculateGlobalChunkIndex(): number { private calculateGlobalChunkIndex(): number {
const batchesBefore = Math.floor(this.currentBatchStartOffset / this.BATCH_SIZE); const batchesBefore = Math.floor(
this.currentBatchStartOffset / this.BATCH_SIZE
);
const chunksInPreviousBatches = batchesBefore * this.CHUNKS_PER_BATCH; const chunksInPreviousBatches = batchesBefore * this.CHUNKS_PER_BATCH;
return chunksInPreviousBatches + this.currentChunkIndexInBatch; return chunksInPreviousBatches + this.currentChunkIndexInBatch;
} }
@@ -270,8 +297,11 @@ export class StreamingFileReader {
totalChunks: number; totalChunks: number;
}; };
} { } {
const progressPercent = this.totalFileSize > 0 ? (this.totalFileOffset / this.totalFileSize) * 100 : 0; const progressPercent =
this.totalFileSize > 0
? (this.totalFileOffset / this.totalFileSize) * 100
: 0;
const result = { const result = {
readBytes: this.totalFileOffset, readBytes: this.totalFileOffset,
totalBytes: this.totalFileSize, totalBytes: this.totalFileSize,
@@ -283,7 +313,9 @@ export class StreamingFileReader {
batchStartOffset: this.currentBatchStartOffset, batchStartOffset: this.currentBatchStartOffset,
batchSize: this.currentBatch.byteLength, batchSize: this.currentBatch.byteLength,
chunkIndex: this.currentChunkIndexInBatch, chunkIndex: this.currentChunkIndexInBatch,
totalChunks: Math.ceil(this.currentBatch.byteLength / this.NETWORK_CHUNK_SIZE), totalChunks: Math.ceil(
this.currentBatch.byteLength / this.NETWORK_CHUNK_SIZE
),
}; };
} }
@@ -300,7 +332,7 @@ export class StreamingFileReader {
this.currentBatch = null; this.currentBatch = null;
this.currentBatchStartOffset = 0; this.currentBatchStartOffset = 0;
this.currentChunkIndexInBatch = 0; this.currentChunkIndexInBatch = 0;
postLogToBackend( postLogToBackend(
`[DEBUG] 🔄 StreamingFileReader reset - startOffset: ${startOffset}` `[DEBUG] 🔄 StreamingFileReader reset - startOffset: ${startOffset}`
); );
-81
View File
@@ -9,85 +9,4 @@ export class TransferConfig {
BATCH_SIZE: 8, // 8个chunk批处理 - 32MB批处理提升性能 BATCH_SIZE: 8, // 8个chunk批处理 - 32MB批处理提升性能
NETWORK_CHUNK_SIZE: 65536, // 64KB - WebRTC安全发送大小,修复sendData failed NETWORK_CHUNK_SIZE: 65536, // 64KB - WebRTC安全发送大小,修复sendData failed
} as const; } as const;
// 网络传输相关配置
static readonly NETWORK_CONFIG = {
BUFFER_THRESHOLD: 3145728, // 3MB - 背压阈值
BACKPRESSURE_TIMEOUT: 2000, // 2秒超时 - 为大chunk处理预留更多时间
} as const;
// 性能调优相关配置
static readonly PERFORMANCE_CONFIG = {
MIN_THRESHOLD: 262144, // 256KB - 最小阈值
MAX_THRESHOLD: 16777216, // 16MB - 最大阈值
ADJUSTMENT_FACTOR: 0.1, // 调整系数
ADAPTIVE_SAMPLES: 5, // 自适应采样数
INITIAL_CLEARING_RATE: 5000, // 5MB/s 初始预估
INITIAL_WAIT_TIME: 50, // 50ms 初始预估
} as const;
// 智能发送控制策略配置
static readonly SEND_STRATEGY_CONFIG = {
AGGRESSIVE_THRESHOLD: 0.3, // 积极模式:30%以下
NORMAL_THRESHOLD: 0.6, // 正常模式:60%以下
CAUTIOUS_THRESHOLD: 0.9, // 谨慎模式:90%以下
POLLING_INTERVAL: 5, // 轮询间隔(ms)
MAX_WAIT_TIME: 3000, // 最大等待时间(ms)
} as const;
// 网络质量评估配置
static readonly QUALITY_CONFIG = {
GOOD_NETWORK_SPEED: 8000, // 8MB/s 以上为好网络
AVERAGE_NETWORK_SPEED: 4000, // 4MB/s 以上为平均网络
GOOD_NETWORK_THRESHOLDS: {
aggressive: 0.4, // 好网络:40%以下积极发送
normal: 0.7, // 好网络:70%以下正常发送
cautious: 0.9, // 好网络:90%以下谨慎发送
},
POOR_NETWORK_THRESHOLDS: {
aggressive: 0.2, // 差网络:20%以下积极发送
normal: 0.5, // 差网络:50%以下正常发送
cautious: 0.8, // 差网络:80%以上等待
},
} as const;
/**
* 获取适应性阈值计算参数
*/
static getAdaptiveThresholds(networkSpeed: number) {
if (networkSpeed > this.QUALITY_CONFIG.GOOD_NETWORK_SPEED) {
// 好网络:使用较高阈值
return {
threshold: Math.max(this.NETWORK_CONFIG.BUFFER_THRESHOLD, 6291456), // 6MB
strategy: this.QUALITY_CONFIG.GOOD_NETWORK_THRESHOLDS,
};
} else if (networkSpeed > this.QUALITY_CONFIG.AVERAGE_NETWORK_SPEED) {
// 平均网络:使用默认阈值
return {
threshold: this.NETWORK_CONFIG.BUFFER_THRESHOLD, // 3MB
strategy: {
aggressive: this.SEND_STRATEGY_CONFIG.AGGRESSIVE_THRESHOLD,
normal: this.SEND_STRATEGY_CONFIG.NORMAL_THRESHOLD,
cautious: this.SEND_STRATEGY_CONFIG.CAUTIOUS_THRESHOLD,
},
};
} else {
// 差网络:使用较低阈值
return {
threshold: Math.min(this.NETWORK_CONFIG.BUFFER_THRESHOLD, 1572864), // 1.5MB
strategy: this.QUALITY_CONFIG.POOR_NETWORK_THRESHOLDS,
};
}
}
/**
* 验证配置的合理性
*/
static validateConfig(): boolean {
return (
this.FILE_CONFIG.NETWORK_CHUNK_SIZE < this.FILE_CONFIG.CHUNK_SIZE &&
this.NETWORK_CONFIG.BUFFER_THRESHOLD > this.FILE_CONFIG.NETWORK_CHUNK_SIZE &&
this.PERFORMANCE_CONFIG.MIN_THRESHOLD < this.PERFORMANCE_CONFIG.MAX_THRESHOLD
);
}
} }
+1 -14
View File
@@ -33,20 +33,7 @@ export { FileTransferOrchestrator } from "./FileTransferOrchestrator";
*/ */
import WebRTC_Initiator from "../webrtc_Initiator"; import WebRTC_Initiator from "../webrtc_Initiator";
import { FileTransferOrchestrator } from "./FileTransferOrchestrator"; import { FileTransferOrchestrator } from "./FileTransferOrchestrator";
import { TransferConfig } from "./TransferConfig";
export function createFileTransferService(webrtcConnection: WebRTC_Initiator): FileTransferOrchestrator { export function createFileTransferService(webrtcConnection: WebRTC_Initiator): FileTransferOrchestrator {
return new FileTransferOrchestrator(webrtcConnection); return new FileTransferOrchestrator(webrtcConnection);
} }
/**
* 📋 版本信息
*/
export const TRANSFER_MODULE_VERSION = "1.0.0";
/**
* 🔍 模块验证 - 确保所有配置都是有效的
*/
export function validateTransferModule(): boolean {
return TransferConfig.validateConfig();
}