Frage

Derzeit gibt es keine NetworkStream.Peek Methode in C#. Was ist der beste Weg, um eine solche Methode zu implementieren, die genau funktioniert wie NetworkStream.ReadByte außer dass die zurückgekehrt sind byte wird nicht eigentlich aus dem entfernt Stream?

War es hilfreich?

Lösung

Wenn Sie das Byte nicht tatsächlich abrufen müssen, können Sie sich auf die beziehen DataAvailable Eigentum.

Andernfalls können Sie es mit einem einwickeln StreamReader und rufe seine auf Peek Methode.

Beachten Sie, dass keiner von beiden für das Lesen aus einem Netzwerkstrom aufgrund von Latenzproblemen besonders zuverlässig ist. Die Daten könnten im Moment verfügbar sein (im Lesepuffer vorhanden) nach Du blickst.

Ich bin mir nicht sicher, was Sie damit tun möchten, aber die Read Methode auf NetworkStream Ist ein Blockierungsanruf, sodass Sie nicht wirklich nach Status suchen müssen, auch wenn Sie in Stücken erhalten. Wenn Sie versuchen, die Anwendung beim Lesen aus dem Stream reaktionsschnell zu halten, sollten Sie einen Thread oder einen asynchronen Anruf verwenden, um stattdessen die Daten zu empfangen.

Bearbeiten: Nach Angaben dieser Beitrag, StreamReader.Peek ist fehlerhaft auf einem NetworkStream, oder zumindest ohne Papiere Verhalten. Seien Sie also vorsichtig, wenn Sie sich für diesen Weg entscheiden.


Aktualisiert - Antwort auf Kommentare

Der Begriff eines "Blicks" auf den tatsächlichen Strom selbst ist eigentlich unmöglich; Es ist nur ein Stream, und sobald das Byte empfangen wird, ist es nicht mehr im Strom. Einige Streams unterstützen die Suche, damit Sie dieses Byte technisch erneut lesen können, aber NetworkStream ist nicht einer von ihnen.

Das Spitzen gilt nur, wenn der Stream in einen Puffer gelesen wird. Sobald sich die Daten in einem Puffer befinden, ist das Spitzen einfach, da Sie einfach die aktuelle Position im Puffer überprüfen. Deshalb a StreamReader ist in der Lage, dies zu tun; nein Stream Die Klasse wird im Allgemeinen seine eigenen haben Peek Methode.

Für dieses Problem frage ich mich jetzt, ob dies wirklich die richtige Antwort ist oder nicht. Ich verstehe die Idee, eine Methode für die Verarbeitung des Streams dynamisch auszuwählen, aber tun Sie es Ja wirklich Müssen Sie dies im Rohstrom tun? Können Sie den Stream nicht zuerst in ein Byte -Array lesen oder ihn sogar in a kopieren? MemoryStream, und verarbeiten es von diesem Zeitpunkt an?

Das Hauptproblem, das ich sehe, ist, dass die Daten verschwunden sind, wenn etwas Schlimmes passiert, wenn Sie aus einem Netzwerkstrom lesen. Wenn Sie es jedoch zuerst an einem vorübergehenden Ort lesen, können Sie dies debuggen. Sie können herausfinden, was die Daten waren und warum das Objekt, das versuchte, die Daten zu verarbeiten, auf halbem Weg fehlgeschlagen sind.

Im Allgemeinen das allererste, was Sie mit einem machen möchten NetworkStream wird es in einen lokalen Puffer gelesen. Der einzige Grund, warum ich mir vorstellen kann, dies nicht zu tun, ist, wenn Sie eine enorme Datenmenge lesen - und selbst dann denke ich in Betracht, das Dateisystem als Zwischenpuffer zu verwenden, wenn es nicht in den Speicher passt.

Ich kenne Ihre genauen Anforderungen nicht, aber nach dem, was ich bisher gelernt habe, wäre mein Rat: Versuchen Sie nicht, Ihre Daten direkt aus dem zu verarbeiten NetworkStream Es sei denn, es gibt einen überzeugenden Grund dafür. Erwägen Sie zuerst die Daten in den Speicher oder auf die Festplatte zu lesen und dann die Kopie zu verarbeiten.

Andere Tipps

Ich stieß auf den gleichen Blick auf die magische Nummer und entschied dann, welcher Stream -Prozessor den Stream an die Anforderung senden soll, und kann mich leider nicht aus diesem Problem herausgeben - wie in Kommentaren zu Aaronaughs Antwort vorgeschlagen -, indem ich die bereits verbrauchten Bytes bestanden habe In die Stream -Verarbeitungsmethoden in getrennten Parametern, wie diese Methoden selbstverständlich sind und sie erwarten, dass System.io.stream und sonst nichts.

Ich habe dies gelöst, indem ich ein mehr oder weniger universell erstellte Peekablestream Klasse, der einen Stream wickelt. Es funktioniert für Netzwerkestreams, aber auch für jeden anderen Stream, vorausgesetzt, Sie Stream.canread es.


Bearbeiten

Alternativ können Sie das brandneue Gebrauch verwenden ReadSeekableStream und TU

