C#:实现NetworkStream.Peek?

C#:实现NetworkStream.Peek?,c#,stream,byte,networkstream,peek,C#,Stream,Byte,Networkstream,Peek,目前,C#中没有NetworkStream.Peek方法。除了返回的字节实际上没有从流中删除之外,实现类似于NetworkStream.ReadByte的方法的最佳方法是什么?如果不需要实际检索字节,可以参考属性 否则,您可以使用包装并调用其Peek方法 请注意,由于延迟问题,这两种方法对于从网络流读取都不是特别可靠的。数据可能在您窥视后的瞬间变得可用(存在于读取缓冲区中) 我不确定您打算用它做什么,但是NetworkStream上的Read方法是一个阻塞调用,因此您实际上不需要检查状态,即使您

目前,C#中没有
NetworkStream.Peek
方法。除了返回的
字节
实际上没有从
中删除之外,实现类似于
NetworkStream.ReadByte
的方法的最佳方法是什么?

如果不需要实际检索字节,可以参考属性

否则,您可以使用包装并调用其
Peek
方法

请注意,由于延迟问题,这两种方法对于从网络流读取都不是特别可靠的。数据可能在您窥视后的瞬间变得可用(存在于读取缓冲区中)

我不确定您打算用它做什么,但是
NetworkStream
上的
Read
方法是一个阻塞调用,因此您实际上不需要检查状态,即使您是分块接收。如果您试图在从流中读取数据时保持应用程序的响应性,那么应该使用线程或异步调用来接收数据

编辑:根据,
StreamReader.Peek
NetworkStream
上有缺陷,或者至少有未记录的行为,所以如果您选择这样做,请小心


更新-回复评论

“窥视”实际流本身的概念实际上是不可能的;它只是一个流,一旦接收到字节,它就不再在流中了。有些流支持查找,因此您可以从技术上重新读取该字节,但
NetworkStream
不是其中之一

窥视仅适用于将流读入缓冲区时;一旦数据在缓冲区中,那么窥视就很容易了,因为您只需检查缓冲区中当前位置的内容。这就是为什么
StreamReader
能够做到这一点;没有
Stream
类通常会有自己的
Peek
方法

现在,对于这个问题,我怀疑这是否真的是正确的答案。我理解动态选择一种方法来处理流的想法,但是您真的需要在原始流上这样做吗?您不能先将流读入字节数组,甚至不能将其复制到
内存流
,然后从那时起对其进行处理吗

我看到的主要问题是,如果在从网络流中读取数据时发生了不好的情况,那么数据就会消失。但如果您先将其读入临时位置,就可以调试它。您可以找出数据是什么,以及试图处理数据的对象中途失败的原因

通常,您要对
网络流执行的第一件事就是将其读入本地缓冲区。我能想到不做这件事的唯一原因是,如果你正在阅读大量的数据,即使这样,我也可以考虑使用文件系统作为中间缓冲区,如果它不适合内存。

我不知道您的确切要求,但根据我目前所了解的情况,我的建议是:不要试图直接从
网络流
处理数据,除非有令人信服的理由这样做。考虑将数据读入内存或先在磁盘上,然后处理副本。

< P>如果您有访问“代码> Socket < /Cult>对象,您可以尝试,通过<代码> SockFLAGS。PEEK < /代码>。这类似于可以传递给BSD套接字或Winsock中的
recv
调用的
MSG_PEEK
标志。

我遇到了相同的“PEEK for magic number,然后决定将流发送到哪个流处理器”要求,不幸的是,我无法回避这个问题,正如在对的评论中所建议的那样Aaronaught的答案是——将已经消耗的字节以单独的参数传递到流处理方法中,因为这些方法是给定的,并且它们期望System.IO.stream而不是其他任何东西

我通过创建一个封装流的或多或少通用的PeekableStream类来解决这个问题。它适用于NetworkStreams,但也适用于任何其他流,只要您选择Stream.CanRead即可


编辑

或者,您可以使用全新的


无论如何,下面是
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); }
}
//
///PeekableStream封装了一个流,可用于在底层流中向前窥视,
///不消耗字节。换句话说,执行Peek()将允许您在流程中向前看,
///但它不会影响后续Read()调用的结果。
/// 
///这有时是必要的,例如,为了窥视字节流的幻数并决定哪个字节
///流处理器来移交流。
/// 
公共类PeakableStream:Stream
{
私有只读流位于流之下;
专用只读字节[]lookAheadBuffer;
私有内部外观索引;
公共PeekTableStream(流underyingStream,int maxPeekBytes)
{
this.underyingstream=underyingstream;
lookAheadBuffer=新字节[maxPeekBytes];
}
受保护的覆盖无效处置(布尔处置)
{
如果(处置)
underyingstream.Dispose();
基地。处置(处置);
}
/// 
///窥视最大计数字节数,如果流在读取该字节数之前结束,则窥视最小计数字节数。
/// 
///对该方法的调用不会影响对Read()和Peek()的后续调用。
/// 
///请注意,与Read()相反,此方法将始终查看字节数,除非在此之前到达流的末尾
///方法,即使尚未到达流的结尾,其读取的字节数也可能少于count字节。
/// 
///字节数组。此方法返回时,缓冲区包含指定的字节数组,其值介于偏移量和
///(偏移量+窥视字节数-1)替换为从当前源窥视的字节。
///零基
/// <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); }
}