WSE3.0框架提供了数据优化传输机制,WSE3.0构建Web服务安全(4):MTOM消息传输优化和文件上传、下载 疑问里进行了介绍。WCF同样也提供了流操作来支持大数据对象的传输和处理优化机制,今天我们WCF分布式开发步步为赢系列的(4):使用流操作(Streaming Operations)优化传输。本节会详细介绍流操作的相关概念、编程实现过程,以及实际开发过程中需要主要的一些问题。本节结构:【1】流处理的概念【2】流处理的特点【3】示例代码分析【4】总结。最后上传本文的示例代码。
Streaming,本文翻译为流处理(张逸兄翻译的Programming WCF Services一书里把这个机制翻译为“流操作”,不存在争议。我选择“流处理”一词的意思,只是想把这个Streaming词形象准确化,动名词,流化处理、流处理,翻译为流操作,初学者会误会认为这个是一个服务操作。因为Streaming只是WCF内建的一个机制。而不是操作。)
我们首先来理解什么是Streaming流处理。
【1】Streaming流处理的概念:
通常情况,客户端和服务端进行交互,传递消息,都是放到接收端的缓存里,待接收完毕后再进行处理。无论接收端是客户端还是服务端都是如此。
【1.1】要解决的问题:
当客户端调用服务时,要阻塞客户单进程,直到消息发送完毕,服务端才开始处理数据,然后是返回处理完毕的结果给客户端,客户端接收完毕,才能解除阻塞。这样带来的问题是当消息传递的时间很短,相对处理时间可以忽略不计,不会影响系统服务的效率。但是要是消息数据很大,比如是图片或者多媒体对象。每次传输时间相对较大,这样接收端的等待时间过久,势必每次阻塞都会很长,进程无法继续执行。因而导致效率低下。
【1.2】Streaming流处理:
Streaming流处理就是WCF提供的主要针对大量消息数据处理的一种优化机制。WCF允许接收端通过通道接受消息的同时,启动对消息数据的处理,这样的过程称为流传输模型。
【2】Streaming流处理的特点:
显然对于处理大量的消息数据而言,流处理机制改善了系统的吞吐量和响应效率。
【2.1】流处理操作定义:
WCF的流处理机制需要使用.NET FrameWork定义的Stream类(它是FileStream, NetworkStream, MemoryStream 的父类)。流处理适用一下场景:
[ServiceContract]
interface
IMyContract
{
[OperationContract]
Stream StreamReply1( );
[OperationContract]
void
StreamReply2(
out
Stream stream);
[OperationContract]
void
StreamRequest(Stream stream);
[OperationContract(IsOneWay
=
true
)]
void
OneWayStream(Stream stream);
}
它可以做为返回数据、参数、输出参数的类型。当然也可以作为单调服务的操作参数。这里使用的参数必须是可序列化的,例如MemoryStream。而FileStream不支持序列化因而不能作为参数或者返回数据的类型。
【2.2】流处理与绑定协议:
流处理机制在特定的绑定协议中才能使用,目前是BasicHttpBinding, NetTcpBinding, 和NetNamedPipeBinding 支持流处理模型。但是在默认情况下,WCF禁止流处理模式。
流传输模式使用使用TransferMode进行配置,TransferMode为枚举类型,其定义如下:
public
enum
TransferMode
{
//
Summary:
//
The request and response messages are both buffered.
Buffered
=
0
,
//
//
Summary:
//
The request and response messages are both streamed.
Streamed
=
1
,
//
//
Summary:
//
The request message is streamed and the response message is buffered.
StreamedRequest
=
2
,
//
//
Summary:
//
The request message is buffered and the response message is streamed.
StreamedResponse
=
3
,
}
只有Streamed模式支持2.1中列举的流处理模式场景。除了直接在服务上配置属性以外,我们还可以再服务的配置文件里定义流传输模式。代码如下:
<
basicHttpBinding
>
<
binding name
=
"
basicHttpBinding
"
receiveTimeout
=
"
10:10:10
"
transferMode
=
"
Streamed
"
maxReceivedMessageSize
=
"
200000
"
>
</
binding
>
</
basicHttpBinding
>
<
netTcpBinding
>
<
binding name
=
"
netTcpBinding
"
receiveTimeout
=
"
10:10:10
"
transferMode
=
"
Streamed
"
maxReceivedMessageSize
=
"
200000
"
>
</
binding
>
</
netTcpBinding
>
此为托管宿主的配置文件,特定的绑定协议,可以配置其传输模式。
【2.3】注意:
流处理在使用http协议时,其默认消息长度是64K,如果希望增加数据长度,需要在配置文件里重新设置。如: maxReceivedMessageSize="200000",具体代码如下:
<
basicHttpBinding
>
<
binding name
=
"
basicHttpBinding
"
receiveTimeout
=
"
10:10:10
"
transferMode
=
"
Streamed
"
maxReceivedMessageSize
=
"
200000
"
>
</
binding
>
</
basicHttpBinding
>
【3】示例代码分析:
这里测试的流处理机制,使用的是处理图片的上传于下载,分别使用Stream和其子类MemoryStream作为参数或者返回消息数据的类型。基本代码演示的是流处理三种模式场景:
【3.1】服务端:
服务契约分别定义了下载数据和上传数据,下载数据使用的类型MemoryStream,上传数据参数类型是是Stream。具体代码如下:
//
1.服务契约
[ServiceContract( Namespace
=
"
http://www.cnblogs.com/frank_xl/
"
)]
public
interface
IWCFService
{
//
操作契约,获取数据流
[OperationContract]
MemoryStream DownLoadStreamData(
string
fileName);
//
操作契约,输出数据流
[OperationContract]
void
DownLoadStreamDataOut(
out
MemoryStream stream,
string
fileName);
//
操作契约,上载数据流,单向操作的消息转换为数据流
[OperationContract(IsOneWay
=
true
)]
void
UpLoadStreamData(Stream stream);
}
//
2.服务类,继承接口。实现服务契约定义的操作
public
class
WCFService : IWCFService
{
//
1实现接口定义的方法,下载文件数据流
public
MemoryStream DownLoadStreamData(
string
fileName)
{
//
Stream stream =
byte
[] file
=
new
byte
[
200000
];
String filePath
=
AppDomain.CurrentDomain.BaseDirectory
+
@"
\
"
+
fileName;
file
=
File.ReadAllBytes(filePath);
MemoryStream memoryStream
=
new
MemoryStream(file);
return
memoryStream;
}
//
2实现接口定义的方法,下载文件数据流
public
void
DownLoadStreamDataOut(
out
MemoryStream stream,
string
fileName)
{
//
Stream stream =
byte
[] file
=
new
byte
[
200000
];
String filePath
=
AppDomain.CurrentDomain.BaseDirectory
+
@"
\
"
+
fileName;
file
=
File.ReadAllBytes(filePath);
MemoryStream memoryStream
=
new
MemoryStream(file);
stream
=
memoryStream;
}
//
3实现接口定义的方法,上传文件数据流
public
void
UpLoadStreamData(Stream stream)
{
//
codes here to deal with the stream Stream stream =
Console.WriteLine(
"
The Stream length is {0}
"
,stream.Length);
}
}
【3.2】托管宿主:
我们分别使用basicHttpBinding和netTcpBinding定义了两个服务终结点,这里不要忘记设置最大接受消息数据大小maxReceivedMessageSize="200000",如果设置较小会导致接受数据超过设定的错误。具体代码如下:
<
system.serviceModel
>
<
services
>
<
service behaviorConfiguration
=
"
WCFService.WCFServiceBehavior
"
name
=
"
WCFService.WCFService
"
>
<
endpoint
address
=
"
http://localhost:8002/WCFService
"
binding
=
"
basicHttpBinding
"
contract
=
"
WCFService.IWCFService
"
>
</
endpoint
>
<
endpoint
address
=
"
net.tcp://localhost:8004/WCFService
"
binding
=
"
netTcpBinding
"
contract
=
"
WCFService.IWCFService
"
>
</
endpoint
>
<
endpoint address
=
"
mex
"
binding
=
"
mexHttpBinding
"
contract
=
"
IMetadataExchange
"
/>
<
endpoint address
=
"
mex
"
binding
=
"
mexTcpBinding
"
contract
=
"
IMetadataExchange
"
/>
<
host
>
<
baseAddresses
>
<
add baseAddress
=
"
http://localhost:8001/
"
/>
<
add baseAddress
=
"
net.tcp://localhost:8003/
"
/>
</
baseAddresses
>
</
host
>
</
service
>
</
services
>
<
behaviors
>
<
serviceBehaviors
>
<
behavior name
=
"
WCFService.WCFServiceBehavior
"
>
<
serviceMetadata httpGetEnabled
=
"
true
"
/>
<
serviceDebug includeExceptionDetailInFaults
=
"
false
"
/>
</
behavior
>
</
serviceBehaviors
>
</
behaviors
>
<
bindings
>
<
basicHttpBinding
>
<
binding name
=
"
basicHttpBinding
"
receiveTimeout
=
"
10:10:10
"
transferMode
=
"
Streamed
"
maxReceivedMessageSize
=
"
200000
"
>
</
binding
>
</
basicHttpBinding
>
<
netTcpBinding
>
<
binding name
=
"
netTcpBinding
"
receiveTimeout
=
"
10:10:10
"
transferMode
=
"
Streamed
"
maxReceivedMessageSize
=
"
200000
"
>
</
binding
>
</
netTcpBinding
>
</
bindings
>
</
system.serviceModel
>
【3.3】客户端:
客户端分别测试数据的上传和下载,使用不同的方法。这里的测试目前
【4】总结:
本文介绍了WCF流处理模型,但是实现代码出现问题。
(1)作为WCF的流处理机制,确实为我们的大规模消息数据传输提供了理想的机制,提高了系统的效率和响应速度。
(2)Stream作为.net类库的内部类型,在.net平台上使用来说较为方便,但是与异构平台的交互势必受到诸多限制,也违背了WCF跨平台的初衷。
(3) 我在调试这个示例代码的过程中遇到了几个错误,基本都整理出来放到WCF分布式开发常见错误里了。使用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 ...这个错误我google国内和国外的一些资料,但是解决的办法都是更换协议。其实是一种很无奈的措施,目前我还没有找出更好的解决办法就是基于netTcpBinding。表面的原因是服务处理超时。但是具体的错误信息没有这样简单。我更换协议以后其他的流服务操作调用出了问题。所以这个只能针对特定的操作有帮助。我把这个错误收集起来供大家参考。也希望发挥大家的作用把这个问题解决。
也许这个错误应该反映给WCF的开发小组,无论国内还是国外的技术论坛,包括MSDN都有人遇到这样的问题,而没有一个最佳的解决方案。这里我对流处理示例代码分别打包,目前都有问题。
<1>流处理机制示例1里代码测试上传文件,成功,下载文件错误。
/Files/frank_xl/WCFServiceStreamingFrankXuLei.rar
<2>这里使用的是字节数组,测试下载文件,下载文件是成功的,上传文件失败。/Files/frank_xl/WCFServiceStreamingByteArrayFrankXuLei.rar
两个失败的原因都是一样,套接字中断,连接超时。WCF分布式开发常见错误解决(10):套接字连接中断,The socket connection was aborted ,我进行了整理,也查找了国外的论坛,没有找到理想的解决办法。我已经尝试了修改接受时间的限制,但是不起作用。我会继续关注这个问题,也希望有兴趣的朋友补充。MSDN论坛上有人提供了解决的方法,但是不理想,更换协议。希望微软WCF的开发、测试小组早日注意这个问题。
参考文章:
1.《Programming WCF Services》;
2.WSE3.0构建Web服务安全(4):MTOM消息传输优化和文件上传、下载