由於上一篇文章的內容太長,無法保存,只好把“流”這一部份單獨寫在一篇文章中。
通常情況下,在服務端與客戶端交換信息的時候,消息會在接受端進行緩存,等消息全都接收完成後再一起進行處理。不管是客戶端向服務端發送消息,還是服務端向客戶端發送消息都是如此。當客戶端調用服務時,要阻塞客戶單進程,直到消息發送完畢,服務端纔開始處理數據,然後是返回處理完畢的結果給客戶端,客戶端接收完畢,才能解除阻塞。這樣帶來的問題是當消息傳遞的時間很短,相對處理時間可以忽略不計,不會影響系統服務的效率。但是要是消息數據很大(比如是圖片或者多媒體對象)每次傳輸時間相對較大,這樣接收端的等待時間過久,勢必每次阻塞都會很長,進程無法繼續執行。因而導致效率低下。
Streaming流處理就是WCF提供的主要針對大量消息數據處理的一種優化機制。它在發送、接受消息的時候並不會阻塞發送端或接收端。即“一邊接收數據一邊處理數據”
WCF的流處理機制需要使用.NET FrameWork定義的Stream類,在方法契約中對流的使用與傳統的I/O操作一樣。
[ServiceContract]
interface IMyContract
{
[OperationContract]
Stream StreamReply1( );
[OperationContract]
void StreamReply2(out Stream stream);
[OperationContract]
void StreamRequest(Stream stream);
[OperationContract(IsOneWay = true)]
void OneWayStream(Stream stream);
}
Stream可以做爲返回數據、參數、輸出參數的類型。流處理機制在特定的綁定協議中才能使用,目前是BasicHttpBinding, NetTcpBinding, 和NetNamedPipeBinding 支持流處理模型。但是在默認情況下,WCF禁止流處理模式,如果需要進行流處理,得使用TransferMode進行配置,TransferMode爲枚舉類型,其定義如下:
public enum TransferMode
{
//默認,請求信息和響應信息都被緩存處理
Buffered,
//請求和響應信息都使用流的方式處理
Streamed,
//請求信息被流處理,響應信息被緩存處理
StreamedRequest,
//請求信息被緩存處理,響應信息被流處理
StreamedResponse
}
啓用流處理的配置設置
<configuration>
<system.serviceModel>
<client>
<endpoint binding = "basicHttpBinding" bindingConfiguration = "StreamedHTTP"
...
/>
</client>
<bindings>
<basicHttpBinding>
<binding name = "StreamedHTTP" transferMode = "Streamed">
</binding>
</basicHttpBinding>
</bindings>
</system.serviceModel>
</configuration>
流處理在使用http協議時,其默認消息長度是64K,如果希望增加數據長度,需要在配置文件裏重新設置maxReceivedMessageSize。
<bindings>
<basicHttpBinding>
<binding name = "StreamedHTTP" transferMode = "Streamed" maxReceivedMessageSize = "120000">
</binding>
</basicHttpBinding>
</bindings>
流的管理:
當客戶端向服務端發送一個流的請求的時候時候,服務端會從流中讀取內容,客戶端也不知道服務端什麼時處理完流,因此,客戶端不應當關閉流,當服務端處理完流的時候,WCF會自動關閉客戶端的流。
當客戶端接收到服務端的響應流時也是同樣的道理。服務端向客戶端發送流數據,服務端不知道客戶端是否處理完流,客戶端也總是負責關閉響應流的。
示例:服務端提供音樂播放列表,並提供媒體流操作契約。客戶端調用服務播放遠程媒體流。
1.服務端代碼:
[ServiceContract]
public interface IStreamMedia
{
[OperationContract]
string[] GetMediaList();
[OperationContract]
Stream GetMediaStream(string name);
}
public class StreamMedia : IStreamMedia
{
#region IStreamMedia 成員
public string[] GetMediaList()
{
string[] musicList= new string[3];
musicList[0] = "1.wav";
musicList[1] = "2.wav";
musicList[2] = "3.wav";
return musicList;
}
public Stream GetMediaStream(string name)
{
name = @"E:/Proj/TestWCF/Services/Services/bin/Debug/"+name;
if (!File.Exists(name))
{
throw new Exception("不存在媒體文件");
}
FileStream stream = null;
try
{
stream = new FileStream(name, FileMode.Open, FileAccess.Read);
}
catch
{
if (stream != null)
stream.Close();
}
return stream;
}
#endregion
}
服務契約IStreamMedia的定義了兩個方法契約:string[] GetMediaList()和Stream GetMediaStream(string name)。
string[] GetMediaList():返回服務端音樂清單。在這裏我們模擬了一個數組。
Stream GetMediaStream(string name):根據音樂名稱返回音樂流。這個方法中的代碼與普通文件操作方法沒有什麼大的區別,只不過這個方法中沒有關閉流。
2.服務器端的配置文件:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="NewBinding1" maxReceivedMessageSize="90000000" transferMode="Streamed" />
</basicHttpBinding>
<netTcpBinding>
<binding name="NewBinding2" transferMode="Streamed" maxReceivedMessageSize="90000000" />
</netTcpBinding>
</bindings>
<services>
<service behaviorConfiguration="NewBehavior" name="Services.StreamMedia">
<endpoint address="basic" binding="basicHttpBinding" bindingConfiguration="NewBinding1" contract="Services.IStreamMedia" />
<endpoint address="mex" binding="mexHttpBinding" bindingConfiguration="" contract="IMetadataExchange" />
<endpoint address="net.tcp://localhost:8071/tcp" binding="netTcpBinding" bindingConfiguration="NewBinding2" contract="Services.IStreamMedia" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8070/" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
在這裏我們配置了兩個終結點:一個是basicHttpBinding,另一個是netTcpBinding。對於這兩個綁定配置我們都需要修改maxReceivedMessageSize屬性和transferMode="Streamed"。
3.客戶端配置文件
客戶端配置文件一般在“添加服務引用”的時候自動生成,一般不需要我們去關心。但這裏需要注意maxReceivedMessageSize屬性和transferMode="Streamed"。
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IStreamMedia" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="90000000"
messageEncoding="Text" textEncoding="utf-8" transferMode="Streamed"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None" realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
<netTcpBinding>
<binding name="NetTcpBinding_IStreamMedia" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
transactionFlow="false" transferMode="Streamed" transactionProtocol="OleTransactions"
hostNameComparisonMode="StrongWildcard" listenBacklog="10"
maxBufferPoolSize="524288" maxBufferSize="65536" maxConnections="10"
maxReceivedMessageSize="90000000" >
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Transport">
<transport clientCredentialType="Windows" protectionLevel="EncryptAndSign" />
<message clientCredentialType="Windows" />
</security>
</binding>
</netTcpBinding>
</bindings>
<client>
<endpoint address="http://localhost:8070/basic" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IStreamMedia" contract="SR.IStreamMedia"
name="BasicHttpBinding_IStreamMedia" />
<endpoint address="net.tcp://localhost:8071/tcp" binding="netTcpBinding"
bindingConfiguration="NetTcpBinding_IStreamMedia" contract="SR.IStreamMedia"
name="NetTcpBinding_IStreamMedia">
<identity>
<userPrincipalName value="PC-04041316/Administrator" />
</identity>
</endpoint>
</client>
</system.serviceModel>
4.編寫客戶端代碼:
public partial class Form2 : Form
{
SR.StreamMediaClient client = new WindowsFormsApplication1.SR.StreamMediaClient("NetTcpBinding_IStreamMedia");
SoundPlayer player = new SoundPlayer();
public Form2()
{
InitializeComponent();
}
//獲取音樂列表
private void button1_Click(object sender, EventArgs e)
{
listBox1.DataSource = client.GetMediaList();
}
//調用服務,接收音樂流,並一邊接收一邊播放
private void button2_Click(object sender, EventArgs e)
{
if (listBox1.SelectedIndex >= 0)
{
Stream stream = client.GetMediaStream(listBox1.Text);
player.Stream = stream;
player.Play();
}
}
//停止播放
private void button3_Click(object sender, EventArgs e)
{
player.Stop();
}
}
《圖8》
點擊“列表”按鈕獲取音樂列表,選中列表中的音樂,點擊“播放”會聽到音頻播放的聲音,點擊停止,會停止播放音頻,但流的傳輸不會終止。
這個例子演示了大文件通過流下載到客戶的操作,在大文件上傳到服務器端的操作與此類似,只要在服務端定義接收Stream類型的方法就可以了。
雖然網上有的朋友在使用netTcpBinding綁定進行數據傳輸的時候會產生“the socket connection was aborted. this could be caused by an error processing your message or a receive timeout being exceeded by the remote host ...”錯誤信息,但我在測試的時候並沒有產生此問題,故竊以爲《Programming WCF Services》一書中所謂的netTcpBinding是可以實現流數據傳輸的。