Delen via


Gedeeltelijke en nul-byte-leesbewerkingen in DeflateStream, GZipStream en CryptoStream

De Read() en ReadAsync() methoden op DeflateStream, GZipStreamen CryptoStream retourneren mogelijk niet meer zoveel bytes als is aangevraagd.

DeflateStreamVoorheen, GZipStreamen CryptoStream afwijkend van typisch Stream.Read gedrag en Stream.ReadAsync gedrag op de volgende twee manieren, waarbij beide van deze wijzigingsadressen worden gebruikt:

  • Ze hebben de leesbewerking pas voltooid nadat de buffer die aan de leesbewerking is doorgegeven, volledig is gevuld of het einde van de stream is bereikt.
  • Als wrapperstreams hebben ze geen bufferfunctionaliteit met lengte nul gedelegeerd aan de stroom die ze verpakken.

Bekijk dit voorbeeld waarmee 150 willekeurige bytes worden gemaakt en gecomprimeerd. Vervolgens worden de gecomprimeerde gegevens één byte tegelijk van de client naar de server verzonden en de server decomprimeert de gegevens door alle 150 bytes aan te roepen Read en aan te vragen.

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;
            }
        }
    }
}

In eerdere versies van .NET en .NET Framework wordt Read in de volgende uitvoer slechts één keer aangeroepen. Hoewel er gegevens beschikbaar waren om GZipStream te retourneren, Read moesten ze wachten totdat het aangevraagde aantal bytes beschikbaar was.

Read: 150 bytes
Total received: 150 bytes

In .NET 6 en latere versies ziet u in de volgende uitvoer dat Read meerdere keren is aangeroepen totdat alle aangevraagde gegevens zijn ontvangen. Hoewel de aanroep om 150 bytes aanvraagtRead, kon elke aanroep om Read een aantal bytes (dat wil zeggen, alle bytes die op dat moment zijn ontvangen) te decomprimeren en wel:

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

Oud gedrag

Wanneer Stream.Read of Stream.ReadAsync werd aangeroepen op een van de betrokken stroomtypen met een buffer van lengte N, zou de bewerking pas worden voltooid als:

  • N bytes zijn gelezen uit de stream of
  • De onderliggende stroom heeft 0 geretourneerd van een aanroep naar de leesbewerking, wat aangeeft dat er geen gegevens meer beschikbaar waren.

Wanneer Stream.Read of Stream.ReadAsync werd aangeroepen met een buffer van lengte 0, zou de bewerking ook onmiddellijk slagen, soms zonder een gelezen met de lengte nul op de stroom te laten teruglopen.

Nieuw gedrag

Vanaf .NET 6, wanneer Stream.Read of Stream.ReadAsync wordt aangeroepen op een van de betrokken stroomtypen met een buffer van lengte N, wordt de bewerking voltooid wanneer:

  • Ten minste 1 byte is gelezen uit de stream, of
  • De onderliggende stroom retourneert 0 van een aanroep naar de leesbewerking, wat aangeeft dat er geen gegevens meer beschikbaar zijn.

Stream.Read Wanneer of Stream.ReadAsync wordt aangeroepen met een buffer van lengte 0, slaagt de bewerking ook zodra een aanroep met een niet-nulbuffer zou slagen.

Wanneer u een van de betrokken Read methoden aanroept, als de leesbewerking aan ten minste één byte van de aanvraag kan voldoen, ongeacht hoeveel er zijn aangevraagd, wordt er op dat moment zoveel geretourneerd als op dat moment.

Versie geïntroduceerd

6,0

Reden voor wijziging

De streams zijn mogelijk niet geretourneerd door een leesbewerking, zelfs niet als gegevens zijn gelezen. Dit betekende dat ze niet gemakkelijk konden worden gebruikt in een bidirectionele communicatiesituatie waarbij berichten die kleiner zijn dan de buffergrootte werden gebruikt. Dit kan leiden tot impasses: de toepassing kan de gegevens niet lezen uit de stroom die nodig is om de bewerking voort te zetten. Het kan ook leiden tot willekeurige vertragingen, waarbij de consument geen beschikbare gegevens kan verwerken terwijl wordt gewacht tot er meer gegevens binnenkomen.

In zeer schaalbare toepassingen is het ook gebruikelijk om zero-byte-leesbewerkingen te gebruiken als een manier om de toewijzing van buffers te vertragen totdat een buffer nodig is. Een toepassing kan een leesbewerking met een lege buffer uitgeven en wanneer deze leesbewerking is voltooid, moeten gegevens binnenkort beschikbaar zijn om te gebruiken. De toepassing kan vervolgens de leesbewerking opnieuw uitvoeren, deze keer met een buffer om de gegevens te ontvangen. Door het delegeren aan de verpakte stroom als er nog geen gedecomprimeerde of getransformeerde gegevens beschikbaar zijn, nemen deze stromen nu dergelijk gedrag over van de stromen die ze verpakken.

In het algemeen moet code het volgende doen:

  • Maak geen veronderstellingen over een stroom Read of ReadAsync bewerking die net zo veel leest als is aangevraagd. De aanroep retourneert het aantal bytes dat is gelezen, wat mogelijk kleiner is dan wat is aangevraagd. Als een toepassing afhankelijk is van de buffer die volledig is gevuld voordat de voortgang wordt uitgevoerd, kan deze de leesbewerking in een lus uitvoeren om het gedrag te herstellen.

    int totalRead = 0;
    while (totalRead < buffer.Length)
    {
        int bytesRead = stream.Read(buffer.AsSpan(totalRead));
        if (bytesRead == 0) break;
        totalRead += bytesRead;
    }
    
  • Verwacht dat een stream Read of ReadAsync aanroep mogelijk pas is voltooid als er ten minste een byte aan gegevens beschikbaar is voor verbruik (of dat de stream het einde bereikt), ongeacht het aantal bytes dat is aangevraagd. Als een toepassing afhankelijk is van een leesbewerking met nul-byte onmiddellijk zonder te wachten, kan deze de bufferlengte zelf controleren en de aanroep volledig overslaan:

    int bytesRead = 0;
    if (!buffer.IsEmpty)
    {
        bytesRead = stream.Read(buffer);
    }
    

Betrokken API's

Zie ook