从数据块创建.Net Stream

时间:2016-04-12 19:55:47

标签: .net wcf stream

我有一些大型数据文件,我可以使用专门为此设计的API以32kb的形式检索。 API的一种用法可以是:

LargeFileAPI lfa = new LargeFileAPI("file1.bin");
bool moredata = true;
List<byte[]> theWholeFile = new List<byte[]>();
while ( moredata  ) 
{
  byte[] arrayRead = new byte[32768];
  moredata = lfa.Read(arrayRead);
  theWholeFile.Add(arrayRead);
}

上面的问题是,从中读取内存的大小与大文件的大小相同(比方说100Mb)。由于我想将此作为返回结果传递给WCF服务,我宁愿使用Stream作为服务的输出。

如何从中创建Stream对象并将其作为返回参数传递给WCF服务而不占用内存中的完整文件大小?

我正在考虑创建一个继承自

的类LargeFileStream
System.IO.Stream

并覆盖Read方法。但我似乎无法弄清楚如何处理Stream.Read采用偏移参数和要读取的字节数这一事实,因为我提到的API需要为每次读取读取固定数量的字节。此外,我必须覆盖的所有其他方法,如Flush(),Position和其他任何方法。他们应该做些什么?我问,因为我不知道除了Stream.Read()之外的其他功能,当我从客户端(WCF服务的调用者)读取流时,WCF会调用。

此外,我需要它可序列化,以便它可以作为WCF服务的输出参数。

由于 圣战

3 个答案:

答案 0 :(得分:2)

您可以使用api大小的一个缓冲区(即32kb)编写您的流以执行您想要的操作,并在阅读时将其回收。示例代码如下(不是它不是生产准备就绪,需要测试,但有些东西可以帮助你开始):

public class LargeFileApiStream : Stream {
    private readonly LargeFileApi _api;
    private bool _hasMore;
    private bool _done;
    private byte[] _buffer;
    const int ApiBufferSize = 32768;
    public LargeFileApiStream(LargeFileApi api) {
        _api = api;    
    }

    public override void Flush() {
        // you can ignore that, this stream is not writable
    }

    public override long Seek(long offset, SeekOrigin origin) {
        throw new NotSupportedException(); // not seekable, only read from beginning to end
    }

    public override void SetLength(long value) {
        throw new NotSupportedException(); // not writable
    }        

    public override void Write(byte[] buffer, int offset, int count) {
        throw new NotSupportedException(); // not writable
    }

    public override int Read(byte[] buffer, int offset, int count) {
        // if we reached end of stream before - done
        if (_done)
            return 0;

        if (_buffer == null) {
            // we are just starting, read first block
            _buffer = new byte[ApiBufferSize];
            _hasMore = _api.Read(_buffer);
        }

        var nextIndex = _position % ApiBufferSize;
        int bytesRead = 0;
        for (int i = 0; i < count; i++) {
            if (_buffer.Length <= nextIndex) {
                // ran out of current chunk - fetch next if possible                    
                if (_hasMore) {
                    _hasMore = _api.Read(_buffer);
                }
                else {
                    // we are done, nothing more to read
                    _done = true;
                    break;
                }
                // reset next index back to 0, we are now reading next chunk
                nextIndex = 0;
                buffer[offset + i] = _buffer[nextIndex];
                nextIndex++;
                bytesRead++;
            }
            else {
                // write byte to output buffer
                buffer[offset + i] = _buffer[nextIndex];
                nextIndex++;
                bytesRead++;
            }                                                                
        }

        _position += bytesRead;
        return bytesRead;
    }

    public override bool CanRead {
        get { return true; }
    }
    public override bool CanSeek {
        get { return false; }
    }
    public override bool CanWrite {
        get { return false; }
    }
    public override long Length {
        get { throw new NotSupportedException(); }
    }

    private long _position;
    public override long Position
    {
        get { return _position; }
        set { throw new NotSupportedException(); } // not seekable
    }
}

答案 1 :(得分:1)

只需将您的数据存储在临时文件中,如下所示:

// create temporary stream
var stream = new FileStream(Path.GetTempFileName(), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose);

try
{
    // write all data to temporary stream
    while (moredata) 
    {
        byte[] arrayRead = new byte[32768];
        moredata = lfa.Read(arrayRead);
        stream.Write(arrayRead, 0, arrayRead.Length);
    }

    stream.Flush();

    stream.Position = 0; // Reset position so stream will be read from beginning
}
catch
{
    stream.Close(); // close stream to delete temporary file if error occured
}

临时文件流保存从LargeFileApi接收的数据。由于数据实际存储在文件中,因此不会耗尽内存。

由于传递给构造函数的FileOptions.DeleteOnClose选项,流将关闭后将删除临时文件。因此,如果出现问题或完成阅读,您可以关闭流。

答案 2 :(得分:0)

您可以执行以下操作:

  1. 使用netTcpBinding创建WCF服务。该服务可以返回一个应用了MessageContract属性的对象

    [MessageContract] 公共类LargeStream {

    [MessageHeader]
    public int Section { get; set; }
    
    [MessageBodyMember]
    public Stream Data { get; set; }
    

    }

  2. 如果您想添加其他元数据,请使用MessageHeader属性修饰它们。

    在客户端,Web应用程序可以使用该服务并发出两个请求。

    1. 获取文件的部分数量
    2. 对于每个部分请求流
    3. 将下载完成后的所有流合并到一个文件中。