Using a simple backpressure mechanism
This commit is contained in:
@@ -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`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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}`, {
|
||||||
|
|||||||
@@ -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");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 进度跟踪相关状态 =====
|
// ===== 进度跟踪相关状态 =====
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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}`
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user