Pregunta

Actualmente, no hay un NetworkStream.Peek Método en C#. ¿Cuál es la mejor manera de implementar tal método que funciona como NetworkStream.ReadByte excepto que el regresado byte en realidad no se elimina del Stream?

¿Fue útil?

Solución

Si no necesita recuperar realmente el byte, puede consultar el DataAvailable propiedad.

De lo contrario, puede envolverlo con un StreamReader e invocar su Peek método.

Tenga en cuenta que ninguno de estos es particularmente confiable para la lectura de una transmisión de red, debido a problemas de latencia. Los datos pueden estar disponibles (presentes en el búfer de lectura) el mismo instante después miras.

No estoy seguro de qué es lo que pretendes hacer con esto, pero el Read método sobre NetworkStream es una llamada de bloqueo, por lo que realmente no necesita verificar si hay estado, incluso si está recibiendo fragmentos. Si intenta mantener la aplicación receptiva mientras lee desde la transmisión, debe usar un hilo o una llamada asíncrona para recibir los datos.

Editar: Según esta publicación, StreamReader.Peek es buggy en un NetworkStream, o al menos tiene un comportamiento indocumentado, así que tenga cuidado si elige seguir esa ruta.


Actualizado - Respuesta a los comentarios

La noción de un "vista" en la corriente real en sí es realmente imposible; Es solo una transmisión, y una vez que se recibe el byte, ya no está en la corriente. Algunas transmisiones admiten la búsqueda para que técnicamente pueda volver a leer ese byte, pero NetworkStream ¿No es uno de ellos?

La mirada solo se aplica cuando leen la transmisión en un búfer; Una vez que los datos están en un búfer, mirar es fácil porque solo verifica lo que esté en la posición actual en el búfer. Por eso un StreamReader es capaz de hacer esto; no Stream La clase generalmente tendrá la suya Peek método.

Ahora, para este problema específicamente, me pregunto si esta es o no la respuesta correcta. Entiendo la idea de seleccionar dinámicamente un método para procesar la transmisión, pero lo hace De Verdad ¿Necesitas hacer esto en la corriente en bruto? ¿No puede leer la transmisión en una matriz de bytes primero, o incluso copiarla en un MemoryStream, y procesarlo a partir de ese momento?

El problema principal que veo es que si sucede algo malo cuando lees desde una transmisión de red, los datos se han ido. Pero si lo lees primero en una ubicación temporal, puedes depurar esto. Puede averiguar cuáles eran los datos y por qué el objeto que intentaba procesar los datos falló a mitad de camino.

En general, lo primero que quieres hacer con un NetworkStream lo lee en un búfer local. La única razón por la que puedo pensar en no hacer esto es si está leyendo una enorme cantidad de datos, e incluso entonces, podría considerar usar el sistema de archivos como un búfer intermedio si no encaja en la memoria.

No conozco sus requisitos exactos, pero por lo que he aprendido hasta ahora, mi consejo sería: no intente procesar sus datos directamente desde el NetworkStream A menos que haya una razón convincente para hacerlo. Considere leer los datos en la memoria o en el disco primero, luego procesar la copia.

Otros consejos

Me encontré con el mismo 'Peek por el número mágico y luego decidí qué procesador de transmisión enviar la transmisión al requisito' y desafortunadamente no puedo salir de ese problema, como se sugiere en los comentarios a la respuesta de Aaronandia, pasando los bytes ya consumidos En los métodos de procesamiento de flujo en parámetros separados, ya que esos métodos son un dado y esperan system.io.stream y nada más.

Resolví esto creando un más o menos universal Asombrar clase que envuelve una transmisión. Funciona para NetworkStreams, pero también para cualquier otra transmisión, siempre que usted Transmisión. eso.


Editar

Alternativamente, puede usar el nuevo ReadSeekableStream y hacer

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

En cualquier caso, aquí viene 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); }
}

Si tiene acceso al Socket objeto, podrías probar el Método de recibir, pase SocketFlags.Peek. Esto es análogo al MSG_PEEK bandera que se puede pasar al recv Llame a los enchufes BSD o Winsock.

Aquí hay un muy simple PeekStream Implementación que le permite echar un vistazo a un cierto número de bytes al comienzo de la transmisión solamente (en lugar de poder echar un vistazo en cualquier momento). Los bytes mirados se devuelven como un Stream ellos mismos, para minimizar los cambios en el código existente.

Así es como lo usas:

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() Devuelve una transmisión solicitable que contiene hasta peekSize bytes iniciales de la corriente subyacente (menos si la corriente es más corta que peekSize).

Debido a su simplicidad, leer Peekstream solo debe ser marginalmente más lento (si es que lo hace) que leer directamente la corriente subyacente.

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

Descargo de responsabilidad: Peekstream arriba se toma de un programa de trabajo, pero no se prueba de manera integral, por lo que puede contener errores. Funciona para mí, pero puede descubrir algunos casos de esquina donde los fallan.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top