WCF足迹5:流

来源:互联网 发布:游戏ui网络班 编辑:程序博客网 时间:2024/04/30 16:12

由于上一篇文章的内容太长,无法保存,只好把“流”这一部份单独写在一篇文章中。
通常情况下,在服务端与客户端交换信息的时候,消息会在接受端进行缓存,等消息全都接收完成后再一起进行处理。不管是客户端向服务端发送消息,还是服务端向客户端发送消息都是如此。当客户端调用服务时,要阻塞客户单进程,直到消息发送完毕,服务端才开始处理数据,然后是返回处理完毕的结果给客户端,客户端接收完毕,才能解除阻塞。这样带来的问题是当消息传递的时间很短,相对处理时间可以忽略不计,不会影响系统服务的效率。但是要是消息数据很大(比如是图片或者多媒体对象)每次传输时间相对较大,这样接收端的等待时间过久,势必每次阻塞都会很长,进程无法继续执行。因而导致效率低下。
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是可以实现流数据传输的。