C# 返回WCF流时将流转换为文件?
我有一个WCF服务,可以从远程FTP服务器传输大文件(100MB+)C# 返回WCF流时将流转换为文件?,c#,wcf,file,caching,streaming,C#,Wcf,File,Caching,Streaming,我有一个WCF服务,可以从远程FTP服务器传输大文件(100MB+) [ServiceContract] public interface IStreamService { [OperationContract] Stream GetDataFromFtp(); } public class StreamService : IStreamService { public Stream GetDataFromFtp() { Stream ftpSt
[ServiceContract]
public interface IStreamService
{
[OperationContract]
Stream GetDataFromFtp();
}
public class StreamService : IStreamService
{
public Stream GetDataFromFtp()
{
Stream ftpStream = Retr(...);
return ftpStream;
}
}
当WCF将文件流式传输到客户端时,我希望将其流式传输到本地缓存中,这样将来的请求就不需要一直返回到远程FTP—我可以直接从磁盘上为它们提供服务
在返回之前,如果不在内存中缓冲整个100MB文件流,我很难实现这一点
我尝试使用一个简单的捕获流包装器,它对每次读取都进行写入:
public class CapturingStreamWrapper : Stream
{
private readonly Stream stream;
private readonly Stream captureStream;
public override int Read(byte[] buffer, int offset, int count)
{
int readBytes = stream.Read(buffer, offset, count);
captureStream.Write(buffer, offset, readBytes);
return readBytes;
}
}
public class StreamService : IStreamService
{
public Stream GetDataFromFtp()
{
Stream ftpStream = Retr(...);
Stream cacheStream = File.OpenWrite(...);
return new CapturingStreamWrapper(ftpStream, cacheStream);
}
}
但这似乎不起作用
此外,这不提供任何错误处理-如果客户端传输失败,我需要缓存的catch块来删除任何半写文件(即事务缓存)。我不确定这将如何工作,因为我不知道在WCF生命周期中何时调用/清理流
你知道如何在流回到客户机时将流传输到文件中吗?我最终编写了两个相互连接的流类,一个在读取时通过管道传输到另一个。对大代码粘贴表示歉意:
/// <summary>
/// A stream that, as it reads, makes those bytes available on an ouput
/// stream. Thread safe.
/// </summary>
public class CacheStream : Stream
{
private readonly Stream stream;
public CacheStream(Stream stream)
{
if (stream == null) throw new ArgumentNullException("stream");
this.stream = stream;
OutputStream = new CacheOutputStream(this);
}
public event EventHandler<BytesReadEventArgs> BytesRead = delegate { };
public event EventHandler Closing = delegate { };
public Stream OutputStream { get; private set; }
public override void Flush()
{
stream.Flush();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new InvalidOperationException("Cannot seek in CachingStream.");
}
public override void SetLength(long value)
{
stream.SetLength(value);
}
public override int Read(byte[] buffer, int offset, int count)
{
int numberOfBytesRead = stream.Read(buffer, offset, count);
if (numberOfBytesRead > 0)
PipeToOutputStream(buffer, offset, numberOfBytesRead);
return numberOfBytesRead;
}
private void PipeToOutputStream(byte[] buffer, int offset, int numberOfBytesRead)
{
var tmp = new byte[numberOfBytesRead];
Array.Copy(buffer, offset, tmp, 0, numberOfBytesRead);
BytesRead(this, new BytesReadEventArgs(tmp));
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new InvalidOperationException("Cannot write in CachingStream.");
}
public override bool CanRead
{
get { return stream.CanRead; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return false; }
}
public override long Length
{
get { return stream.Length; }
}
public override long Position
{
get { return stream.Position; }
set { throw new InvalidOperationException("Cannot set position in CachingStream."); }
}
public override void Close()
{
Closing(this, EventArgs.Empty);
base.Close();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
OutputStream.Dispose();
}
}
//
///一种在读取时使这些字节在输出端可用的流
///小溪。线程安全。
///
公共类CacheStream:Stream
{
私有只读流;
公共缓存流(流)
{
如果(stream==null)抛出新的ArgumentNullException(“stream”);
this.stream=流;
OutputStream=新缓存OutputStream(此);
}
public event EventHandler BytesRead=委托{};
public event EventHandler Closing=委托{};
公共流输出流{get;private set;}
公共覆盖无效刷新()
{
stream.Flush();
}
公共覆盖长寻道(长偏移,参见原始坐标系)
{
抛出新的InvalidOperationException(“无法在CachingStream中查找”);
}
公共覆盖无效设置长度(长值)
{
stream.SetLength(值);
}
公共重写整型读取(字节[]缓冲区、整型偏移量、整型计数)
{
int numberOfBytesRead=stream.Read(缓冲区、偏移量、计数);
如果(numberOfBytesRead>0)
PipeToOutputStream(缓冲区、偏移量、numberOfBytesRead);
返回numberOfBytesRead;
}
私有void PipeToOutputStream(字节[]缓冲区,int偏移量,int numberOfBytesRead)
{
var tmp=新字节[numberOfBytesRead];
复制(缓冲区、偏移量、tmp、0、numberOfBytesRead);
ByteRead(这是新的ByteReadEventArgs(tmp));
}
公共重写无效写入(字节[]缓冲区、整数偏移量、整数计数)
{
抛出新的InvalidOperationException(“无法在CachingStream中写入”);
}
公共覆盖布尔可读取
{
获取{return stream.CanRead;}
}
公共覆盖布尔搜索
{
获取{return false;}
}
公共覆盖布尔可写
{
获取{return false;}
}
公共覆盖长长度
{
获取{return stream.Length;}
}
公众优先多头仓位
{
获取{return stream.Position;}
set{抛出新的invalidoOperationException(“无法在CachingStream中设置位置”);}
}
公共覆盖无效关闭()
{
关闭(此,EventArgs.Empty);
base.Close();
}
受保护的覆盖无效处置(布尔处置)
{
基地。处置(处置);
OutputStream.Dispose();
}
}
及
//
///缓存流的输出部分。从缓冲区队列中传输字节。
///线程安全。
///
公共类CacheOutputStream:Stream
{
私人职位;
私有可变整数长度;
私有易失性bool源被关闭;
//BCL中还没有Deque,但LinkedList大致相同。
私有只读LinkedList缓冲区=新LinkedList();
公共CacheOutputStream(CacheStream流)
{
如果(stream==null)抛出新的ArgumentNullException(“stream”);
stream.BytesRead+=(o,e)=>AddToQueue(e.Buffer);
stream.Closing+=(o,e)=>sourceIsClosed=true;
}
专用void AddToQueue(字节[]缓冲区)
{
如果(buffer.Length==0)
返回;
锁(缓冲区)
{
buffers.AddLast(buffer);
长度+=缓冲区长度;
}
}
公共重写整型读取(字节[]缓冲区、整型偏移量、整型计数)
{
如果(buffer==null)抛出新的ArgumentNullException(“buffer”);
bool nomorebuffersavaailable=hasnomorebuffersavaailable();
//Guard子句-已关闭,无需编写更多内容。
if(nomorebuffersavaailable&&sourceIsClosed)
返回0;
if(nomorebuffersavaailable)
{
//还没有关闭!无限阻塞,直到我们关闭或有一些数据。
while(hasnomorebuffersavaailable())
{
如果(源已关闭)
返回0;
睡眠(时间跨度从毫秒(50));
}
}
字节[]currentBuffer=GetCurrentBuffer();
int numberOfBytesRead=DoRead(缓冲区、计数、当前缓冲区、偏移量);
PutLeftOverBytesAtFrontof队列(currentBuffer,numberOfBytesRead);
返回numberOfBytesRead;
}
//检查调用方是否没有足够的空间容纳缓冲区。
//将剩余字节放在队列的前面。
private void PutLeftoverBytesAtFrontOfQueue(字节[]currentBuffer,int numberOfBytesRead)
{
如果(currentBuffer==null)抛出新的ArgumentNullException(“currentBuffer”);
if(numberOfBytesRead==currentBuffer.Length)
return;//干净读取!
var remainingBuffer=新字节[currentBuffer.Length-numberOfBytesRead];
复制(currentBuffer,numberOfBytesRead,remainingBuffer,0,remainingBuffer.Length);
锁(缓冲区)
buffers.AddFirst(remainingBuffer);
}
私有int-DoRead(字节[]缓冲区,int计数,字节[]当前缓冲区,int偏移量)
{
int maxNumberOfBytesWeCanWr
/// <summary>
/// Output portion of CacheStream. Streams bytes from a queue of buffers.
/// Thread safe.
/// </summary>
public class CacheOutputStream : Stream
{
private volatile int position;
private volatile int length;
private volatile bool sourceIsClosed;
// No Deque<T> in the BCL yet, but LinkedList is more or less the same.
private readonly LinkedList<byte[]> buffers = new LinkedList<byte[]>();
public CacheOutputStream(CacheStream stream)
{
if (stream == null) throw new ArgumentNullException("stream");
stream.BytesRead += (o, e) => AddToQueue(e.Buffer);
stream.Closing += (o, e) => sourceIsClosed = true;
}
private void AddToQueue(byte[] buffer)
{
if (buffer.Length == 0)
return;
lock (buffers)
{
buffers.AddLast(buffer);
length += buffer.Length;
}
}
public override int Read(byte[] buffer, int offset, int count)
{
if (buffer == null) throw new ArgumentNullException("buffer");
bool noMoreBuffersAvailable = HasNoMoreBuffersAvailable();
// Guard clause - closed and nothing more to write.
if (noMoreBuffersAvailable && sourceIsClosed)
return 0;
if (noMoreBuffersAvailable)
{
// Not closed yet! Block infinitely until we get closed or have some data.
while (HasNoMoreBuffersAvailable())
{
if (sourceIsClosed)
return 0;
Thread.Sleep(TimeSpan.FromMilliseconds(50));
}
}
byte[] currentBuffer = GetCurrentBuffer();
int numberOfBytesRead = DoRead(buffer, count, currentBuffer, offset);
PutLeftoverBytesAtFrontOfQueue(currentBuffer, numberOfBytesRead);
return numberOfBytesRead;
}
// Check if caller didn't have enough space to fit the buffer.
// Put the remaining bytes at the front of the queue.
private void PutLeftoverBytesAtFrontOfQueue(byte[] currentBuffer, int numberOfBytesRead)
{
if (currentBuffer == null) throw new ArgumentNullException("currentBuffer");
if (numberOfBytesRead == currentBuffer.Length)
return; // Clean read!
var remainingBuffer = new byte[currentBuffer.Length - numberOfBytesRead];
Array.Copy(currentBuffer, numberOfBytesRead, remainingBuffer, 0, remainingBuffer.Length);
lock (buffers)
buffers.AddFirst(remainingBuffer);
}
private int DoRead(byte[] buffer, int count, byte[] currentBuffer, int offset)
{
int maxNumberOfBytesWeCanWrite = Math.Min(count, currentBuffer.Length);
Array.Copy(currentBuffer, 0, buffer, offset, maxNumberOfBytesWeCanWrite);
position += maxNumberOfBytesWeCanWrite;
return maxNumberOfBytesWeCanWrite;
}
private byte[] GetCurrentBuffer()
{
byte[] currentBuffer;
lock (buffers)
{
currentBuffer = buffers.First.Value;
buffers.RemoveFirst();
}
return currentBuffer;
}
private bool HasNoMoreBuffersAvailable()
{
lock (buffers)
return buffers.Count == 0;
}
public override void Flush() { }
public override long Seek(long offset, SeekOrigin origin)
{
throw new InvalidOperationException("Cannot seek in CachingStream.");
}
public override void SetLength(long value)
{
throw new InvalidOperationException("Cannot set length in CachingStream.");
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new InvalidOperationException("Cannot write in a CachingStream.");
}
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 { return length; }
}
public override long Position
{
get { return position; }
set { throw new InvalidOperationException("Cannot set position in CachingStream."); }
}
}