NetworkStream 只有在发送 NetworkStream 关​​闭或处置后才能读取数据

问题描述

我正在编写一个用于发送和接收 AES 加密文件的应用程序。我有两个功能一个用于发送:

public async Task SendFileAsync()
{
    var buffer = new byte[1024];

    using (Aes aesAlg = Aes.Create())
    {
        // tcpHandler.stream is a NetworkStream
        using (ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key,aesAlg.IV))
        {
            using (Stream fileStream = await selectedFile.OpenStreamForReadAsync())
            {
                using (CryptoStream csEncrypt = new CryptoStream(tcpHandler.stream,encryptor,CryptoStreamMode.Write,true))
                {
                    while (stream.Position < selectedFileSize)
                    {
                        int NowRead = fileStream.Read(buffer,buffer.Length); // read bytes from file
                        csEncrypt.Write(buffer,NowRead); // write bytes to CryptoStream (which writes to NetworkStream)
                    }
                }
            }
        }
    }
    await tcpHandler.stream.FlushAsync()
}

还有一个用于接收:

public async Task ReceiveFileAsync()
{
    var buffer = new byte[1024];
    BinaryFormatter formatter = new BinaryFormatter();
    int messageLength = tcpHandler.ReadMessageLength();
    int totalBytesRead = 0;

    using (Aes aesAlg = Aes.Create())
    {
        // tcpHandler.stream is a NetworkStream
        using (ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key,aesAlg.IV))
        {
            using (var fileStream = await newFile.OpenStreamForWriteAsync())
            {
                using (CryptoStream csDecrypt = new CryptoStream(tcpHandler.stream,decryptor,CryptoStreamMode.Read,true)) 
                {
                    while (totalBytesRead < messageLength)
                    {
                        // calculate how many bytes have to be read in this iteration
                        var toRead = Math.Min(buffer.Length,messageLength - totalBytesRead);
                        var NowRead = csDecrypt.Read(buffer,toRead); // read bytes from CryptoStream
                        totalBytesRead += NowRead; // sum read bytes
                        fileStream.Write(buffer,NowRead); // write decrypted bytes to file
                    }
                }
            }
        }
    }
}

问题是 ReceiveFileAsync() 在最后一个 csDecrypt.Read(buffer,toRead) 上阻塞了自己,就好像 csDecrypt 流中没有足够的数据一样。但是,当我关闭(终止进程)发送应用程序时,接收应用程序正确接收到最后一个缓冲区。

当我将 using (CryptoStream csEncrypt = new CryptoStream(tcpHandler.stream,true)) 的最后一个参数更改为 false 时会发生同样的事情 - 它使 CryptoStream 在被处理时关闭基本流 (tcpHandler.stream)。

如果我在 tcpHandler.stream.Close() 末尾执行 SendFileAsync(),它也有帮助。

简而言之,在我关闭发送 NetworkStream (tcpHandler.stream) 之前,我发送的最后一个缓冲区不会被接收到,无论是通过关闭/处置它还是关闭应用程序。

我尝试将 await tcpHandler.stream.FlushAsync() 添加SendFileAsync() 的最后一行,但没有帮助。有什么想法我应该怎么做才能解决这个问题?

编辑:使用嵌套的 using 语句更新代码

解决方法

我测试的 .NET 5.0 异步控制台应用程序带有一个 5 字节的文本文件,其中包含一些睡眠以保持进程处于活动状态。

发件人应用要点:

  1. NetworkStream.FlushAsync 或 (NetworkStream.Flush)
  2. CryptoStream.FlushFinalBlock
  3. NetworkStream.Close
  4. TcpClient.Close(同时关闭 NetworkStream)
  5. leaveOpen 构造函数上的
  6. CryptoStream 参数(true -> 使 NetworkStream 保持打开状态,false -> 在 NetworkStream 上调用 Close)

接收器应用要点:

  1. leaveOpen 构造函数上的 CryptoStream 参数(这里没什么意思),

