fix:Temporarily optimized the speed issue on the mobile

This commit is contained in:
david_bai
2025-08-30 00:06:59 +08:00
parent b38ef84bca
commit 4437c70257
+246 -13
View File
@@ -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;
}
}