You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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流式传输的特性和代码中错误的读取逻辑:

  1. TCP是流式协议,NetworkStream.Read()仅返回当前缓冲区中可用的字节数,无法保证一次性读取到你请求的全部字节(比如IV的16字节、1MB的加密块)。大文件传输时,网络延迟、缓冲区限制会导致读取不完整,后续读取的字节混入下一个数据块,最终导致解密时数据块长度不符合AES的16字节块要求,触发错误。
  2. 加密后的块长度可能因填充发生变化(最后一块不足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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 00:07:32