边注:

  • Flush 使缓冲区中的所有数据都写入底层流。它不会关闭流。
  • Close 显然会关闭流
  • CryptoStream 会在流关闭时自动管理最终密码块本身,因此需要使用 Flushing 按预期执行此操作。

几乎你自己已经有了答案:

您必须关闭 NetworkStream 才能让接收者能够接收到它。有多种方法可以按照所述进行操作。

冲洗不会关闭流,因此它不会工作。

此行为不依赖于 CryptoStream。我也尝试过直接写入 NetworkStream 而不使用加密 -> 相同的结果。 但是,您可以让 tcp 客户端保持打开状态,这意味着您仍将拥有活动连接。 所以就像你发现的和我测试的一样:

  • 1)、2) 和 5)true 将不起作用(网络流将被关闭)
  • 3)、4) 和 5)false 将起作用(网络流将保持打开状态)

这是我的测试代码(几乎相同但仍然决定发布):

  1. 客户:

     class Program
     {
         static byte[] key = { 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x10,0x11,0x12,0x13,0x14,0x15,0x16 };
         static byte[] iv = { 0x01,0x16 };
    
         static async Task Main(string[] args)
         {
             try
             {
                 TcpClient tcpClient = new TcpClient();
                 tcpClient.Connect("127.0.0.1",8001);
                 Stream stream = tcpClient.GetStream(); // NetworkStream
    
                 await SendFileAsync(stream,"test.txt");
    
                 //await stream.FlushAsync();
    
                 //stream.Close();
    
                 //tcpClient.Close();
                 Thread.Sleep(10000);
             }
    
             catch (Exception e)
             {
                 Console.WriteLine("Error..... " + e.Message);
             }
         }
    
         public static async Task SendFileAsync(Stream stream,string filePath)
         {
             var buffer = new byte[1024];
    
             using (Aes aesAlg = Aes.Create())
             using (ICryptoTransform encryptor = aesAlg.CreateEncryptor(key,iv))//aesAlg.Key,aesAlg.IV
             {
                 using (FileStream fs = new FileStream(filePath,FileMode.Open))
                 using (BinaryReader br = new BinaryReader(fs))
                 {
                     using (CryptoStream csEncrypt = new CryptoStream(stream,encryptor,CryptoStreamMode.Write,false))
                     {
                         int bytesRead;
                         while ((bytesRead = br.Read(buffer,buffer.Length)) > 0)
                         {
                             csEncrypt.Write(buffer,bytesRead);
                             //csEncrypt.FlushFinalBlock();
                             break;
                         }
                     }
                 }
             }
         }
     }
    
  2. 服务器:

     class Program
     {
         static byte[] key = { 0x01,0x16 };
    
         public static async Task Main(string[] args)
         {
             try
             {
                 IPAddress ipAd = IPAddress.Parse("127.0.0.1");
                 TcpListener tcpServer = new TcpListener(ipAd,8001);
                 tcpServer.Start();
                 var tcpClient = await tcpServer.AcceptTcpClientAsync();
    
                 await ReceiveFileAsync(tcpClient,"text.txt");
    
                 tcpServer.Stop();
             }
             catch (Exception e)
             {
                 Console.WriteLine("Error..... " + e.Message);
             }
         }
    
         public static async Task ReceiveFileAsync(TcpClient tcpClient,string filePath)
         {
             var buffer = new byte[1024];
             var stream = tcpClient.GetStream();
    
             using (Aes aesAlg = Aes.Create())
             using (ICryptoTransform decryptor = aesAlg.CreateDecryptor(key,iv))
             {
                 using (FileStream fs = new FileStream(filePath,FileMode.Create))
                 using (BinaryWriter br = new BinaryWriter(fs))
                 {
                     using (CryptoStream csDecrypt = new CryptoStream(stream,decryptor,CryptoStreamMode.Read,true))
                     {
                         int bytesRead;
                         while ((bytesRead = csDecrypt.Read(buffer,buffer.Length)) > 0)
                         {
                             br.Write(buffer,bytesRead);
                         }
                     }
                 }
             }
         }
     }