实现自定义流

时间:2015-10-18 22:06:29

标签: c# .net stream

首先,我应该提一下,我没有可用的内部Stream对象。而不是那样,我确实有这个对象:

public interface IChannel
{
    void Send(byte[] data);
    event EventHandler<byte[]> Receive;
}

我想实现一个Stream类,如下所示:

public class ChannelStream : Stream
{
    private readonly IChannel _channel;

    public ChannelStream(IChannel channel)
    {
        this._channel = channel;
    }

    // TODO: Implement Stream class
}

我需要的功能与NetworkStream非常相似:
将字节写入我的流应该将这些字节添加到缓冲区,并在调用_channel.Send后调用Flush() Stream还将侦听_channel.Receive个事件,并将字节添加到另一个内部缓冲区,直到从流中读取它们。如果Stream没有任何可用数据,则应阻止直到新数据可用。

然而,我正在努力实施。我已经在内部使用了两个MemoryStream进行了实验,但这导致缓冲区继续吃掉越来越多的ram。

我可以使用哪种收集/流来实现我的流?

1 个答案:

答案 0 :(得分:2)

从集合中考虑你需要的东西并从那里开始。

当您需要某种类型的集合时,您应该考虑以下几个问题:

  1. 您是否需要随机访问集合中的项目?

  2. 多个线程是否会访问该集合?

  3. 读取后是否需要保留集合中的数据?

  4. 订购重要吗?如果是这样,什么顺序 - 通过一些比较添加订单,反向添加订单,项目排序?

  5. 对于这种情况下的输出缓冲区,答案是no,yes,no和yes:添加顺序。这几乎是ConcurrentQueue课的单挑。这允许您从一个或多个源添加对象,这些对象不需要与读取它们的代码位于同一个线程中。它不会让你随意索引收集(好吧,不是直接),你似乎并不需要。

    我使用相同的类型作为输入缓冲区,使用&#39;当前块&#39;缓冲区,用于保存最近读取的缓冲区,包含在一些简单的对象锁定语义中,以处理任何线程问题。

    输出部分如下所示:

    // Output buffer
    private readonly ConcurrentQueue<byte[]> _outputBuffer = new ConcurrentQueue<byte[]>();
    
    public override void Write(byte[] buffer, int offset, int count)
    {
        // Copy written data to new buffer and add to output queue
        byte[] data = new byte[count];
        Buffer.BlockCopy(buffer, offset, data, 0, count);
        _outputBuffer.Enqueue(data);
    }
    
    public override void Flush()
    {
        // pull everything out of the queue and send to wherever it is going
        byte[] curr;
        while (_outputBuffer.TryDequeue(out curr))
            internalSendData(curr);
    }
    

    internalSendData方法是数据传输到网络的地方。

    读缓冲有点复杂:

    // collection to hold unread input data
    private readonly ConcurrentQueue<byte[]> _inputBuffer = new ConcurrentQueue<byte[]>();
    // current data block being read from
    private byte[] _inputCurrent = null;
    // read offset in current block
    private short _inputPos = 0;
    // object for locking access to the above.
    private readonly object _inputLock = new object();
    
    public override int Read(byte[] buffer, int offset, int count)
    {
        int readCount = 0;
        lock(_inputLock)
        {
            while (count > 0)
            {
                if (_inputCurrent == null || _inputCurrent.Length <= _inputPos)
                {
                    // read next block from input buffer
                    if (!_inputBuffer.TryDequeue(out _inputCurrent))
                        break;
    
                    _inputPos = 0;
                }
    
                // copy bytes to destination
                int nBytes = Math.Min(count, _inputCurrent.Length - _inputPos);
                Buffer.BlockCopy(_inputCurrent, _inputPos, buffer, offset, nBytes);
    
                // adjust all the offsets and counters
                readCount += nBytes;
                offset += nBytes;
                count -= nBytes;
                _inputPos += (short)nBytes;
            }
        }
        return readCount;
    }
    

    希望这是有道理的。

    使用队列进行这种软缓冲意味着只要数据被延迟发送或读取,数据就只保存在内存中。一旦你调用Flush输出缓冲区的内存被释放用于垃圾收集,所以你不必担心内存爆炸,除非你试图发送比实际传输机制快得多的内存处理。但是,如果你每秒排队几兆字节的数据来通过ADSL连接,那么没有什么可以拯救你:P

    我在上面添加了一些改进,比如一些检查,以确保在缓冲区处于合理的级别后自动调用Flush