Using a simple backpressure mechanism
This commit is contained in:
@@ -205,31 +205,33 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
): Promise<void> {
|
||||
const fileId = generateFileId(file);
|
||||
const peerState = this.stateManager.getPeerState(peerId);
|
||||
const transferStartTime = performance.now();
|
||||
|
||||
// 1. 初始化流式文件读取器
|
||||
const streamReader = new StreamingFileReader(file, peerState.readOffset || 0);
|
||||
|
||||
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 {
|
||||
let totalBytesSent = 0;
|
||||
let networkChunkIndex = 0;
|
||||
let totalReadTime = 0;
|
||||
let totalSendTime = 0;
|
||||
let totalProgressTime = 0;
|
||||
let lastProgressTime = performance.now();
|
||||
|
||||
// 2. 流式处理:逐个获取64KB网络块并发送
|
||||
while (peerState.isSending) {
|
||||
// 获取下一个网络块
|
||||
const readStartTime = performance.now();
|
||||
const chunkInfo = await streamReader.getNextNetworkChunk();
|
||||
const readTime = performance.now() - readStartTime;
|
||||
totalReadTime += readTime;
|
||||
|
||||
// 检查是否已完成
|
||||
if (chunkInfo.chunk === null) {
|
||||
postLogToBackend(
|
||||
`[DEBUG] 🏁 STREAMING_SEND completed - totalChunks: ${networkChunkIndex}, totalBytes: ${totalBytesSent}`
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -245,6 +247,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
|
||||
// 发送带嵌入元数据的网络块
|
||||
let sendSuccessful = false;
|
||||
const sendStartTime = performance.now();
|
||||
try {
|
||||
sendSuccessful = await this.networkTransmitter.sendEmbeddedChunk(
|
||||
chunkInfo.chunk,
|
||||
@@ -254,16 +257,13 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
|
||||
if (sendSuccessful) {
|
||||
totalBytesSent += chunkInfo.chunk.byteLength;
|
||||
postLogToBackend(
|
||||
`[DEBUG] ✓ STREAMING_CHUNK sent #${chunkInfo.chunkIndex}/${chunkInfo.totalChunks} - size: ${chunkInfo.chunk.byteLength}, isLast: ${chunkInfo.isLastChunk}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ❌ STREAMING_CHUNK failed #${chunkInfo.chunkIndex}: ${error}`
|
||||
);
|
||||
this.log("warn", `Chunk send failed #${chunkInfo.chunkIndex}: ${error}`);
|
||||
sendSuccessful = false;
|
||||
}
|
||||
const sendTime = performance.now() - sendStartTime;
|
||||
totalSendTime += sendTime;
|
||||
|
||||
// 更新状态和进度
|
||||
if (sendSuccessful) {
|
||||
@@ -271,6 +271,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
readOffset: chunkInfo.fileOffset + chunkInfo.chunk.byteLength
|
||||
});
|
||||
|
||||
const progressStartTime = performance.now();
|
||||
await this.progressTracker.updateFileProgress(
|
||||
chunkInfo.chunk.byteLength,
|
||||
fileId,
|
||||
@@ -278,40 +279,57 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate {
|
||||
peerId,
|
||||
true
|
||||
);
|
||||
} else {
|
||||
this.log("warn", `Send failed, continuing with next chunk...`, {
|
||||
chunkIndex: chunkInfo.chunkIndex,
|
||||
fileId,
|
||||
peerId
|
||||
});
|
||||
const progressTime = performance.now() - progressStartTime;
|
||||
totalProgressTime += progressTime;
|
||||
}
|
||||
|
||||
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) {
|
||||
postLogToBackend(
|
||||
`[DEBUG] 🏁 Last chunk sent, waiting for receiver confirmation...`
|
||||
);
|
||||
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(
|
||||
`[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) {
|
||||
const errorMessage = `Streaming send error: ${error.message}`;
|
||||
const totalTime = performance.now() - transferStartTime;
|
||||
postLogToBackend(
|
||||
`[DEBUG] ❌ STREAMING_ERROR: ${errorMessage}`
|
||||
`[PERF] ❌ TRANSFER_ERROR after ${(totalTime/1000).toFixed(1)}s: ${errorMessage}`
|
||||
);
|
||||
this.fireError(errorMessage, { fileId, peerId, offset: peerState.readOffset });
|
||||
throw error;
|
||||
} finally {
|
||||
// 清理资源
|
||||
streamReader.cleanup();
|
||||
postLogToBackend(`[DEBUG] 🧹 StreamingFileReader cleaned up`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ export class MessageHandler {
|
||||
* 🎯 处理接收到的信令消息
|
||||
*/
|
||||
handleSignalingMessage(message: WebRTCMessage, peerId: string): void {
|
||||
postLogToBackend(`[DEBUG] 📨 Message received - type: ${message.type}, peerId: ${peerId}`);
|
||||
// 删除频繁的消息接收日志
|
||||
|
||||
switch (message.type) {
|
||||
case "fileRequest":
|
||||
@@ -83,7 +83,7 @@ export class MessageHandler {
|
||||
peerId: string
|
||||
): void {
|
||||
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%进度(只有非文件夹情况)
|
||||
if (!peerState.currentFolderName) {
|
||||
postLogToBackend(
|
||||
`[DEBUG] 🎯 Setting single file progress to 100% - ${message.fileId}`
|
||||
);
|
||||
// 删除频繁的进度日志
|
||||
peerState.progressCallback?.(message.fileId, 1, 0);
|
||||
} 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}`, {
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import { EmbeddedChunkMeta } from "@/types/webrtc";
|
||||
import { StateManager } from "./StateManager";
|
||||
import { TransferConfig } from "./TransferConfig";
|
||||
import WebRTC_Initiator from "../webrtc_Initiator";
|
||||
import { postLogToBackend } from "@/app/config/api";
|
||||
|
||||
/**
|
||||
* 🚀 发送策略枚举
|
||||
*/
|
||||
type SendStrategy = "AGGRESSIVE" | "NORMAL" | "CAUTIOUS" | "WAIT";
|
||||
|
||||
/**
|
||||
* 🚀 网络传输器
|
||||
* 负责所有WebRTC数据传输、背压控制、自适应性能调整
|
||||
* 🚀 网络传输器 - 简化版
|
||||
* 使用WebRTC原生bufferedAmountLowThreshold进行背压控制
|
||||
*/
|
||||
export class NetworkTransmitter {
|
||||
constructor(
|
||||
@@ -27,21 +21,48 @@ export class NetworkTransmitter {
|
||||
metadata: EmbeddedChunkMeta,
|
||||
peerId: string
|
||||
): Promise<boolean> {
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
// 1. 构建融合数据包
|
||||
const embeddedPacket = this.createEmbeddedChunkPacket(chunkData, metadata);
|
||||
const createStartTime = performance.now();
|
||||
const embeddedPacket = this.createEmbeddedChunkPacket(
|
||||
chunkData,
|
||||
metadata
|
||||
);
|
||||
const createTime = performance.now() - createStartTime;
|
||||
|
||||
// 2. 发送完整的融合数据包(不可分片)
|
||||
const sendStartTime = performance.now();
|
||||
await this.sendSingleData(embeddedPacket, peerId);
|
||||
const sendTime = performance.now() - sendStartTime;
|
||||
|
||||
const totalTime = performance.now() - startTime;
|
||||
|
||||
// 只在关键节点或耗时较长时输出日志
|
||||
if (
|
||||
metadata.chunkIndex % 100 === 0 ||
|
||||
metadata.isLastChunk ||
|
||||
totalTime > 50
|
||||
) {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ✓ EMBEDDED chunk #${metadata.chunkIndex}/${metadata.totalChunks} sent - size: ${chunkData.byteLength}, packet: ${embeddedPacket.byteLength} bytes, isLast: ${metadata.isLastChunk}`
|
||||
`[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;
|
||||
} catch (error) {
|
||||
const totalTime = performance.now() - startTime;
|
||||
postLogToBackend(
|
||||
`[DEBUG] ❌ EMBEDDED chunk #${metadata.chunkIndex} send failed: ${error}`
|
||||
`[PERF] ❌ CHUNK #${
|
||||
metadata.chunkIndex
|
||||
} FAILED after ${totalTime.toFixed(1)}ms: ${error}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -72,10 +93,6 @@ export class NetworkTransmitter {
|
||||
finalPacket.set(metaBytes, 4);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -91,26 +108,73 @@ export class NetworkTransmitter {
|
||||
throw new Error("Data channel not found");
|
||||
}
|
||||
|
||||
// 调试信息
|
||||
const dataType = typeof data === "string" ? "string" : data instanceof ArrayBuffer ? "ArrayBuffer" : "unknown";
|
||||
const dataSize = typeof data === "string" ? data.length : data instanceof ArrayBuffer ? data.byteLength : 0;
|
||||
|
||||
// 智能背压控制
|
||||
await this.smartBufferControl(dataChannel, peerId);
|
||||
// 简化背压控制
|
||||
await this.simpleBufferControl(dataChannel, peerId);
|
||||
|
||||
// 直接发送,不分片
|
||||
const sendResult = this.webrtcConnection.sendData(data, peerId);
|
||||
|
||||
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}`;
|
||||
postLogToBackend(`[DEBUG] ❌ ${errorMessage}`);
|
||||
postLogToBackend(`[PERF] ❌ ${errorMessage}`);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🎯 原生背压控制 - 使用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(
|
||||
`[DEBUG] 📤 Data sent successfully - type: ${dataType}, size: ${dataSize}`
|
||||
`[PERF] 🚀 BACKPRESSURE - wait: ${waitTime.toFixed(
|
||||
1
|
||||
)}ms, buffered: ${initialBuffered} -> ${dataChannel.bufferedAmount}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🚀 发送带背压控制的数据
|
||||
@@ -133,7 +197,7 @@ export class NetworkTransmitter {
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = `sendWithBackpressure failed: ${error}`;
|
||||
postLogToBackend(`[DEBUG] ${errorMessage}`);
|
||||
postLogToBackend(`[PERF] ❌ ${errorMessage}`);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
}
|
||||
@@ -145,7 +209,7 @@ export class NetworkTransmitter {
|
||||
data: ArrayBuffer,
|
||||
peerId: string
|
||||
): Promise<void> {
|
||||
const networkChunkSize = TransferConfig.FILE_CONFIG.NETWORK_CHUNK_SIZE;
|
||||
const networkChunkSize = 65536; // 64KB
|
||||
const totalSize = data.byteLength;
|
||||
|
||||
// 如果数据小于64KB,直接发送
|
||||
@@ -164,146 +228,23 @@ export class NetworkTransmitter {
|
||||
|
||||
// 发送分片
|
||||
await this.sendSingleData(chunk, peerId);
|
||||
postLogToBackend(
|
||||
`[DEBUG] 📦 Fragment sent #${fragmentIndex} - size: ${chunkSize}`
|
||||
);
|
||||
|
||||
offset += chunkSize;
|
||||
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) {
|
||||
const networkPerf = this.stateManager.getNetworkPerformance(peerId);
|
||||
const dataChannel = this.webrtcConnection.dataChannels.get(peerId);
|
||||
|
||||
return {
|
||||
peerId,
|
||||
networkPerformance: networkPerf || null,
|
||||
currentBufferedAmount: dataChannel?.bufferedAmount || 0,
|
||||
adaptiveThreshold: this.stateManager.getAdaptiveThreshold(peerId),
|
||||
channelState: dataChannel?.readyState || 'unknown',
|
||||
bufferedAmountLowThreshold: dataChannel?.bufferedAmountLowThreshold || 0,
|
||||
channelState: dataChannel?.readyState || "unknown",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -311,8 +252,6 @@ export class NetworkTransmitter {
|
||||
* 🧹 清理资源
|
||||
*/
|
||||
public cleanup(): void {
|
||||
// NetworkTransmitter本身没有需要清理的资源
|
||||
// 实际的清理工作由StateManager和WebRTC_Initiator处理
|
||||
postLogToBackend("[DEBUG] 🧹 NetworkTransmitter cleaned up");
|
||||
postLogToBackend("[PERF] 🧹 NetworkTransmitter cleaned up");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,6 @@ export class ProgressTracker {
|
||||
|
||||
// 重要修复:只有成功发送的数据才更新统计
|
||||
if (!wasActuallySent) {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ⚠️ Data send failed, not updating progress - fileId: ${fileId}, size: ${byteLength}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -57,9 +54,7 @@ export class ProgressTracker {
|
||||
// 这对于断点续传更加健壮和正确
|
||||
currentBytes = this.stateManager.getFolderBytesSent(peerId, folderName);
|
||||
|
||||
postLogToBackend(
|
||||
`[DEBUG] 📁 Folder progress update - folder: ${folderName}, file: ${fileId}, currentBytes: ${currentBytes}, totalSize: ${totalSize}`
|
||||
);
|
||||
// 删除频繁的文件夹进度日志
|
||||
}
|
||||
|
||||
// 更新速度计算器
|
||||
@@ -67,16 +62,16 @@ export class ProgressTracker {
|
||||
const speed = this.speedCalculator.getSendSpeed(peerId);
|
||||
const progress = totalSize > 0 ? currentBytes / totalSize : 0;
|
||||
|
||||
// 持续更新网络性能(从传输速度学习)
|
||||
this.stateManager.updateNetworkFromSpeed(peerId, speed);
|
||||
|
||||
// 触发进度回调
|
||||
this.triggerProgressCallback(peerId, progressFileId, progress, speed);
|
||||
|
||||
// 只在关键进度节点输出日志(每10%)
|
||||
if (Math.floor(progress * 10) !== Math.floor((progress - 0.1) * 10)) {
|
||||
postLogToBackend(
|
||||
`[DEBUG] 📊 Progress updated - ${progressFileId}: ${(progress * 100).toFixed(2)}%, speed: ${speed.toFixed(2)} KB/s, bytes: ${currentBytes}/${totalSize}`
|
||||
`[PERF] 📊 PROGRESS ${progressFileId}: ${(progress * 100).toFixed(0)}%, speed: ${speed.toFixed(1)} KB/s`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🎯 更新文件夹传输进度
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PeerState, CustomFile, FolderMeta } from "@/types/webrtc";
|
||||
import { TransferConfig } from "./TransferConfig";
|
||||
// 简化版不再依赖TransferConfig的复杂配置
|
||||
|
||||
/**
|
||||
* 🚀 网络性能监控指标接口
|
||||
@@ -129,96 +129,6 @@ export class StateManager {
|
||||
public getAllFolderMeta(): Record<string, FolderMeta> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 进度跟踪相关状态 =====
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,8 +20,11 @@ export interface NetworkChunk {
|
||||
*/
|
||||
export class StreamingFileReader {
|
||||
// 配置参数
|
||||
private readonly BATCH_SIZE = 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 BATCH_SIZE =
|
||||
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块
|
||||
|
||||
// 文件状态
|
||||
@@ -66,7 +69,7 @@ export class StreamingFileReader {
|
||||
chunkIndex: this.calculateGlobalChunkIndex(),
|
||||
totalChunks: this.calculateTotalNetworkChunks(),
|
||||
fileOffset: this.totalFileOffset,
|
||||
isLastChunk: true
|
||||
isLastChunk: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -78,16 +81,21 @@ export class StreamingFileReader {
|
||||
// 4. 更新状态
|
||||
this.updateChunkState(networkChunk);
|
||||
|
||||
// 只在关键节点输出日志
|
||||
if (globalChunkIndex % 100 === 0 || isLast) {
|
||||
postLogToBackend(
|
||||
`[DEBUG] ✂️ NETWORK_CHUNK extracted #${globalChunkIndex}/${this.calculateTotalNetworkChunks()} - size: ${networkChunk.byteLength}, isLast: ${isLast}`
|
||||
`[PERF] ✂️ CHUNK progress #${globalChunkIndex}/${this.calculateTotalNetworkChunks()} - size: ${
|
||||
networkChunk.byteLength
|
||||
}, isLast: ${isLast}`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
chunk: networkChunk,
|
||||
chunkIndex: globalChunkIndex,
|
||||
totalChunks: this.calculateTotalNetworkChunks(),
|
||||
fileOffset: this.totalFileOffset - networkChunk.byteLength,
|
||||
isLastChunk: isLast
|
||||
isLastChunk: isLast,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -119,12 +127,13 @@ export class StreamingFileReader {
|
||||
if (this.isReading) {
|
||||
// 防止并发读取
|
||||
while (this.isReading) {
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.isReading = true;
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
// 1. 清理旧批次内存
|
||||
@@ -140,27 +149,39 @@ export class StreamingFileReader {
|
||||
}
|
||||
|
||||
// 3. 执行大块文件读取
|
||||
const sliceStartTime = performance.now();
|
||||
const fileSlice = this.file.slice(
|
||||
this.totalFileOffset,
|
||||
this.totalFileOffset + batchSize
|
||||
);
|
||||
|
||||
postLogToBackend(
|
||||
`[DEBUG] 📖 BATCH_READ start - offset: ${this.totalFileOffset}, size: ${batchSize}, remaining: ${remainingFileSize}`
|
||||
);
|
||||
const sliceTime = performance.now() - sliceStartTime;
|
||||
|
||||
// 4. 异步读取文件数据
|
||||
const readStartTime = performance.now();
|
||||
this.currentBatch = await this.readFileSlice(fileSlice);
|
||||
const readTime = performance.now() - readStartTime;
|
||||
|
||||
this.currentBatchStartOffset = this.totalFileOffset;
|
||||
this.currentChunkIndexInBatch = 0;
|
||||
|
||||
const expectedNetworkChunks = Math.ceil(this.currentBatch.byteLength / this.NETWORK_CHUNK_SIZE);
|
||||
postLogToBackend(
|
||||
`[DEBUG] ✅ BATCH_LOADED - ${this.currentBatch.byteLength} bytes, networkChunks: ${expectedNetworkChunks}`
|
||||
);
|
||||
const totalTime = performance.now() - startTime;
|
||||
const speedMBps = batchSize / 1024 / 1024 / (totalTime / 1000);
|
||||
|
||||
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) {
|
||||
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}`);
|
||||
} finally {
|
||||
this.isReading = false;
|
||||
@@ -182,7 +203,13 @@ export class StreamingFileReader {
|
||||
};
|
||||
|
||||
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);
|
||||
@@ -197,7 +224,8 @@ export class StreamingFileReader {
|
||||
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 chunkSize = Math.min(this.NETWORK_CHUNK_SIZE, remainingInBatch);
|
||||
|
||||
@@ -210,10 +238,7 @@ export class StreamingFileReader {
|
||||
chunkStartInBatch + chunkSize
|
||||
);
|
||||
|
||||
postLogToBackend(
|
||||
`[DEBUG] ✂️ SLICE_CHUNK batch[${this.currentChunkIndexInBatch}/${Math.ceil(this.currentBatch.byteLength / this.NETWORK_CHUNK_SIZE)}] - size: ${chunkSize}, remaining: ${remainingInBatch - chunkSize}`
|
||||
);
|
||||
|
||||
// 删除频繁的slice日志,只在需要时输出
|
||||
return networkChunk;
|
||||
}
|
||||
|
||||
@@ -221,7 +246,9 @@ export class StreamingFileReader {
|
||||
* 📊 计算全局网络块索引
|
||||
*/
|
||||
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;
|
||||
return chunksInPreviousBatches + this.currentChunkIndexInBatch;
|
||||
}
|
||||
@@ -270,7 +297,10 @@ export class StreamingFileReader {
|
||||
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 = {
|
||||
readBytes: this.totalFileOffset,
|
||||
@@ -283,7 +313,9 @@ export class StreamingFileReader {
|
||||
batchStartOffset: this.currentBatchStartOffset,
|
||||
batchSize: this.currentBatch.byteLength,
|
||||
chunkIndex: this.currentChunkIndexInBatch,
|
||||
totalChunks: Math.ceil(this.currentBatch.byteLength / this.NETWORK_CHUNK_SIZE),
|
||||
totalChunks: Math.ceil(
|
||||
this.currentBatch.byteLength / this.NETWORK_CHUNK_SIZE
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -9,85 +9,4 @@ export class TransferConfig {
|
||||
BATCH_SIZE: 8, // 8个chunk批处理 - 32MB批处理提升性能
|
||||
NETWORK_CHUNK_SIZE: 65536, // 64KB - WebRTC安全发送大小,修复sendData failed
|
||||
} 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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,20 +33,7 @@ export { FileTransferOrchestrator } from "./FileTransferOrchestrator";
|
||||
*/
|
||||
import WebRTC_Initiator from "../webrtc_Initiator";
|
||||
import { FileTransferOrchestrator } from "./FileTransferOrchestrator";
|
||||
import { TransferConfig } from "./TransferConfig";
|
||||
|
||||
export function createFileTransferService(webrtcConnection: WebRTC_Initiator): FileTransferOrchestrator {
|
||||
return new FileTransferOrchestrator(webrtcConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
* 📋 版本信息
|
||||
*/
|
||||
export const TRANSFER_MODULE_VERSION = "1.0.0";
|
||||
|
||||
/**
|
||||
* 🔍 模块验证 - 确保所有配置都是有效的
|
||||
*/
|
||||
export function validateTransferModule(): boolean {
|
||||
return TransferConfig.validateConfig();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user