DeflateStream, GZipStream 및 CryptoStream의 부분 및 0바이트 읽기
및 ReadAsync()
메서드가 DeflateStreamRead()
요청 GZipStreamCryptoStream 된 만큼 더 이상 바이트를 반환하지 않을 수 있습니다.
DeflateStreamGZipStreamCryptoStream 이전에는 다음과 같은 두 가지 방법으로 일반적인 Stream.Read 동작과 Stream.ReadAsync 다른 두 가지 방법으로 이 변경 사항을 해결했습니다.
- 읽기 작업에 전달된 버퍼가 완전히 채워지거나 스트림의 끝에 도달할 때까지 읽기 작업을 완료하지 않았습니다.
- 래퍼 스트림으로, 래핑하는 스트림에 길이가 0인 버퍼 기능을 위임하지 않았습니다.
150개의 임의 바이트를 만들고 압축하는 이 예제를 생각해 보세요. 그런 다음 클라이언트에서 서버로 한 번에 하나씩 압축된 데이터를 보내고, 서버는 150바이트를 모두 호출 Read
하고 요청하여 데이터를 압축 해제합니다.
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
. 데이터를 반환 Read
할 수 GZipStream
있었음에도 불구하고 요청된 바이트 수를 사용할 수 있게 될 때까지 기다려야 했습니다.
Read: 150 bytes
Total received: 150 bytes
.NET 6 이상 버전에서 다음 출력은 요청된 모든 데이터를 받을 때까지 여러 번 호출된 것을 Read
보여줍니다. 150바이트를 요청하는 호출 Read
에도 불구하고 각 호출은 반환할 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
가 호출되면 래핑되는 스트림에서 길이가 0인 읽기를 수행하지 않고 작업이 즉시 성공합니다.
새 동작
.NET 6부터 버퍼 길이가 N
인 영향을 받는 스트림 형식 중 하나에서 Stream.Read
또는 Stream.ReadAsync
가 호출된 경우 작업은 다음과 같은 경우에 완료됩니다.
- 스트림에서 1 바이트 이상을 읽었거나
- 기본 스트림은 더 이상 사용할 수 없는 데이터를 나타내는 읽기 호출에서 0을 반환합니다.
Stream.Read
또한 길이가 0인 버퍼로 호출되거나 Stream.ReadAsync
호출되면 0이 아닌 버퍼를 사용한 호출이 성공하면 작업이 성공합니다.
영향을 받는 Read
메서드 중 하나를 호출할 때 요청된 수에 관계없이 읽기가 요청의 바이트를 하나 이상 충족할 수 있는 경우 해당 시점에 가능한 한 많은 값이 반환됩니다.
도입된 버전
6.0
변경 이유
데이터를 성공적으로 읽은 경우에도 스트림이 읽기 작업에서 반환되지 않았을 수 있습니다. 즉, 버퍼 크기보다 작은 메시지가 사용되는 양방향 통신 상황에서는 쉽게 사용할 수 없습니다. 이로 인해 애플리케이션이 작업을 계속하는 데 필요한 스트림에서 데이터를 읽을 수 없어 교착 상태가 될 수 있습니다. 또한 소비자가 더 많은 데이터가 도착할 때까지 기다리는 동안 사용 가능한 데이터를 처리할 수 없으므로 임의로 느려질 수 있습니다.
또한 확장성이 뛰어난 애플리케이션에서는 버퍼 할당을 버퍼가 필요할 때까지 지연하는 방법으로 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
호출이 완료되지 않을 수 있습니다. 애플리케이션이 대기하지 않고 즉시 완료되는 0바이트 읽기에 의존하는 경우 버퍼 길이 자체를 확인하고 호출을 완전히 건너뛸 수 있습니다.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)
.NET