var readSeekableStream = new ReadSeekableStream(networkStream, /* >= */ count);
...
readSeekableStream.Read(..., count);
readSeekableStream.Seek(-count, SeekOrigin.Current);

In jedem Fall kommt hier PeekableStream:

/// <summary>
/// PeekableStream wraps a Stream and can be used to peek ahead in the underlying stream,
/// without consuming the bytes. In other words, doing Peek() will allow you to look ahead in the stream,
/// but it won't affect the result of subsequent Read() calls.
/// 
/// This is sometimes necessary, e.g. for peeking at the magic number of a stream of bytes and decide which
/// stream processor to hand over the stream.
/// </summary>
public class PeekableStream : Stream
{
    private readonly Stream underlyingStream;
    private readonly byte[] lookAheadBuffer;

    private int lookAheadIndex;

    public PeekableStream(Stream underlyingStream, int maxPeekBytes)
    {
        this.underlyingStream = underlyingStream;
        lookAheadBuffer = new byte[maxPeekBytes];
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
            underlyingStream.Dispose();

        base.Dispose(disposing);
    }

    /// <summary>
    /// Peeks at a maximum of count bytes, or less if the stream ends before that number of bytes can be read.
    /// 
    /// Calls to this method do not influence subsequent calls to Read() and Peek().
    /// 
    /// Please note that this method will always peek count bytes unless the end of the stream is reached before that - in contrast to the Read()
    /// method, which might read less than count bytes, even though the end of the stream has not been reached.
    /// </summary>
    /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and
    /// (offset + number-of-peeked-bytes - 1) replaced by the bytes peeked from the current source.</param>
    /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data peeked from the current stream.</param>
    /// <param name="count">The maximum number of bytes to be peeked from the current stream.</param>
    /// <returns>The total number of bytes peeked into the buffer. If it is less than the number of bytes requested then the end of the stream has been reached.</returns>
    public virtual int Peek(byte[] buffer, int offset, int count)
    {
        if (count > lookAheadBuffer.Length)
            throw new ArgumentOutOfRangeException("count", "must be smaller than peekable size, which is " + lookAheadBuffer.Length);

        while (lookAheadIndex < count)
        {
            int bytesRead = underlyingStream.Read(lookAheadBuffer, lookAheadIndex, count - lookAheadIndex);

            if (bytesRead == 0) // end of stream reached
                break;

            lookAheadIndex += bytesRead;
        }

        int peeked = Math.Min(count, lookAheadIndex);
        Array.Copy(lookAheadBuffer, 0, buffer, offset, peeked);
        return peeked;
    }

    public override bool CanRead { get { return true; } }

    public override long Position
    {
        get
        {
            return underlyingStream.Position - lookAheadIndex;
        }
        set
        {
            underlyingStream.Position = value;
            lookAheadIndex = 0; // this needs to be done AFTER the call to underlyingStream.Position, as that might throw NotSupportedException, 
                                // in which case we don't want to change the lookAhead status
        }
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        int bytesTakenFromLookAheadBuffer = 0;
        if (count > 0 && lookAheadIndex > 0)
        {
            bytesTakenFromLookAheadBuffer = Math.Min(count, lookAheadIndex);
            Array.Copy(lookAheadBuffer, 0, buffer, offset, bytesTakenFromLookAheadBuffer);
            count -= bytesTakenFromLookAheadBuffer;
            offset += bytesTakenFromLookAheadBuffer;
            lookAheadIndex -= bytesTakenFromLookAheadBuffer;
            if (lookAheadIndex > 0) // move remaining bytes in lookAheadBuffer to front
                // copying into same array should be fine, according to http://msdn.microsoft.com/en-us/library/z50k9bft(v=VS.90).aspx :
                // "If sourceArray and destinationArray overlap, this method behaves as if the original values of sourceArray were preserved
                // in a temporary location before destinationArray is overwritten."
                Array.Copy(lookAheadBuffer, lookAheadBuffer.Length - bytesTakenFromLookAheadBuffer + 1, lookAheadBuffer, 0, lookAheadIndex);
        }

        return count > 0
            ? bytesTakenFromLookAheadBuffer + underlyingStream.Read(buffer, offset, count)
            : bytesTakenFromLookAheadBuffer;
    }

    public override int ReadByte()
    {
        if (lookAheadIndex > 0)
        {
            lookAheadIndex--;
            byte firstByte = lookAheadBuffer[0];
            if (lookAheadIndex > 0) // move remaining bytes in lookAheadBuffer to front
                Array.Copy(lookAheadBuffer, 1, lookAheadBuffer, 0, lookAheadIndex);
            return firstByte;
        }
        else
        {
            return underlyingStream.ReadByte();
        }
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        long ret = underlyingStream.Seek(offset, origin);
        lookAheadIndex = 0; // this needs to be done AFTER the call to underlyingStream.Seek(), as that might throw NotSupportedException,
                            // in which case we don't want to change the lookAhead status
        return ret;
    }

    // from here on, only simple delegations to underlyingStream

