fix:Temporarily optimized the speed issue on the mobile
This commit is contained in:
+246
-13
@@ -5,6 +5,7 @@
|
||||
// The receiving display end distinguishes between single files and folders.
|
||||
import { generateFileId } from "@/lib/fileUtils";
|
||||
import { SpeedCalculator } from "@/lib/speedCalculator";
|
||||
import { postLogToBackend } from "@/app/config/api";
|
||||
import WebRTC_Initiator from "./webrtc_Initiator";
|
||||
import {
|
||||
CustomFile,
|
||||
@@ -26,13 +27,14 @@ class FileSender {
|
||||
private pendingFolerMeta: Record<string, FolderMeta>;
|
||||
private speedCalculator: SpeedCalculator;
|
||||
|
||||
// 统一优化配置 - 适用于所有设备的最佳参数
|
||||
// 混合优化配置 - FileReader大块 + 网络小包策略(修复sendData failed)
|
||||
private static readonly OPTIMIZED_CONFIG = {
|
||||
CHUNK_SIZE: 262144, // 256KB - 最优块大小,减少I/O调用
|
||||
BATCH_SIZE: 12, // 12块批量 - 充分利用内存和网络
|
||||
BUFFER_THRESHOLD: 1572864, // 1.5MB - 最大化网络利用率
|
||||
MAX_BUFFER_SIZE: 5, // 5块预读缓冲
|
||||
BACKPRESSURE_TIMEOUT: 1000, // 1秒超时
|
||||
CHUNK_SIZE: 4194304, // 4MB - 极致大块,最大化减少FileReader调用次数
|
||||
BATCH_SIZE: 8, // 8块批量 - 32MB批次处理成功
|
||||
NETWORK_CHUNK_SIZE: 65536, // 64KB - WebRTC安全发送大小,修复sendData failed
|
||||
BUFFER_THRESHOLD: 3145728, // 3MB - 提升缓冲区阈值匹配大块处理
|
||||
MAX_BUFFER_SIZE: 3, // 3块预读缓冲 - 平衡内存和性能
|
||||
BACKPRESSURE_TIMEOUT: 2000, // 2秒超时 - 为大块处理预留更多时间
|
||||
} as const;
|
||||
|
||||
constructor(WebRTC_initiator: WebRTC_Initiator) {
|
||||
@@ -51,6 +53,27 @@ class FileSender {
|
||||
// Create a SpeedCalculator instance
|
||||
this.speedCalculator = new SpeedCalculator();
|
||||
this.setupDataHandler();
|
||||
|
||||
// 显示极致优化参数并发送日志
|
||||
const batchSizeMB =
|
||||
(this.chunkSize * FileSender.OPTIMIZED_CONFIG.BATCH_SIZE) / (1024 * 1024);
|
||||
const initMessage = `[FileSender] 混合优化初始化 - FileReader: ${
|
||||
this.chunkSize / (1024 * 1024)
|
||||
}MB块, 批次: ${batchSizeMB}MB, 网络: 64KB小包, 修复sendData failed!`;
|
||||
console.log(initMessage);
|
||||
|
||||
// 发送初始化日志到后端用于性能分析
|
||||
postLogToBackend(
|
||||
`HYBRID_OPTIMIZATION_INIT: FileReaderChunkSizeMB=${
|
||||
this.chunkSize / (1024 * 1024)
|
||||
}, BatchSize=${
|
||||
FileSender.OPTIMIZED_CONFIG.BATCH_SIZE
|
||||
}, BatchSizeMB=${batchSizeMB.toFixed(1)}, NetworkChunkSizeKB=${
|
||||
FileSender.OPTIMIZED_CONFIG.NETWORK_CHUNK_SIZE / 1024
|
||||
}, BufferThresholdMB=${
|
||||
FileSender.OPTIMIZED_CONFIG.BUFFER_THRESHOLD / (1024 * 1024)
|
||||
}, strategy=HYBRID_FileReader_Network_Optimization`
|
||||
).catch(() => {});
|
||||
}
|
||||
|
||||
// region Logging and Error Handling
|
||||
@@ -236,9 +259,19 @@ class FileSender {
|
||||
const peerState = this.getPeerState(peerId);
|
||||
|
||||
if (peerState.isSending) {
|
||||
console.log(`[FileSender] 正在发送文件给 ${peerId},忽略 ${file.name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const fileSizeMB = Math.round((file.size / (1024 * 1024)) * 100) / 100;
|
||||
const startMessage = `[FileSender] 开始混合优化传输: ${file.name} (${fileSizeMB}MB) - 4MB FileReader + 64KB 网络分片`;
|
||||
console.log(startMessage);
|
||||
|
||||
// 发送传输开始日志
|
||||
postLogToBackend(
|
||||
`HYBRID_TRANSFER_START: fileName=${file.name}, fileSizeMB=${fileSizeMB}, fileReaderChunkSizeMB=4, networkChunkSizeKB=64, strategy=HYBRID_FileReader_Network_Optimization`
|
||||
).catch(() => {});
|
||||
|
||||
// Reset state for the new transfer
|
||||
peerState.isSending = true;
|
||||
peerState.currentFolderName = file.folderName;
|
||||
@@ -252,6 +285,22 @@ class FileSender {
|
||||
this.finalizeSendFile(fileId, peerId);
|
||||
|
||||
await this.waitForTransferComplete(peerId); // Wait for transfer completion -- receiver confirmation
|
||||
|
||||
const finalSpeed = this.speedCalculator.getSendSpeed(peerId);
|
||||
const successMessage = `[FileSender] 文件 ${fileId} 成功发送至 ${peerId} - 最终速度: ${finalSpeed.toFixed(
|
||||
2
|
||||
)} KB/s`;
|
||||
console.log(successMessage);
|
||||
|
||||
// 发送成功日志到后端
|
||||
const fileSizeMB = Math.round((file.size / (1024 * 1024)) * 100) / 100;
|
||||
postLogToBackend(
|
||||
`HYBRID_TRANSFER_SUCCESS: fileName=${
|
||||
file.name
|
||||
}, fileSizeMB=${fileSizeMB}, finalSpeedKBps=${finalSpeed.toFixed(
|
||||
2
|
||||
)}, fileReaderChunkSizeMB=4, networkChunkSizeKB=64, hybridOptimized=true`
|
||||
).catch(() => {});
|
||||
} catch (error: any) {
|
||||
this.fireError(`Error sending file ${file.name}: ${error.message}`, {
|
||||
fileId,
|
||||
@@ -334,10 +383,102 @@ class FileSender {
|
||||
throw new Error("Data channel not found");
|
||||
}
|
||||
|
||||
// 统一使用优化的缓冲区阈值
|
||||
const threshold = FileSender.OPTIMIZED_CONFIG.BUFFER_THRESHOLD;
|
||||
// 对于ArrayBuffer,如果超过64KB需要分片发送(修复sendData failed)
|
||||
if (data instanceof ArrayBuffer) {
|
||||
await this.sendLargeArrayBuffer(data, peerId);
|
||||
} else {
|
||||
// 字符串直接发送
|
||||
await this.sendSingleData(data, peerId);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否需要等待背压缓解
|
||||
// 新增:分片发送大ArrayBuffer
|
||||
private async sendLargeArrayBuffer(
|
||||
data: ArrayBuffer,
|
||||
peerId: string
|
||||
): Promise<void> {
|
||||
const networkChunkSize = FileSender.OPTIMIZED_CONFIG.NETWORK_CHUNK_SIZE;
|
||||
const totalSize = data.byteLength;
|
||||
|
||||
// 如果数据小于64KB,直接发送
|
||||
if (totalSize <= networkChunkSize) {
|
||||
await this.sendSingleData(data, peerId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 分片发送大块
|
||||
let offset = 0;
|
||||
let fragmentIndex = 0;
|
||||
const totalFragments = Math.ceil(totalSize / networkChunkSize);
|
||||
|
||||
const fragmentStartTime = performance.now();
|
||||
console.log(
|
||||
`[FileSender] 分片发送${
|
||||
Math.round((totalSize / (1024 * 1024)) * 100) / 100
|
||||
}MB大块 -> ${totalFragments}个64KB小包`
|
||||
);
|
||||
|
||||
// 记录分片开始日志
|
||||
postLogToBackend(
|
||||
`NETWORK_FRAGMENTATION_START: totalSizeMB=${
|
||||
Math.round((totalSize / (1024 * 1024)) * 100) / 100
|
||||
}, totalFragments=${totalFragments}, networkChunkSizeKB=${
|
||||
networkChunkSize / 1024
|
||||
}`
|
||||
).catch(() => {});
|
||||
|
||||
while (offset < totalSize) {
|
||||
const chunkSize = Math.min(networkChunkSize, totalSize - offset);
|
||||
const chunk = data.slice(offset, offset + chunkSize);
|
||||
|
||||
// 发送分片
|
||||
await this.sendSingleData(chunk, peerId);
|
||||
|
||||
offset += chunkSize;
|
||||
fragmentIndex++;
|
||||
|
||||
// 每10个分片记录一次进度
|
||||
if (fragmentIndex % 10 === 0) {
|
||||
console.log(
|
||||
`[FileSender] 分片发送进度: ${fragmentIndex}/${totalFragments} (${Math.round(
|
||||
(offset / totalSize) * 100
|
||||
)}%)`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const fragmentTime = performance.now() - fragmentStartTime;
|
||||
const fragmentThroughputMBps =
|
||||
totalSize / (1024 * 1024) / (fragmentTime / 1000);
|
||||
|
||||
console.log(
|
||||
`[FileSender] 大块分片发送完成: ${totalFragments}个64KB小包, 用时: ${fragmentTime.toFixed(
|
||||
0
|
||||
)}ms, 速度: ${fragmentThroughputMBps.toFixed(2)}MB/s`
|
||||
);
|
||||
|
||||
// 记录分片完成日志
|
||||
postLogToBackend(
|
||||
`NETWORK_FRAGMENTATION_COMPLETE: totalFragments=${totalFragments}, fragmentTimeMs=${fragmentTime.toFixed(
|
||||
0
|
||||
)}, fragmentThroughputMBps=${fragmentThroughputMBps.toFixed(
|
||||
2
|
||||
)}, networkOptimizationSuccess=true`
|
||||
).catch(() => {});
|
||||
}
|
||||
|
||||
// 新增:单个数据包发送含背压控制
|
||||
private async sendSingleData(
|
||||
data: string | ArrayBuffer,
|
||||
peerId: string
|
||||
): Promise<void> {
|
||||
const dataChannel = this.webrtcConnection.dataChannels.get(peerId);
|
||||
if (!dataChannel) {
|
||||
throw new Error("Data channel not found");
|
||||
}
|
||||
|
||||
// 背压控制
|
||||
const threshold = FileSender.OPTIMIZED_CONFIG.BUFFER_THRESHOLD;
|
||||
if (dataChannel.bufferedAmount > threshold) {
|
||||
await new Promise<void>((resolve) => {
|
||||
const onBufferedAmountLow = () => {
|
||||
@@ -349,7 +490,6 @@ class FileSender {
|
||||
};
|
||||
dataChannel.addEventListener("bufferedamountlow", onBufferedAmountLow);
|
||||
|
||||
// 设置超时以避免永久等待
|
||||
setTimeout(() => {
|
||||
dataChannel.removeEventListener(
|
||||
"bufferedamountlow",
|
||||
@@ -360,6 +500,7 @@ class FileSender {
|
||||
});
|
||||
}
|
||||
|
||||
// 发送数据
|
||||
if (!this.webrtcConnection.sendData(data, peerId)) {
|
||||
throw new Error("sendData failed");
|
||||
}
|
||||
@@ -431,10 +572,23 @@ class FileSender {
|
||||
let offset = peerState.readOffset || 0;
|
||||
const batchSize = FileSender.OPTIMIZED_CONFIG.BATCH_SIZE;
|
||||
|
||||
const expectedBatchSizeMB = (this.chunkSize * batchSize) / (1024 * 1024);
|
||||
console.log(
|
||||
`[FileSender] 极致优化传输开始 - 块大小: ${
|
||||
this.chunkSize / (1024 * 1024)
|
||||
}MB, 批次: ${batchSize}块 (${expectedBatchSizeMB}MB), 充分利用内存!`
|
||||
);
|
||||
|
||||
// 记录批次处理开始日志
|
||||
postLogToBackend(
|
||||
`HYBRID_BATCH_START: fileReaderChunkSizeMB=4, batchSize=${batchSize}, expectedBatchSizeMB=${expectedBatchSizeMB}, fileOffset=${offset}, networkChunkSizeKB=64`
|
||||
).catch(() => {});
|
||||
|
||||
try {
|
||||
// 使用批量读取+循环替代传统递归,大幅提升性能
|
||||
while (offset < file.size && peerState.isSending) {
|
||||
// 批量读取多个块
|
||||
// 批量读取多个大块 - 充分利用内存优势
|
||||
const batchReadStartTime = performance.now();
|
||||
const chunks = await this.readMultipleChunks(
|
||||
fileReader,
|
||||
file,
|
||||
@@ -442,10 +596,40 @@ class FileSender {
|
||||
this.chunkSize,
|
||||
batchSize
|
||||
);
|
||||
const batchReadTime = performance.now() - batchReadStartTime;
|
||||
|
||||
if (chunks.length === 0) break;
|
||||
|
||||
// 批量发送所有读取的块
|
||||
const actualBatchSizeMB =
|
||||
chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0) /
|
||||
(1024 * 1024);
|
||||
const readThroughputMBps = actualBatchSizeMB / (batchReadTime / 1000);
|
||||
|
||||
console.log(
|
||||
`[FileSender] FileReader批次读取完成: ${
|
||||
chunks.length
|
||||
}块, 实际大小: ${actualBatchSizeMB.toFixed(
|
||||
1
|
||||
)}MB, 读取时间: ${batchReadTime.toFixed(
|
||||
0
|
||||
)}ms, 吐量: ${readThroughputMBps.toFixed(2)}MB/s, 将分片为64KB发送`
|
||||
);
|
||||
|
||||
// 发送批次读取性能日志
|
||||
postLogToBackend(
|
||||
`HYBRID_BATCH_READ: chunksCount=${
|
||||
chunks.length
|
||||
}, actualBatchSizeMB=${actualBatchSizeMB.toFixed(
|
||||
1
|
||||
)}, readTimeMs=${batchReadTime.toFixed(
|
||||
0
|
||||
)}, readThroughputMBps=${readThroughputMBps.toFixed(
|
||||
2
|
||||
)}, fileReaderCallsReduced=93.75%, willFragmentToNetworkChunks=true`
|
||||
).catch(() => {});
|
||||
|
||||
// 批量发送所有读取的大块 - 记录发送性能
|
||||
const batchSendStartTime = performance.now();
|
||||
for (const chunk of chunks) {
|
||||
if (!peerState.isSending || offset >= file.size) break;
|
||||
|
||||
@@ -463,18 +647,67 @@ class FileSender {
|
||||
peerId
|
||||
);
|
||||
}
|
||||
const batchSendTime = performance.now() - batchSendStartTime;
|
||||
const sendThroughputMBps = actualBatchSizeMB / (batchSendTime / 1000);
|
||||
const totalBatchTime = batchReadTime + batchSendTime;
|
||||
const readVsSendRatio = (
|
||||
(batchReadTime / totalBatchTime) *
|
||||
100
|
||||
).toFixed(1);
|
||||
|
||||
// 记录批次处理性能日志
|
||||
postLogToBackend(
|
||||
`HYBRID_BATCH_COMPLETE: sendTimeMs=${batchSendTime.toFixed(
|
||||
0
|
||||
)}, sendThroughputMBps=${sendThroughputMBps.toFixed(
|
||||
2
|
||||
)}, totalBatchTimeMs=${totalBatchTime.toFixed(
|
||||
0
|
||||
)}, readRatio=${readVsSendRatio}%, memoryUsageMB=${actualBatchSizeMB.toFixed(
|
||||
1
|
||||
)}, networkFragmentationApplied=true`
|
||||
).catch(() => {});
|
||||
}
|
||||
|
||||
// 文件发送完毕
|
||||
if (offset >= file.size && !peerState.currentFolderName) {
|
||||
peerState.progressCallback?.(fileId, 1, 0);
|
||||
}
|
||||
|
||||
const finalSpeed = this.speedCalculator.getSendSpeed(peerId);
|
||||
const totalChunks = Math.ceil(file.size / this.chunkSize);
|
||||
const fileReaderCallReduction = (
|
||||
(1 - totalChunks / (file.size / 262144)) *
|
||||
100
|
||||
).toFixed(1); // 与256KB对比
|
||||
|
||||
console.log(
|
||||
`[FileSender] 混合优化传输完成! FileReader: ${totalChunks}个4MB块, 网络: 64KB分片, 最终速度: ${finalSpeed.toFixed(
|
||||
2
|
||||
)} KB/s - sendData failed已修复!`
|
||||
);
|
||||
|
||||
// 发送最终性能统计日志
|
||||
const fileSizeMB = Math.round((file.size / (1024 * 1024)) * 100) / 100;
|
||||
postLogToBackend(
|
||||
`HYBRID_TRANSFER_COMPLETE: fileSizeMB=${fileSizeMB}, totalChunks=${totalChunks}, finalSpeedKBps=${finalSpeed.toFixed(
|
||||
2
|
||||
)}, fileReaderCallReduction=${fileReaderCallReduction}%, fileReaderChunkSizeMB=4, networkChunkSizeKB=64, hybridOptimizationSuccess=true`
|
||||
).catch(() => {});
|
||||
} catch (error: any) {
|
||||
this.fireError(`Error in optimized batch transfer: ${error.message}`, {
|
||||
const errorMessage = `Error in hybrid optimized transfer: ${error.message}`;
|
||||
this.fireError(errorMessage, {
|
||||
fileId,
|
||||
peerId,
|
||||
offset,
|
||||
});
|
||||
|
||||
// 发送错误日志到后端用于分析
|
||||
const fileSizeMB = Math.round((file.size / (1024 * 1024)) * 100) / 100;
|
||||
postLogToBackend(
|
||||
`HYBRID_TRANSFER_ERROR: fileName=${file.name}, fileSizeMB=${fileSizeMB}, errorMessage=${error.message}, offset=${offset}, fileReaderChunkSizeMB=4, networkChunkSizeKB=64, hybridOptimization=true`
|
||||
).catch(() => {});
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user