Domanda

Al momento, non c'è un metodo NetworkStream.Peek in C #. Qual è il modo migliore di attuare tale metodo, che funziona esattamente come NetworkStream.ReadByte tranne che il byte restituito non viene effettivamente rimosso dal Stream?

È stato utile?

Soluzione

Se non è necessario in realtà recuperare il byte, si può fare riferimento al DataAvailable proprietà.

In caso contrario, si può avvolgere con una StreamReader e invocare il suo metodo Peek.

Si noti che nessuna di queste sono particolarmente affidabile per la lettura da un flusso di rete, a causa di problemi di latenza. I dati potrebbe diventare disponibili (presenti nel buffer di lettura) nello stesso istante in dopo di sbirciare.

Non sono sicuro che cosa è che avete intenzione di fare con questo, ma il metodo Read su NetworkStream è un blocco delle chiamate, in modo che non si ha realmente bisogno di controllare lo status, anche se si ricevono in blocchi. Se si sta cercando di mantenere l'applicazione di rispondere durante la lettura dal flusso, è necessario utilizzare un filo o una chiamata asincrona per ricevere i dati, invece.

Modifica: Secondo questo post , StreamReader.Peek è bacato su un NetworkStream, o almeno ha un comportamento non documentato, quindi fate attenzione se si sceglie di seguire questa strada.


Aggiornamento - risposta ai commenti

L'idea di una "sbirciatina" sul torrente vero e proprio è in realtà impossibile; è solo un torrente, e una volta che il byte ricevuto, allora è più sul torrente. Alcuni flussi supportano cercando così si potrebbe tecnicamente rileggere quel byte, ma NetworkStream non è uno di loro.

Sbirciare applica solo quando sono lettura del flusso di in un buffer; una volta che i dati sono in un buffer poi sbirciare è facile, perché basta controllare tutto quello che è nella posizione corrente nel buffer. Ecco perché un StreamReader è in grado di farlo; nessuna classe Stream in genere hanno un proprio metodo Peek.

Ora, per questo problema specifico, mi chiedo se questo è davvero la risposta giusta. Capisco l'idea di selezionare dinamicamente un metodo per l'elaborazione del flusso, ma si fa a davvero bisogno di fare questo sul flusso crudo? Non è possibile leggere il flusso in un array di byte prima, o addirittura copiarlo in un MemoryStream, e di processo che da quel momento in?

Il problema principale che vedo è che se succede qualcosa di brutto, quando si sta leggendo da un flusso di rete, i dati sono andati. Ma se lo si legge in una posizione temporanea prima, è possibile eseguire il debug di questo. Si può scoprire ciò che i dati è stato e perché l'oggetto che stava cercando di elaborare i dati fallito a metà.

In generale, la prima cosa che si vuole fare con un NetworkStream è leggerlo in un buffer locale. L'unico motivo che posso pensare di non fare questo è se stai leggendo una quantità enorme di dati - e anche allora, potrei prendere in considerazione utilizzando il file system come un buffer intermedio se non si adatta in memoria

Non so le vostre esigenze, ma da quello che ho imparato finora, il mio consiglio è: non cercare di elaborare i dati direttamente dal NetworkStream meno che ci sia un motivo valido per farlo. Potreste leggere i dati nella memoria o sul disco prima, poi l'elaborazione del copia.

Altri suggerimenti

ho incontrato lo stesso 'peek per il numero magico e poi decidere quale processore stream per inviare il flusso a' esigenza e purtroppo non può donnola la mia via d'uscita da questo problema - come suggerito nei commenti alla risposta di Aaronaught - passando il già consumato byte nei metodi di lavorazione flusso dei parametri separati, in quanto tali metodi sono un dato di fatto e si aspettano System.IO.Stream e nient'altro.

Ho risolto questo con la creazione di un più o meno universale PeekableStream di classe che avvolge un ruscello. Funziona per NetworkStreams, ma anche per qualsiasi altro flusso, a condizione di Stream.CanRead di esso.


Modifica

In alternativa, è possibile utilizzare il nuovo ReadSeekableStream e fare

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

In ogni caso, ecco che arriva 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); }
}

Se si ha accesso all'oggetto Socket, si potrebbe provare la metodo di ricezione , passando SocketFlags.Peek. Questo è analogo alla bandiera MSG_PEEK che può essere passato alla chiamata recv in BSD Sockets o Winsock.

Ecco una semplice implementazione PeekStream che permette di sbirciare un certo numero di byte all'inizio del flusso unico (invece di essere in grado di sbirciare in ogni momento). I byte sbirciò vengono restituiti come un Stream se stessi, per ridurre al minimo le modifiche al codice esistente.

Ecco come lo si utilizza:

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() restituisce un flusso ricercabile contenente fino a peekSize byte iniziali del flusso sottostante (meno se il flusso è più breve di peekSize).

A causa della sua semplicità, leggendo PeekStream dovrebbe essere solo marginalmente più lento (eventualmente) di leggere direttamente flusso sottostante.

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

Disclaimer: PeekStream sopra è tratto da un programma di lavoro, ma non è completo testato, quindi può contenere bug. Funziona per me, ma si potrebbe scoprire alcuni casi angolo in cui è fallisce.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top