C# P2P文件传输随机报错:输入数据非完整块问题求助
解决C# TCP传输加密大文件时"The input data is not a complete block"错误
问题描述
编写C#服务器-客户端P2P文件共享代码时,遇到随机出现的The input data is not a complete block错误:9MB左右的文件可正常传输,但40MB的文件仅下载约11MB就抛出该错误。文件被分割为1MB块,加密后发送,接收端解密并追加到文件。尝试修改填充方式、更换AES模式均无效。
错误核心原因
问题出在TCP流式传输的特性和代码中错误的读取逻辑:
- TCP是流式协议,
NetworkStream.Read()仅返回当前缓冲区中可用的字节数,无法保证一次性读取到你请求的全部字节(比如IV的16字节、1MB的加密块)。大文件传输时,网络延迟、缓冲区限制会导致读取不完整,后续读取的字节混入下一个数据块,最终导致解密时数据块长度不符合AES的16字节块要求,触发错误。 - 加密后的块长度可能因填充发生变化(最后一块不足1MB时),客户端固定用1MB数组读取,会导致读取到多余字节或不足的字节,破坏解密的块结构。
解决方案
1. 实现强制完整读取的工具方法
封装方法循环调用Read,直到读取到指定长度的字节,确保数据完整性:
private static int ReadExactly(NetworkStream stream, byte[] buffer, int offset, int count) { int totalRead = 0; while (totalRead < count) { int read = stream.Read(buffer, offset + totalRead, count - totalRead); if (read == 0) throw new EndOfStreamException("连接已关闭,未读取到全部数据"); totalRead += read; } return totalRead; }
2. 修改服务器发送逻辑:传输块长度
发送加密块前,先发送块的字节长度(用4字节int表示),让客户端明确知道需要读取多少字节:
// 原服务器sendtoClient方法中,发送IV和加密块的部分修改为: networkStream.Write(iv, 0, iv.Length); // 发送加密块长度 byte[] chunkLengthBytes = BitConverter.GetBytes(encryptedChunk.Length); networkStream.Write(chunkLengthBytes, 0, chunkLengthBytes.Length); // 发送加密块 networkStream.Write(encryptedChunk, 0, encryptedChunk.Length);
同时修正文件信息的发送逻辑,避免文件名过长导致截断:
// 原服务器sendtoClient方法中,发送文件信息的部分修改为: string fileInfo = getFileString() + "," + totalChunks + ","; byte[] fileInfoBytes = Encoding.UTF8.GetBytes(fileInfo); // 先发送文件信息长度 byte[] infoLengthBytes = BitConverter.GetBytes(fileInfoBytes.Length); networkStream.Write(infoLengthBytes, 0, infoLengthBytes.Length); // 再发送文件信息内容 networkStream.Write(fileInfoBytes, 0, fileInfoBytes.Length);
3. 修改客户端接收逻辑:按长度读取
客户端先读取块长度,再读取对应长度的加密数据,确保读取的字节数和发送的完全一致:
// 原客户端recive方法中,循环读取块的部分修改为: for (int chunkIndex = 0; chunkIndex < ccount; chunkIndex++) { byte[] iv = new byte[16]; // 完整读取IV ReadExactly(networkStream, iv, 0, iv.Length); aes.IV = iv; // 读取加密块长度 byte[] chunkLengthBytes = new byte[4]; ReadExactly(networkStream, chunkLengthBytes, 0, 4); int encryptedChunkLength = BitConverter.ToInt32(chunkLengthBytes, 0); // 读取对应长度的加密块 byte[] encryptedChunk = new byte[encryptedChunkLength]; ReadExactly(networkStream, encryptedChunk, 0, encryptedChunkLength); // 后续解密逻辑不变 using (ICryptoTransform decryptor = aes.CreateDecryptor()) using (MemoryStream decryptedStream = new MemoryStream()) using (CryptoStream cryptoStream = new CryptoStream(decryptedStream, decryptor, CryptoStreamMode.Write)) { cryptoStream.Write(encryptedChunk, 0, encryptedChunk.Length); cryptoStream.FlushFinalBlock(); byte[] decryptedChunk = decryptedStream.ToArray(); AppendAllBytes(filepath, decryptedChunk); Console.WriteLine($"Client: Decrypted chunk {chunkIndex + 1} received."); } }
同时修正文件信息的读取逻辑:
// 原客户端recive方法中,读取文件信息的部分修改为: // 读取文件信息长度 byte[] infoLengthBytes = new byte[4]; ReadExactly(networkStream, infoLengthBytes, 0, 4); int infoLength = BitConverter.ToInt32(infoLengthBytes, 0); // 读取文件信息内容 byte[] fileInfoBytes = new byte[infoLength]; ReadExactly(networkStream, fileInfoBytes, 0, infoLength); string fileInfo = Encoding.UTF8.GetString(fileInfoBytes);
4. 优化大文件内存占用(可选)
避免用File.ReadAllBytes一次性将大文件加载到内存,改用FileStream分块读取:
// 服务器sendtoClient方法中,替换File.ReadAllBytes的部分: using (FileStream fs = new FileStream(filepath, FileMode.Open, FileAccess.Read)) { int totalChunks = (int)Math.Ceiling((double)fs.Length / chunkSize); // 发送文件信息... using (Aes aes = new AesCryptoServiceProvider()) { aes.KeySize = 256; aes.Key = key; aes.BlockSize = 128; aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.Zeros; byte[] chunk = new byte[chunkSize]; int bytesRead; int chunkIndex = 0; while ((bytesRead = fs.Read(chunk, 0, chunk.Length)) > 0) { aes.GenerateIV(); byte[] iv = aes.IV; // 加密当前块 using (ICryptoTransform encryptor = aes.CreateEncryptor()) using (MemoryStream encryptedStream = new MemoryStream()) using (CryptoStream cryptoStream = new CryptoStream(encryptedStream, encryptor, CryptoStreamMode.Write)) { cryptoStream.Write(chunk, 0, bytesRead); cryptoStream.FlushFinalBlock(); byte[] encryptedChunk = encryptedStream.ToArray(); // 发送IV、块长度、加密块 networkStream.Write(iv, 0, iv.Length); byte[] chunkLengthBytes = BitConverter.GetBytes(encryptedChunk.Length); networkStream.Write(chunkLengthBytes, 0, chunkLengthBytes.Length); networkStream.Write(encryptedChunk, 0, encryptedChunk.Length); } chunkIndex++; } } }
内容的提问来源于stack exchange,提问作者neke ceke
相关产品推荐
相关产品推荐