    public override bool CanSeek { get { return underlyingStream.CanSeek; } }
    public override bool CanWrite { get { return underlyingStream.CanWrite; } }
    public override bool CanTimeout { get { return underlyingStream.CanTimeout; } }
    public override int ReadTimeout { get { return underlyingStream.ReadTimeout; } set { underlyingStream.ReadTimeout = value; } }
    public override int WriteTimeout { get { return underlyingStream.WriteTimeout; } set { underlyingStream.WriteTimeout = value; } }
    public override void Flush() { underlyingStream.Flush(); }
    public override long Length { get { return underlyingStream.Length; } }
    public override void SetLength(long value) { underlyingStream.SetLength(value); }
    public override void Write(byte[] buffer, int offset, int count) { underlyingStream.Write(buffer, offset, count); }
    public override void WriteByte(byte value) { underlyingStream.WriteByte(value); }
}

Wenn Sie Zugriff auf die Socket Objekt, Sie könnten die ausprobieren Empfangsmethode, Vorbeigehen SocketFlags.Peek. Dies ist analog zu dem MSG_PEEK Flagge, die an die übergeben werden kann recv Rufen Sie BSD -Steckdosen oder Winsock an.

Hier ist sehr einfach PeekStream Implementierung, mit der Sie zu Beginn des Streams eine bestimmte Anzahl von Bytes nur zu sehen sind (im Gegensatz zu jederzeit in der Lage, einen Blick darauf zu werfen). Die angespiechten Bytes werden als zurückgegeben Stream selbst, um Änderungen am vorhandenen Code zu minimieren.

So verwenden Sie es:

Stream nonSeekableStream = ...;
PeekStream peekStream = new PeekStream(nonSeekableStream, 30); // Peek max 30 bytes
Stream initialBytesStream = peekStream.GetInitialBytesStream();
ParseHeaders(initialBytesStream);  // Work on initial bytes of nonSeekableStream
peekStream.Read(...) // Read normally, the read will start from the beginning

GetInitialBytesStream() Gibt einen suchbaren Stream zurück, der bis zu peekSize Anfangs Bytes des zugrunde liegenden Streams (weniger, wenn der Strom kürzer ist als peekSize).

Aufgrund seiner Einfachheit sollte das Lesen von Peekstream nur geringfügig langsamer sein (wenn überhaupt) als das Lesen des zugrunde liegenden Streams direkt.

public class PeekStream : Stream
{
    private Stream m_stream;
    private byte[] m_buffer;
    private int m_start;
    private int m_end;

    public PeekStream(Stream stream, int peekSize)
    {
        if (stream == null)
        {
            throw new ArgumentNullException("stream");
        }
        if (!stream.CanRead)
        {
            throw new ArgumentException("Stream is not readable.");
        }
        if (peekSize < 0)
        {
            throw new ArgumentOutOfRangeException("peekSize");
        }
        m_stream = stream;
        m_buffer = new byte[peekSize];
        m_end = stream.Read(m_buffer, 0, peekSize);
    }

    public override bool CanRead
    {
        get
        {
            return true;
        }
    }

    public override bool CanWrite
    {
        get
        {
            return false;
        }
    }

    public override bool CanSeek
    {
        get
        {
            return false;
        }
    }

    public override long Length
    {
        get
        {
            throw new NotSupportedException();
        }
    }

    public override long Position
    {
        get
        {
            throw new NotSupportedException();
        }
        set
        {
            throw new NotSupportedException();
        }
    }

    public MemoryStream GetInitialBytesStream()
    {
        return new MemoryStream(m_buffer, 0, m_end, false);
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        throw new NotSupportedException();
    }

    public override void SetLength(long value)
    {
        throw new NotSupportedException();
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        // Validate arguments
        if (buffer == null)
        {
            throw new ArgumentNullException("buffer");
        }
        if (offset < 0)
        {
            throw new ArgumentOutOfRangeException("offset");
        }
        if (offset + count > buffer.Length)
        {
            throw new ArgumentOutOfRangeException("count");
        }

        int totalRead = 0;

        // Read from buffer
        if (m_start < m_end)
        {
            int toRead = Math.Min(m_end - m_start, count);
            Array.Copy(m_buffer, m_start, buffer, offset, toRead);
            m_start += toRead;
            offset += toRead;
            count -= toRead;
            totalRead += toRead;
        }

        // Read from stream
        if (count > 0)
        {
            totalRead += m_stream.Read(buffer, offset, count);
        }

        // Return total bytes read
        return totalRead;
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        throw new NotImplementedException();
    }

    public override int ReadByte()
    {
        if (m_start < m_end)
        {
            return m_buffer[m_start++];
        }
        else
        {
            return m_stream.ReadByte();
        }
    }

    public override void Flush()
    {
        m_stream.Flush();
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            m_stream.Dispose();
        }
        base.Dispose(disposing);
    }
}

Haftungsausschluss: Peekstream oben stammt aus einem Arbeitsprogramm, wird jedoch nicht umfassend getestet und kann also Fehler enthalten. Es funktioniert bei mir, aber Sie könnten einige Eckfälle aufdecken, in denen es ausfällt.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top