It is found that the order of data packets received by Firefox is disordered

This commit is contained in:
david_bai
2025-09-05 22:58:56 +08:00
parent ec6a18dfc0
commit 81c2b204f3
2 changed files with 153 additions and 134 deletions
+114 -77
View File
@@ -1,5 +1,5 @@
// 🚀 新流程 - 接收端主导的文件传输:
// 1. 接收文件元数据 (fileMetadata)
// 1. 接收文件元数据 (fileMetadata)
// 2. 用户点击下载,发送文件请求 (fileRequest)
// 3. 接收所有数据块,自动检测完整性
// 4. 完成Store同步后,主动发送完成确认 (fileReceiveComplete/folderReceiveComplete)
@@ -187,7 +187,7 @@ class FileReceiver {
const receptionPromise = new Promise<void>((resolve, reject) => {
const expectedChunksCount = Math.ceil((fileInfo.size - offset) / 65536); // 计算预期chunk数量
this.activeFileReception = {
meta: fileInfo,
chunks: [],
@@ -284,7 +284,7 @@ class FileReceiver {
// 🚀 新流程:发送文件夹接收完成确认
// 收集所有成功完成的文件ID
const completedFileIds = folderProgress.fileIds.filter(fileId => {
const completedFileIds = folderProgress.fileIds.filter((fileId) => {
// 这里可以添加更复杂的验证逻辑,现在简单假设都成功了
return true;
});
@@ -299,21 +299,23 @@ class FileReceiver {
// endregion
// region WebRTC Data Handlers
/**
* 将各种二进制数据格式转换为ArrayBuffer
* 支持Firefox的Blob、Uint8Array等格式
*/
private async convertToArrayBuffer(data: any): Promise<ArrayBuffer | null> {
const originalType = Object.prototype.toString.call(data);
if (data instanceof ArrayBuffer) {
return data;
} else if (data instanceof Blob) {
try {
const arrayBuffer = await data.arrayBuffer();
if (data.size !== arrayBuffer.byteLength) {
postLogToBackend(`[DEBUG] ⚠️ Blob size mismatch: ${data.size}${arrayBuffer.byteLength}`);
postLogToBackend(
`[DEBUG] ⚠️ Blob size mismatch: ${data.size}${arrayBuffer.byteLength}`
);
}
return arrayBuffer;
} catch (error) {
@@ -322,9 +324,10 @@ class FileReceiver {
}
} else if (data instanceof Uint8Array || ArrayBuffer.isView(data)) {
try {
const uint8Array = data instanceof Uint8Array
? data
: new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
const uint8Array =
data instanceof Uint8Array
? data
: new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
const newArrayBuffer = new ArrayBuffer(uint8Array.length);
new Uint8Array(newArrayBuffer).set(uint8Array);
return newArrayBuffer;
@@ -333,7 +336,9 @@ class FileReceiver {
return null;
}
} else {
postLogToBackend(`[DEBUG] ❌ Unknown data type: ${Object.prototype.toString.call(data)}`);
postLogToBackend(
`[DEBUG] ❌ Unknown data type: ${Object.prototype.toString.call(data)}`
);
return null;
}
}
@@ -363,13 +368,8 @@ class FileReceiver {
} else {
// 处理各种格式的二进制数据 - Firefox兼容性修复
const arrayBuffer = await this.convertToArrayBuffer(data);
if (arrayBuffer) {
// 调试日志:记录接收到二进制数据
postLogToBackend(
`[Firefox Debug] Received binary data - originalType: ${Object.prototype.toString.call(data)}, convertedSize: ${arrayBuffer.byteLength}, peerId: ${peerId}`
);
if (arrayBuffer) {
if (!this.activeFileReception) {
postLogToBackend(
`[Firefox Debug] ERROR: Received file chunk but no active file reception!`
@@ -381,9 +381,6 @@ class FileReceiver {
return;
}
postLogToBackend(
`[Firefox Debug] Processing chunk for file: ${this.activeFileReception.meta.name}`
);
this.updateProgress(arrayBuffer.byteLength);
await this.handleFileChunk(arrayBuffer);
} else {
@@ -392,7 +389,7 @@ class FileReceiver {
);
this.fireError("Received unsupported binary data format", {
dataType: Object.prototype.toString.call(data),
peerId
peerId,
});
}
}
@@ -460,35 +457,49 @@ class FileReceiver {
// 🐛 DEBUG: 记录接收到的原始chunk信息
const currentChunkIndex = this.activeFileReception.receivedChunksCount;
postLogToBackend(
`[DEBUG] 📥 RECEIVE chunk#${currentChunkIndex} - size: ${chunk.byteLength} bytes`
);
// 更新统计信息
this.activeFileReception.receivedChunksCount++;
this.activeFileReception.lastChunkIndex = Math.max(this.activeFileReception.lastChunkIndex, currentChunkIndex);
this.activeFileReception.lastChunkIndex = Math.max(
this.activeFileReception.lastChunkIndex,
currentChunkIndex
);
// 更新进度统计
this.updateProgress(chunk.byteLength);
if (this.activeFileReception.writeStream) {
await this.writeLargeFileChunk(chunk);
} else {
// 存储chunk到内存
this.activeFileReception.chunks.push(chunk);
// 🐛 DEBUG: 验证存储结果
const storedChunk = this.activeFileReception.chunks[this.activeFileReception.chunks.length - 1];
const currentTotalSize = this.activeFileReception.chunks.reduce((sum, c) => sum + (c?.byteLength || 0), 0);
postLogToBackend(
`[DEBUG] 📦 STORED chunk#${currentChunkIndex} - original: ${chunk.byteLength}, stored: ${storedChunk?.byteLength || 'null'}, total: ${currentTotalSize}`
const storedChunk =
this.activeFileReception.chunks[
this.activeFileReception.chunks.length - 1
];
const currentTotalSize = this.activeFileReception.chunks.reduce(
(sum, c) => sum + (c?.byteLength || 0),
0
);
postLogToBackend(
`[DEBUG] 📦 STORED chunk#${currentChunkIndex} - original: ${
chunk.byteLength
}, stored: ${
storedChunk?.byteLength || "null"
}, total: ${currentTotalSize}`
);
// 🐛 DEBUG: 特别关注最后几个chunks
if (currentChunkIndex >= 65) {
postLogToBackend(
`[DEBUG] 🔍 CRITICAL_CHUNK#${currentChunkIndex} - input: ${chunk.byteLength}, stored: ${storedChunk?.byteLength}, isLast: ${currentChunkIndex >= 67}`
`[DEBUG] 🔍 CRITICAL_CHUNK#${currentChunkIndex} - input: ${
chunk.byteLength
}, stored: ${storedChunk?.byteLength}, isLast: ${
currentChunkIndex >= 67
}`
);
}
}
@@ -506,44 +517,41 @@ class FileReceiver {
const reception = this.activeFileReception;
const receivedChunks = reception.receivedChunksCount;
const expectedChunks = reception.expectedChunksCount;
// 计算当前实际接收的总大小
const currentTotalSize = reception.chunks.reduce((sum, chunk) => {
return sum + (chunk instanceof ArrayBuffer ? chunk.byteLength : 0);
}, 0);
const expectedSize = reception.meta.size;
const chunksComplete = (receivedChunks >= expectedChunks);
const sizeComplete = (currentTotalSize >= expectedSize);
const chunksComplete = receivedChunks >= expectedChunks;
const sizeComplete = currentTotalSize >= expectedSize;
const isDataComplete = chunksComplete && sizeComplete;
// 🐛 DEBUG: 完成状态检查
if (receivedChunks % 10 === 0 || receivedChunks >= expectedChunks - 5) {
postLogToBackend(
`[DEBUG] 🔄 Progress check - chunks: ${receivedChunks}/${expectedChunks}, size: ${currentTotalSize}/${expectedSize}, complete: ${isDataComplete}`
);
}
postLogToBackend(
`[DEBUG] 🔄 Progress check - chunks: ${receivedChunks}/${expectedChunks}, size: ${currentTotalSize}/${expectedSize}, complete: ${isDataComplete}, isFinalized:${reception.isFinalized}`
);
// 防止重复finalize
if (reception.isFinalized) {
return;
}
if (isDataComplete) {
postLogToBackend(
`[DEBUG] 🎯 TRIGGERING finalize - chunks: ${receivedChunks}/${expectedChunks}, size: ${currentTotalSize}/${expectedSize}`
);
reception.isFinalized = true;
try {
await this.finalizeFileReceive();
if (reception.completionNotifier) {
reception.completionNotifier.resolve();
}
this.activeFileReception = null;
postLogToBackend(`[DEBUG] ✅ Auto-finalize SUCCESS`);
} catch (error) {
postLogToBackend(`[DEBUG] ❌ Auto-finalize ERROR: ${error}`);
@@ -693,56 +701,79 @@ class FileReceiver {
let totalChunkSize = 0;
let validChunks = 0;
const chunkDetails: string[] = [];
reception.chunks.forEach((chunk, index) => {
if (chunk instanceof ArrayBuffer) {
validChunks++;
totalChunkSize += chunk.byteLength;
// 🐛 DEBUG: 特别关注最后几个chunks
if (index >= reception.chunks.length - 5) {
chunkDetails.push(`chunk#${index}: ${chunk.byteLength}bytes`);
postLogToBackend(
`[DEBUG] 🔍 FINAL_CHUNK_ANALYSIS - index: ${index}, size: ${chunk.byteLength}, isLast: ${index === reception.chunks.length - 1}`
`[DEBUG] 🔍 FINAL_CHUNK_ANALYSIS - index: ${index}, size: ${
chunk.byteLength
}, isLast: ${index === reception.chunks.length - 1}`
);
}
// 检测异常大小
if (chunk.byteLength !== 65536 && index < reception.chunks.length - 1) {
postLogToBackend(`[DEBUG] ⚠️ UNEXPECTED_SIZE - chunk#${index}: ${chunk.byteLength} (should be 65536)`);
postLogToBackend(
`[DEBUG] ⚠️ UNEXPECTED_SIZE - chunk#${index}: ${chunk.byteLength} (should be 65536)`
);
}
} else {
postLogToBackend(`[DEBUG] ❌ INVALID_CHUNK - index: ${index}, type: ${Object.prototype.toString.call(chunk)}`);
postLogToBackend(
`[DEBUG] ❌ INVALID_CHUNK - index: ${index}, type: ${Object.prototype.toString.call(
chunk
)}`
);
}
});
// 🐛 DEBUG: 总体分析
postLogToBackend(
`[DEBUG] 📊 CHUNK_SUMMARY - valid: ${validChunks}/${reception.chunks.length}, totalSize: ${totalChunkSize}, expected: ${reception.meta.size}, diff: ${reception.meta.size - totalChunkSize}`
`[DEBUG] 📊 CHUNK_SUMMARY - valid: ${validChunks}/${
reception.chunks.length
}, totalSize: ${totalChunkSize}, expected: ${
reception.meta.size
}, diff: ${reception.meta.size - totalChunkSize}`
);
if (chunkDetails.length > 0) {
postLogToBackend(`[DEBUG] 🔍 FINAL_CHUNKS: ${chunkDetails.join(', ')}`);
postLogToBackend(`[DEBUG] 🔍 FINAL_CHUNKS: ${chunkDetails.join(", ")}`);
}
// 最终验证
const sizeDifference = reception.meta.size - totalChunkSize;
if (sizeDifference !== 0) {
postLogToBackend(`[DEBUG] ❌ SIZE_MISMATCH - missing: ${sizeDifference} bytes`);
postLogToBackend(
`[DEBUG] ❌ SIZE_MISMATCH - missing: ${sizeDifference} bytes`
);
} else {
postLogToBackend(`[DEBUG] ✅ SIZE_VERIFIED - ${totalChunkSize} bytes`);
}
// 创建文件
const fileBlob = new Blob(reception.chunks.filter(chunk => chunk instanceof ArrayBuffer) as ArrayBuffer[], {
type: reception.meta.fileType,
});
const fileBlob = new Blob(
reception.chunks.filter(
(chunk) => chunk instanceof ArrayBuffer
) as ArrayBuffer[],
{
type: reception.meta.fileType,
}
);
const file = new File([fileBlob], reception.meta.name, {
type: reception.meta.fileType,
});
postLogToBackend(`[DEBUG] 📄 FILE_CREATED - size: ${file.size}, expected: ${reception.meta.size}, match: ${file.size === reception.meta.size}`);
postLogToBackend(
`[DEBUG] 📄 FILE_CREATED - size: ${file.size}, expected: ${
reception.meta.size
}, match: ${file.size === reception.meta.size}`
);
const customFile = Object.assign(file, {
fullName: reception.meta.fullName,
@@ -755,7 +786,7 @@ class FileReceiver {
await Promise.resolve();
await new Promise<void>((resolve) => setTimeout(() => resolve(), 0));
storeUpdated = true;
postLogToBackend(`[DEBUG] ✅ STORE_UPDATED - ${reception.meta.name}`);
}
@@ -781,7 +812,7 @@ class FileReceiver {
storeUpdated: boolean
): void {
if (!this.peerId) return;
const completeMessage: FileReceiveComplete = {
type: "fileReceiveComplete",
fileId,
@@ -789,9 +820,12 @@ class FileReceiver {
receivedChunks,
storeUpdated,
};
const success = this.webrtcConnection.sendData(JSON.stringify(completeMessage), this.peerId);
const success = this.webrtcConnection.sendData(
JSON.stringify(completeMessage),
this.peerId
);
postLogToBackend(
`[DEBUG] 📤 SENT fileReceiveComplete - size: ${receivedSize}, chunks: ${receivedChunks}, success: ${success}`
);
@@ -806,16 +840,19 @@ class FileReceiver {
allStoreUpdated: boolean
): void {
if (!this.peerId) return;
const completeMessage: FolderReceiveComplete = {
type: "folderReceiveComplete",
folderName,
completedFileIds,
allStoreUpdated,
};
const success = this.webrtcConnection.sendData(JSON.stringify(completeMessage), this.peerId);
const success = this.webrtcConnection.sendData(
JSON.stringify(completeMessage),
this.peerId
);
postLogToBackend(
`[Firefox Debug] 📤 Sent folderReceiveComplete - folderName: ${folderName}, completedFiles: ${completedFileIds.length}, allStoreUpdated: ${allStoreUpdated}, success: ${success}`
);
+39 -57
View File
@@ -1,6 +1,6 @@
// 🚀 新流程 - 接收端主导的文件传输:
// 1. 发送文件元数据 (fileMetadata)
// 2. 接收文件请求 (fileRequest)
// 2. 接收文件请求 (fileRequest)
// 3. 发送所有数据块,完成后等待接收端确认
// 4. 收到接收端确认 (fileReceiveComplete/folderReceiveComplete) 后设置进度100%
// 发送端不再主动发送完成信号,完全由接收端控制完成时机
@@ -113,6 +113,7 @@ class FileSender {
private handleSignalingMessage(message: WebRTCMessage, peerId: string): void {
const peerState = this.getPeerState(peerId);
postLogToBackend(`debug Message:${message.type}`);
switch (message.type) {
case "fileRequest":
this.handleFileRequest(message as FileRequest, peerId);
@@ -121,7 +122,10 @@ class FileSender {
this.handleFileReceiveComplete(message as FileReceiveComplete, peerId);
break;
case "folderReceiveComplete":
this.handleFolderReceiveComplete(message as FolderReceiveComplete, peerId);
this.handleFolderReceiveComplete(
message as FolderReceiveComplete,
peerId
);
break;
default:
this.log("warn", `Unknown signaling message type received`, {
@@ -146,7 +150,7 @@ class FileSender {
peerId: string
): void {
const peerState = this.getPeerState(peerId);
postLogToBackend(
`[Firefox Debug] 📥 Received fileReceiveComplete - fileId: ${message.fileId}, receivedSize: ${message.receivedSize}, receivedChunks: ${message.receivedChunks}, storeUpdated: ${message.storeUpdated}`
);
@@ -181,7 +185,7 @@ class FileSender {
peerId: string
): void {
const peerState = this.getPeerState(peerId);
postLogToBackend(
`[Firefox Debug] 📥 Received folderReceiveComplete - folderName: ${message.folderName}, completedFiles: ${message.completedFileIds.length}, allStoreUpdated: ${message.allStoreUpdated}`
);
@@ -211,13 +215,15 @@ class FileSender {
"log",
`Handling file request for ${request.fileId} from ${peerId} with offset ${offset}`
);
// 🔧 Firefox兼容性修复:添加稍长延迟确保接收端完全准备好
// 根据[[memory:7549586]],这个延迟解决了时序竞态条件
await new Promise(resolve => setTimeout(resolve, 10));
await new Promise((resolve) => setTimeout(resolve, 10));
if (file) {
postLogToBackend(`[Firefox Debug] Starting file send - fileName: ${file.name}, fileSize: ${file.size}, offset: ${offset}`);
postLogToBackend(
`[Firefox Debug] Starting file send - fileName: ${file.name}, fileSize: ${file.size}, offset: ${offset}`
);
await this.sendSingleFile(file, peerId, offset);
} else {
this.fireError(`File not found for request`, {
@@ -317,7 +323,7 @@ class FileSender {
try {
await this.processSendQueue(file, peerId);
// 🚀 新流程:不再主动发送fileEnd,等待接收端的fileReceiveComplete确认
postLogToBackend(
`[Firefox Debug] 📤 File sending completed, waiting for receiver confirmation - ${file.name}`
@@ -459,7 +465,9 @@ class FileSender {
// Send fragment
await this.sendSingleData(chunk, peerId);
postLogToBackend(
`[sender Debug] chunk idx:${fragmentIndex} ,size:${chunkSize}`
);
offset += chunkSize;
fragmentIndex++;
}
@@ -474,21 +482,27 @@ class FileSender {
if (!dataChannel) {
throw new Error("Data channel not found");
}
// Firefox兼容性调试:记录发送前的数据信息
const dataType = typeof data === "string" ? "string" : data instanceof ArrayBuffer ? "ArrayBuffer" : "unknown";
const dataSize = typeof data === "string" ? data.length : data instanceof ArrayBuffer ? data.byteLength : 0;
postLogToBackend(`[Firefox Debug] Sending data - type: ${dataType}, size: ${dataSize}, channelState: ${dataChannel.readyState}`);
const dataType =
typeof data === "string"
? "string"
: data instanceof ArrayBuffer
? "ArrayBuffer"
: "unknown";
const dataSize =
typeof data === "string"
? data.length
: data instanceof ArrayBuffer
? data.byteLength
: 0;
// Intelligent send control - decide sending strategy based on buffer status
await this.smartBufferControl(dataChannel, peerId);
// Send data
const sendResult = this.webrtcConnection.sendData(data, peerId);
postLogToBackend(`[Firefox Debug] Data send result: ${sendResult ? 'success' : 'failed'} - type: ${dataType}, size: ${dataSize}`);
if (!sendResult) {
const errorMessage = `sendData failed for ${dataType} data of size ${dataSize}`;
postLogToBackend(`[Firefox Debug] ❌ ${errorMessage}`);
@@ -751,10 +765,6 @@ class FileSender {
// Initialize network performance monitoring
this.initializeNetworkPerformance(peerId);
postLogToBackend(
`[Firefox Debug] 🚀 processSendQueue started - fileName: ${file.name}, fileSize: ${file.size}, startOffset: ${offset}`
);
try {
let loopCount = 0;
// Use batch reading + loop instead of traditional recursion to greatly improve performance
@@ -772,23 +782,22 @@ class FileSender {
if (chunks.length === 0) break;
postLogToBackend(
`[Firefox Debug] 📦 Loop ${loopCount} - read ${chunks.length} chunks, totalSize: ${chunks.reduce((sum, c) => sum + c.byteLength, 0)}`
);
for (const chunk of chunks) {
if (!peerState.isSending || offset >= file.size) break;
// 🔧 修复:检查发送是否成功
let sendSuccessful = false;
try {
await this.sendWithBackpressure(chunk, peerId);
sendSuccessful = true;
totalChunksSent++;
totalBytesSentInLoop += chunk.byteLength;
} catch (error) {
postLogToBackend(
`[Firefox Debug] ❌ Failed to send chunk ${totalChunksSent + 1}: ${error}`
`[Firefox Debug] ❌ Failed to send chunk ${
totalChunksSent + 1
}: ${error}`
);
sendSuccessful = false;
// 不更新统计,但继续尝试发送下一个chunk
@@ -807,14 +816,6 @@ class FileSender {
peerId,
true // 明确标记为发送成功
);
// 每50个chunk记录一次进度
if (totalChunksSent % 50 === 0) {
const progress = ((offset / file.size) * 100).toFixed(2);
postLogToBackend(
`[Firefox Debug] 📊 Progress: ${totalChunksSent} chunks sent, ${totalBytesSentInLoop} bytes, ${progress}% complete`
);
}
} else {
// 发送失败但不中止传输,记录失败信息
postLogToBackend(
@@ -823,25 +824,7 @@ class FileSender {
}
}
}
// File sending completed - final statistics using actual sent bytes
const actualBytesSent = peerState.totalBytesSent[fileId] || 0;
postLogToBackend(
`[Firefox Debug] 🏁 Send completed - totalChunks: ${totalChunksSent}, loopBytes: ${totalBytesSentInLoop}, actualBytes: ${actualBytesSent}, finalOffset: ${offset}, expected: ${file.size}`
);
// 验证统计一致性
if (totalBytesSentInLoop !== actualBytesSent) {
postLogToBackend(
`[Firefox Debug] ⚠️ Statistics mismatch! Loop counted ${totalBytesSentInLoop} bytes but progress tracked ${actualBytesSent} bytes`
);
}
// 🚀 新流程:不再在这里设置进度100%,等待接收端确认
// if (offset >= file.size && !peerState.currentFolderName) {
// peerState.progressCallback?.(fileId, 1, 0);
// }
postLogToBackend(
`[Firefox Debug] 🏁 All data sent, waiting for receiver to confirm completion...`
);
@@ -861,7 +844,6 @@ class FileSender {
}
}
private abortFileSend(fileId: string, peerId: string): void {
this.log("warn", `Aborting file send for ${fileId} to ${peerId}`);
const peerState = this.getPeerState(peerId);