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
}
加上配套的契約和實現:
契約 [ServiceContract]
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));
}
}
以及調用的服務和用戶端:
服務和用戶端 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();
}
}
}
這樣就可以完美運行了