wcf提供了streaming方式后,一直有个小问题,找不到合适的stream载体,如果能用上文件流什么的做返回值那是最好不过了,但是更多的情况下,需要返回一个流,但是这个流并没有类似文件之类的真实载体,而且有时候这个流还比较大(如果很小的话,也不需要用到streaming方式了),这时候似乎就有那么点麻烦了。
首先,我不喜欢用MemoryStream,因为它真实的占用了这么多内存,遇到大数据量的情况下,wcf的streaming方式的威力将大大降低。当然,也可以借助文件,返回文件流来绕开这个问题,或者使用其他的什么现成的流。
然而,我更倾向于使用类似Circular Buffer的逻辑来处理这个流,这样可以占用很小的内存,来输出很大体积的流,最大限度的体现wcf的streaming模式的优势。
不过,网上查了几个Circlar Buffer的实现,总感觉不是特别满意。。。无奈之下只能自己动手打造一个:
CircularStream
public class CircularStream
: Stream
{
#region Fields
private const int DefaultCapacity = 0x10000;
private readonly object SyncRoot = new object();
private bool m_closed;
private readonly byte[] m_buffer;
private int m_read;
private int m_write;
#endregion
#region Ctors
public CircularStream(int capacity)
{
m_buffer = new byte[capacity];
}
public CircularStream(Action<Stream> action)
: this(DefaultCapacity)
{
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
action(this);
}
finally
{
this.Dispose();
}
});
}
#endregion
#region Overrides
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return true; }
}
public override void Flush() { }
public override long Length
{
get { throw new NotSupportedException(); }
}
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
protected override void Dispose(bool disposing)
{
lock (SyncRoot)
{
m_closed = true;
Monitor.Pulse(SyncRoot);
}
base.Dispose(disposing);
}
public override int Read(byte[] buffer, int offset, int count)
{
lock (SyncRoot)
{
while (m_read == m_write)//empty
if (m_closed)
return 0;
else
Monitor.Wait(SyncRoot, 100, true);
int c;
if (m_write > m_read)
c = m_write - m_read;
else
c = m_buffer.Length - m_read;
Monitor.Pulse(SyncRoot);
if (c > count)
{
Buffer.BlockCopy(m_buffer, m_read, buffer, offset, count);
m_read += count;
return count;
}
else
{
Buffer.BlockCopy(m_buffer, m_read, buffer, offset, c);
m_read = (m_read + c) % m_buffer.Length;
return c;
}
}
}
public override void Write(byte[] buffer, int offset, int count)
{
lock (SyncRoot)
{
if (m_closed)
throw new ObjectDisposedException("CircleBufferStream");
while (count > 0)
{
while ((m_write + 1) % m_buffer.Length == m_read)// full
Monitor.Wait(SyncRoot, 100, true);
if (m_read > m_write)
{
int c = Math.Min(m_read - m_write - 1, count);
Buffer.BlockCopy(buffer, offset, m_buffer, m_write, c);
m_write += c;
count -= c;
offset += c;
}
else
{
int c = m_buffer.Length - m_write;
if (m_read == 0)
--c;
if (c > count)
{
Buffer.BlockCopy(buffer, offset, m_buffer, m_write, count);
m_write += count;
count = 0;
}
else
{
Buffer.BlockCopy(buffer, offset, m_buffer, m_write, c);
m_write = (m_write + c) % m_buffer.Length;
count -= c;
offset += c;
}
}
Monitor.Pulse(SyncRoot);
}
}
}
#endregion
#region Properties
public int Capacity { get { return m_buffer.Length; } }
#endregion
}
: Stream
{
#region Fields
private const int DefaultCapacity = 0x10000;
private readonly object SyncRoot = new object();
private bool m_closed;
private readonly byte[] m_buffer;
private int m_read;
private int m_write;
#endregion
#region Ctors
public CircularStream(int capacity)
{
m_buffer = new byte[capacity];
}
public CircularStream(Action<Stream> action)
: this(DefaultCapacity)
{
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
action(this);
}
finally
{
this.Dispose();
}
});
}
#endregion
#region Overrides
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return true; }
}
public override void Flush() { }
public override long Length
{
get { throw new NotSupportedException(); }
}
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
protected override void Dispose(bool disposing)
{
lock (SyncRoot)
{
m_closed = true;
Monitor.Pulse(SyncRoot);
}
base.Dispose(disposing);
}
public override int Read(byte[] buffer, int offset, int count)
{
lock (SyncRoot)
{
while (m_read == m_write)//empty
if (m_closed)
return 0;
else
Monitor.Wait(SyncRoot, 100, true);
int c;
if (m_write > m_read)
c = m_write - m_read;
else
c = m_buffer.Length - m_read;
Monitor.Pulse(SyncRoot);
if (c > count)
{
Buffer.BlockCopy(m_buffer, m_read, buffer, offset, count);
m_read += count;
return count;
}
else
{
Buffer.BlockCopy(m_buffer, m_read, buffer, offset, c);
m_read = (m_read + c) % m_buffer.Length;
return c;
}
}
}
public override void Write(byte[] buffer, int offset, int count)
{
lock (SyncRoot)
{
if (m_closed)
throw new ObjectDisposedException("CircleBufferStream");
while (count > 0)
{
while ((m_write + 1) % m_buffer.Length == m_read)// full
Monitor.Wait(SyncRoot, 100, true);
if (m_read > m_write)
{
int c = Math.Min(m_read - m_write - 1, count);
Buffer.BlockCopy(buffer, offset, m_buffer, m_write, c);
m_write += c;
count -= c;
offset += c;
}
else
{
int c = m_buffer.Length - m_write;
if (m_read == 0)
--c;
if (c > count)
{
Buffer.BlockCopy(buffer, offset, m_buffer, m_write, count);
m_write += count;
count = 0;
}
else
{
Buffer.BlockCopy(buffer, offset, m_buffer, m_write, c);
m_write = (m_write + c) % m_buffer.Length;
count -= c;
offset += c;
}
}
Monitor.Pulse(SyncRoot);
}
}
}
#endregion
#region Properties
public int Capacity { get { return m_buffer.Length; } }
#endregion
}
加上配套的契约和实现:
契约
[ServiceContract]
public interface IService1
{
[OperationContract]
Stream GetStream(int value);
}
public interface IService1
{
[OperationContract]
Stream GetStream(int value);
}
实现
public class Service1 : IService1
{
public Stream GetStream(int value)
{
return new CircularStream(s =>
new XStreamingElement("root",
from i in Enumerable.Range(1, value)
select new XElement("item",
new XAttribute("id", i))).Save(s));
}
}
{
public Stream GetStream(int value)
{
return new CircularStream(s =>
new XStreamingElement("root",
from i in Enumerable.Range(1, value)
select new XElement("item",
new XAttribute("id", i))).Save(s));
}
}
以及调用的服务和客户端:
服务和客户端
static class Program
{
static void Main(string[] args)
{
var host = new ServiceHost(typeof(Service1));
host.Open();
var binding= new BasicHttpBinding();
binding.TransferMode=TransferMode.StreamedResponse;
binding.MaxReceivedMessageSize = int.MaxValue;
// 如果觉得不过瘾,可以打开循环爽一把
//for (int i = 0; i < 100; i++)
//{
new Uri("http://localhost:12123/").InvokeWcfClient<IService1>(binding, c =>
{
var stream = c.GetStream(10000);
string line;
using (var r = new StreamReader(stream))
while (null != (line = r.ReadLine()))
Console.WriteLine(line);
});
//}
host.Close();
}
public static void InvokeWcfClient<TChannel>(
this Uri uri, Binding binding, Action<TChannel> action)
where TChannel : class
{
TChannel client = ChannelFactory<TChannel>.CreateChannel(
binding, new EndpointAddress(uri));
var co = (ICommunicationObject)client;
try
{
co.Open();
action(client);
}
finally
{
if (co.State == CommunicationState.Faulted)
co.Abort();
else
co.Close();
}
}
}
{
static void Main(string[] args)
{
var host = new ServiceHost(typeof(Service1));
host.Open();
var binding= new BasicHttpBinding();
binding.TransferMode=TransferMode.StreamedResponse;
binding.MaxReceivedMessageSize = int.MaxValue;
// 如果觉得不过瘾,可以打开循环爽一把
//for (int i = 0; i < 100; i++)
//{
new Uri("http://localhost:12123/").InvokeWcfClient<IService1>(binding, c =>
{
var stream = c.GetStream(10000);
string line;
using (var r = new StreamReader(stream))
while (null != (line = r.ReadLine()))
Console.WriteLine(line);
});
//}
host.Close();
}
public static void InvokeWcfClient<TChannel>(
this Uri uri, Binding binding, Action<TChannel> action)
where TChannel : class
{
TChannel client = ChannelFactory<TChannel>.CreateChannel(
binding, new EndpointAddress(uri));
var co = (ICommunicationObject)client;
try
{
co.Open();
action(client);
}
finally
{
if (co.State == CommunicationState.Faulted)
co.Abort();
else
co.Close();
}
}
}
这样就可以完美运行了