Вопрос

В настоящее время нет NetworkStream.Peek Метод в C #. Какой лучший способ внедрить такой метод, который функционирует так же, как NetworkStream.ReadByte за исключением того, что возвращенные byte на самом деле не удален из Stream?

Это было полезно?

Решение

Если вам не нужно на самом деле получить байт, вы можете обратиться к DataAvailable имущество.

В противном случае вы можете обернуть его с помощью StreamReader и вызывают его Peek метод.

Обратите внимание, что ни один из них не особенно надежен для чтения от сетевого потока, из-за проблем задержки. Данные могут стать доступными (присутствуют в прочитанном буфере) очень мгновенно после Вы заглядываете.

Я не уверен, что вы собираетесь делать с этим, но Read метод включения NetworkStream Это блокирующий вызов, поэтому вам не нужно проверять статус, даже если вы получаете в кусках. Если вы пытаетесь сохранить адаптивность приложения во время чтения из потока, вы должны использовать нить или асинхронный вызов, чтобы получить вместо этого данных.

Редактировать: согласно эта почта, StreamReader.Peek багги на NetworkStream, или, по крайней мере, не имеет недокументированного поведения, поэтому будьте осторожны, если вы решите пойти на этот маршрут.


Обновлено - ответ на комментарии

Понятие «заглянуть» на самом фактическом потоке на самом деле невозможно; Это просто поток, и как только байт получен, то он больше не находится на потоке. Некоторые потоки поддерживают поиск, чтобы вы могли технически перечитать этот байт, но NetworkStream не один из них.

Peeking применяется только тогда, когда читают поток в буфер; После того, как данные в буфере, то PEEKTING легко, потому что вы просто проверяете все, что на текущей позиции в буфере. Вот почему StreamReader умеет сделать это; нет Stream Класс обычно имеет свой собственный Peek метод.

Теперь, для этой проблемы конкретно, я задаваю вопрос, действительно ли это правильный ответ. Я понимаю идею динамически выбора метода для обработки потока, но вы В самом деле Нужно сделать это на сырье? Можете ли вы сначала прочитать поток в байтовый массив или даже скопировать его в MemoryStream, и обрабатывать это с этого момента?

Основная проблема, которую я вижу, это то, что если что-то плохое происходит, когда вы читаете с сетевого потока, данные исчезли. Но если вы прочитаете его во временное местоположение, вы можете отладить это. Вы можете узнать, какие данные были и почему объект, который пытался обрабатывать данные, не пройденные на полпути.

В общем, самое первое, что вы хотите сделать с NetworkStream Прочитайте его в локальный буфер. Единственная причина, по которой я могу думать не делать этого, - это если вы читаете огромное количество данных - и даже тогда я мог бы рассмотреть возможность использования файловой системы в качестве промежуточного буфера, если он не будет вписаться в память.

Я не знаю ваши точные требования, но от того, что я узнал до сих пор, мой совет будет: не пытайтесь обработать ваши данные напрямую из NetworkStream Если нет убедительной причины для этого. Подумайте о том, чтобы прочитать данные в память или на диск сначала, а затем обработайте копию.

Другие советы

Я бежал в то же самое «заглянуть за волшебным числом, а затем решить, какой процессор по потоковому отправить поток в« требование »и, к сожалению, не может выйти из этой проблемы - как предложено в комментариях к ответу AaroNaus В методы обработки потока в отдельных параметрах, поскольку эти методы даны, и они ожидают, что System.io.stream и ничего другого.

Я решил это, создав более или менее универсальный Peekablestrewream. класс, который заводит поток. Он работает для сетей по течению, но и для любого другого потока, при условии, что вы Stream.Canread. Это.


Редактировать

В качестве альтернативы вы можете использовать совершенно новый ReadSeekableStream и делай

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

В любом случае, вот приходит 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); }
}

Если у вас есть доступ к Socket объект, вы можете попробовать Получите метод, Прохождение SocketFlags.Peek. Отказ Это аналогично MSG_PEEK флаг, который можно передавать recv Вызов в розетках BSD или Winsock.

Вот очень простой PeekStream Реализация, которая позволяет вам заглянуть определенное количество байтов в начале только потока (в отличие от того, чтобы быть в состоянии заглянуть в любое время). Закрытые байты возвращаются как Stream Сами, чтобы минимизировать изменения в существующем коде.

Вот как вы используете это:

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() Возвращает поиск поток, который содержит до peekSize начальные байты базового потока (меньше, если поток короче, чем peekSize).

Из-за его простоты, чтение peektream следует быть незначительно медленным (если вообще), чем читать базовый поток напрямую.

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

Отказ от ответственности: Peektream выше взята из рабочей программы, но она не всестороннее протестирована, поэтому может содержать ошибки. Это работает для меня, но вы можете раскрыть некоторые угловые случаи, где не удается.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top