DeflateStream、GZipStream 和 CryptoStream 中的部分和零位元組讀取
Read()
、 GZipStream和 上的DeflateStream和 ReadAsync()
CryptoStream 方法可能不再傳回所要求的位元元組數目。
先前、DeflateStreamGZipStream、 和 CryptoStream 會以下列兩種方式與一般Stream.Read和Stream.ReadAsync行為不同,這兩種變更都會解決:
- 在傳遞至讀取作業的緩衝區已完全填滿,或到達串流結尾後,才會完成讀取作業。
- 若是包裝函式串流,不會將長度為零的緩衝區功能委派給所包裝的串流。
假設這個範例會建立和壓縮 150 個隨機位元組。 然後,它會一次將壓縮的數據從用戶端傳送到伺服器,伺服器會呼叫 Read
並要求全部 150 個字節來解壓縮數據。
using System.IO.Compression;
using System.Net;
using System.Net.Sockets;
internal class Program
{
private static async Task Main()
{
// Connect two sockets and wrap a stream around each.
using (Socket listener = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
using (Socket client = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
listener.Listen(int.MaxValue);
client.Connect(listener.LocalEndPoint!);
using (Socket server = listener.Accept())
{
var clientStream = new NetworkStream(client, ownsSocket: true);
var serverStream = new NetworkStream(server, ownsSocket: true);
// Create some compressed data.
var compressedData = new MemoryStream();
using (var gz = new GZipStream(compressedData, CompressionLevel.Fastest, leaveOpen: true))
{
byte[] bytes = new byte[150];
new Random().NextBytes(bytes);
gz.Write(bytes, 0, bytes.Length);
}
// Trickle it from the client stream to the server.
Task sendTask = Task.Run(() =>
{
foreach (byte b in compressedData.ToArray())
{
clientStream.WriteByte(b);
}
clientStream.Dispose();
});
// Read and decompress all the sent bytes.
byte[] buffer = new byte[150];
int total = 0;
using (var gz = new GZipStream(serverStream, CompressionMode.Decompress))
{
int numRead = 0;
while ((numRead = gz.Read(buffer.AsSpan(numRead))) > 0)
{
total += numRead;
Console.WriteLine($"Read: {numRead} bytes");
}
}
Console.WriteLine($"Total received: {total} bytes");
await sendTask;
}
}
}
}
在舊版 .NET 和 .NET Framework 中,下列輸出顯示 Read
只呼叫一次。 即使數據可供 GZipStream
傳回, Read
仍被迫等到要求的位元組數目可用為止。
Read: 150 bytes
Total received: 150 bytes
在 .NET 6 和更新版本中,下列輸出會顯示 Read
呼叫多次,直到 收到所有 要求的數據為止。 即使呼叫 Read
要求 150 個字節,但 每個 對 的呼叫 Read
都能夠成功解壓縮 一些 位元組(也就是當時已收到的所有位元組)以傳回,而且它確實:
Read: 1 bytes
Read: 101 bytes
Read: 4 bytes
Read: 4 bytes
Read: 2 bytes
Read: 2 bytes
Read: 2 bytes
Read: 2 bytes
Read: 3 bytes
Read: 2 bytes
Read: 3 bytes
Read: 2 bytes
Read: 2 bytes
Read: 2 bytes
Read: 2 bytes
Read: 1 bytes
Read: 2 bytes
Read: 1 bytes
Read: 1 bytes
Read: 1 bytes
Read: 2 bytes
Read: 1 bytes
Read: 1 bytes
Read: 2 bytes
Read: 1 bytes
Read: 1 bytes
Read: 2 bytes
Total received: 150 bytes
舊的行為
在長度為緩衝區N
的其中一個受影響的數據流類型上呼叫 或 Stream.ReadAsync
時Stream.Read
,作業在下列情況下才會完成:
- 已從串流讀取
N
個位元組,或 - 基礎數據流從對讀取的呼叫傳回 0,表示沒有更多數據可供使用。
此外,以長度為 0 的緩衝區呼叫時 Stream.Read
或 Stream.ReadAsync
,作業會立即成功,有時不會對其包裝的串流執行長度為零的讀取。
新的行為
從 .NET 6 開始,在長度為 N
的其中一個受影響串流類型上呼叫 Stream.Read
或 Stream.ReadAsync
時,作業會在下列情況發生時完成:
- 已從數據流讀取至少 1 個字節,或
- 基礎數據流會從對讀取的呼叫傳回 0,表示沒有可用的數據。
此外,使用長度為0的緩衝區呼叫 或 Stream.ReadAsync
時Stream.Read
,一旦具有非零緩衝區的呼叫成功,作業就會成功。
當您呼叫其中一個受影響的 Read
方法時,如果讀取可以滿足至少一個字節的要求,無論要求多少, 它都會傳回當時的次數。
導入的版本
6.0
變更原因
即使已成功讀取資料,串流也可能尚未從讀取作業傳回。 這表示串流無法立即用於任何雙向通訊情境,因為訊息要小於所使用的緩衝區大小。 這可能會導致死結:應用程式無法從串流讀取繼續執行作業所需的資料。 這也可能導致任意速度變慢,取用者無法在等待更多數據送達時處理可用的數據。
此外,在擴充彈性高的應用程式中,通常會使用零位元組讀取的方式來延遲緩衝區配置,直到需要緩衝區為止。 應用程式可以發出緩衝區空白的讀取,而且當讀取完成時,資料應該很快就可供取用。 應用程式接著可以再次發出讀取,這次會使用緩衝區來接收資料。 如果沒有已經解壓縮或轉換的資料可用,這些串流現在可以透過委派到已包裝的串流,繼承其包裝之串流的任何這類行為。
建議的動作
一般而言,程式碼應該:
不假設串流
Read
或ReadAsync
作業盡可能依要求數目讀取。 呼叫會傳回讀取的位元組數目,可能小於所要求的位元元組數目。 如果應用程式相依的緩衝區在進行之前已完全填滿,可以在迴圈中執行讀取,以重新取得行為。int totalRead = 0; while (totalRead < buffer.Length) { int bytesRead = stream.Read(buffer.AsSpan(totalRead)); if (bytesRead == 0) break; totalRead += bytesRead; }
預期數據流
Read
或ReadAsync
呼叫可能尚未完成,直到至少有一位元組的數據可供取用(或數據流到達其結尾),不論要求多少個字節。 如果應用程式相依於立即完成且不等候的零位元組讀取,則可以自行檢查緩衝區長度,並完全略過呼叫:int bytesRead = 0; if (!buffer.IsEmpty) { bytesRead = stream.Read(buffer); }
受影響的 API
- System.IO.Compression.DeflateStream.Read
- System.IO.Compression.DeflateStream.ReadAsync
- System.IO.Compression.DeflateStream.BeginRead(Byte[], Int32, Int32, AsyncCallback, Object)
- System.IO.Compression.GZipStream.Read
- System.IO.Compression.GZipStream.ReadAsync
- System.IO.Compression.GZipStream.BeginRead(Byte[], Int32, Int32, AsyncCallback, Object)
- System.Security.Cryptography.CryptoStream.Read
- System.Security.Cryptography.CryptoStream.ReadAsync
- System.Security.Cryptography.CryptoStream.BeginRead(Byte[], Int32, Int32, AsyncCallback, Object)