WCF足跡5:流

由於上一篇文章的內容太長,無法保存,只好把“流”這一部份單獨寫在一篇文章中。
通常情況下,在服務端與客戶端交換信息的時候,消息會在接受端進行緩存,等消息全都接收完成後再一起進行處理。不管是客戶端向服務端發送消息,還是服務端向客戶端發送消息都是如此。當客戶端調用服務時,要阻塞客戶單進程,直到消息發送完畢,服務端纔開始處理數據,然後是返回處理完畢的結果給客戶端,客戶端接收完畢,才能解除阻塞。這樣帶來的問題是當消息傳遞的時間很短,相對處理時間可以忽略不計,不會影響系統服務的效率。但是要是消息數據很大(比如是圖片或者多媒體對象)每次傳輸時間相對較大,這樣接收端的等待時間過久,勢必每次阻塞都會很長,進程無法繼續執行。因而導致效率低下。
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是可以實現流數據傳輸的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章