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