Question

À l'heure actuelle, il n'y a pas une méthode NetworkStream.Peek en C #. Quelle est la meilleure façon de mettre en œuvre une telle méthode qui fonctionne comme NetworkStream.ReadByte sauf que le byte retourné n'est pas supprimée du Stream?

Était-ce utile?

La solution

Si vous n'avez pas besoin de récupérer effectivement l'octet, vous pouvez vous référer à la section propriété de DataAvailable.

Dans le cas contraire, vous pouvez l'envelopper avec un StreamReader et invoquer la méthode de Peek.

Notez que ni d'entre eux sont particulièrement fiables pour la lecture d'un flux de réseau, en raison de problèmes de latence. Les données peuvent être disponibles (présent dans la mémoire tampon de lecture) l'instant après peek.

Je ne sais pas ce qu'il est que vous avez l'intention de le faire avec cela, mais la méthode Read sur NetworkStream est un appel de blocage, de sorte que vous ne vraiment pas besoin de vérifier le statut, même si vous recevez en morceaux. Si vous essayez de garder l'application réactive lors de la lecture du flux, vous devez utiliser un fil ou un appel asynchrone pour recevoir les données à la place.

Edit: Selon cette post, StreamReader.Peek est buggé sur un NetworkStream, ou au moins a un comportement sans papier, alors soyez prudent si vous choisissez d'aller dans cette voie.


Mise à jour - réponse aux commentaires

La notion d'un « coup d'oeil » lui-même sur le flux réel est en fait impossible; il est juste un flux, et une fois que l'octet est reçu, il ne se trouve plus sur le flux. Certains cours d'eau Recherche soutien pour que vous puissiez techniquement relire cet octet, mais NetworkStream n'est pas un d'entre eux.

Peeking applique uniquement lorsque le courant en train de lire dans une mémoire tampon; Une fois les données dans un tampon puis jeter un oeil est facile parce que vous vérifiez que tout ce qui est à la position actuelle dans la mémoire tampon. Voilà pourquoi un StreamReader est capable de le faire; aucune classe Stream aura généralement sa propre méthode de Peek.

Maintenant, pour ce problème particulier, je me demande si c'est vraiment la bonne réponse. Je comprends l'idée de choisir dynamiquement une méthode de traitement du flux, mais avez-vous vraiment besoin de le faire sur le flux brut? Pouvez-vous pas lu le flux dans un tableau d'octets en premier lieu, ou même le copier dans un MemoryStream et le traiter de ce moment-là?

Le principal problème que je vois est que si quelque chose se passe mal lorsque vous lisez un flux réseau, les données sont là. Mais si vous lisez dans un emplacement temporaire d'abord, vous pouvez déboguer cela. Vous pouvez découvrir ce que les données et pourquoi l'objet qui essayait de traiter les données échoué à mi-chemin.

En général, la première chose que vous voulez faire avec un NetworkStream est lu dans un tampon local. La seule raison pour laquelle je peux penser à ne pas le faire est si vous lisez une énorme quantité de données - et même alors, je pourrais envisager d'utiliser le système de fichiers comme un tampon intermédiaire si elle ne rentre pas dans la mémoire

Je ne sais pas exactement à vos besoins, mais de ce que je l'ai appris à ce jour, mon conseil serait: Ne pas essayer de traiter vos données directement à partir du NetworkStream moins qu'il y ait une raison impérieuse de le faire. Considérons la lecture des données dans la mémoire ou sur le disque en premier, puis le traitement de la copie.

Autres conseils

Je suis tombé sur le même « coup d'oeil pour le nombre magique, puis décider quel processeur flux pour envoyer le flux à » exigence et ne peut malheureusement pas belette me sortir de ce problème - comme suggéré dans les commentaires à la réponse de Aaronaught - en passant le déjà consommé octets dans les méthodes de traitement de flux dans les paramètres séparés, car ces méthodes sont donnés et ils attendent System.IO.Stream et rien d'autre.

Je résolu ce problème en créant un plus ou moins universel PeekableStream classe qui enveloppe un ruisseau. Il travaille pour NetworkStreams, mais aussi pour tout autre flux, à condition que vous Stream.CanRead il.


Modifier

Vous pouvez utiliser la nouvelle marque ReadSeekableStream et faire

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

En tout état de cause, voici 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 vous avez accès à l'objet Socket, vous pouvez essayer le href="http://msdn.microsoft.com/en-us/library/26f591ax.aspx" méthode receive , en passant SocketFlags.Peek. Ceci est analogue au drapeau de MSG_PEEK qui peut être transmis à l'appel recv dans BSD Sockets ou Winsock.

Voici une implémentation PeekStream très simple qui vous permet de jeter un regard d'un certain nombre d'octets au début du cours d'eau uniquement (au lieu d'être en mesure de jeter un regard à tout moment). Les octets sont renvoyés jeté un œil comme Stream eux-mêmes, afin de minimiser les changements au code existant.

Voici comment vous l'utilisez:

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() renvoie un flux seekable qui contient jusqu'à peekSize octets initiaux du flux sous-jacent (moins si le courant est plus courte que peekSize).

En raison de sa simplicité, la lecture PeekStream ne devrait être légèrement plus lent (le cas échéant) que la lecture flux sous-jacent directement.

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

Avertissement: PeekStream ci-dessus est tirée d'un programme de travail, mais il ne complètement testé, donc peut contenir des bugs. Il fonctionne pour moi, mais vous pouvez découvrir quelques cas marginaux où est échoue.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top