WCF在跨域传输使用了两种模型的方法调用:一种是同步模型,这种模型显然对那些需要大量操作时间的方法调用(如从数据库中获取大量数据时)是一种痛苦的选择。另一种是异步模型的方法调用,这种模型是一种非阻塞方法,其方法调用期间并不等到方法调用结束获得结果才返回,而是方法调用一经开始就马上返回,程序可以继续向前执行,被调用方法和主程序同时执行,在调用方法结束才返回结果。显然这种模型给了我们很好的编程和使用体验。
基于WCF在普通的编码是以文本编码方式在信道之间传输信息的,这种编码会把所有的二进制信息以字节数组的形式存储,并以Base64进行编码,而Base64则是用三个字节来储存4 个字符信息。使得数据量增大约30%以上。在WCF中引入了一种专门针对数据流进行优化编码的MTOM模型。下面我们使用编码模型和调用模型三种方式来改写文件流的传输,以提高WCF应用程序的性能。
1、 MTOM模型:
这模型在于将SOAP消息编码成SOAP MT OM(消息传输优化机制)编码。这种编码是为那些包含大量的二进制数据的SOAP消息而做的,它是把数据流作为SOAP消息的附件而添加的。所以利用这种编码在传输信道之间传输可以显著提高传输性能。在WCF中MTOM模型的操作契约中只能使用单个Stream对象作为参数或者返回类型。
这种模型的特点如图所示:
1.1实现服务契约
服务契约是服务所支持的操作、使用的消息交换模式和每一则消息的格式,它控制消息被格式化的方式,在这里由于要使用MTOM编码消息,所以在操作契约中必须要以单一的Stream对象为输入输出参数。所以这儿我们把服务定义为如下的形式:
[ServiceContract]
public interface ISendStreamService
{
[OperationContract]
void SendStream(Stream stream);
//这个方法的是为了传递文件的参数而设的
[OperationContract]
void FileNameSetting(string filename, string destinationpath);
}
另外我们还定义了一个传输文件路径的名称的辅助方法:FileNameSetting();
1.2实现服务器方法
在上面定义了公共的接口后,接下来我们就实现接口的方法,主要的方法的目的是为了传输Stream对象,由于Stream是一个抽象类,所以这儿以文件流为操作对象来使用SendStream()这个方法。
public class SendStreamService : ISendStreamService
{
static FileStream outStream = null;
static int startLength;
static int fileLength;
static int maxBytesCount=4096;
static byte[] bytes = new byte[int maxBytesCount];
string filePath;
static string fileName;
public void SendStream(System.IO.Stream stream)
{
string file = filePath + "//" + fileName;
outStream = new FileStream(file, FileMode.OpenOrCreate, FileAccess.Write);
try {
while ((startLength = stream.Read(bytes, 0, int maxBytesCount)) > 0){
outStream.Write(bytes, 0, startLength);
fileLength += startLength;
}
}
catch (Exception e) { }
}
public void FileNameSetting(string filename,string destinationpath)
{
fileName = filename;
filePath = destinationpath;
}
}
1.3客户通过接口调用服务器方法
客户端调用服务器方法至少有三种,这里我们选择工厂方法来实现,System.ServiceMode.Channel.ChannelFactory<T>类是这个信道工厂类,它的方法CreateChannel()可以创建T的实例。
ISendStreamService proxy=new
ChannelFactory<ISendStreamService>(“WSHttpBinding_ISendStreamService”).Create-
Channel();
proxy.FileNameSetting(file.Substring (file.LastIndexOf ("\\")+1), filePath);
proxy.SendStream(inStream);
1.4服务器和客户端的配置信息
配置信息定义了双方通信的终结点、绑定、契约行为及其他的配置如安全,可靠性等。服务器的配置如:
<service behaviorConfiguration="SendStreamServiceBehavior"
name="SendStreamService">
<endpoint address=" http://localhost:5504/WebSite2/ISendStreamService "
binding="wsHttpBinding" bindingConfiguration="MTMOBinding"
contract="ISendStreamService">
</endpoint>
<bindings>
<wsHttpBinding>
<binding name="MTMOBinding" messageEncoding="Mtom">
</wsHttpBinding>
</bindings>
</service>
同样客户端的配置如:
<client>
<endpoint address="http://localhost:5504/WebSite2/ISendStreamService"
binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_ISendStreamService"
contract="ServiceReference1.ISendStreamService" name="WSHttpBinding_ISendStreamService">
</endpoint>
</client>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_ISendStreamService"
messageEncoding="Mtom" textEncoding="utf-8" >
</binding>
</wsHttpBinding>
</bindings>
注意:在这种方式下使用同步和异步方法没有明显的差别,后来我在分析了Windows Trace Viewer的消息包,发现在用异步方法时,整个过程只用两个消息来回,这就意味着第一次的SOAP包是在把SOAP消息加上MTOM编码的文件流作为附件一起发送的,在等待文件传输完成后才会返回一个加高消息给方法。也就是说异步方法IAsyncResult Begin*(params parameters,AsyncCallback callback,object state)是在发送第一个SOAP包,并等待服务器接收完第一个包后回应消息包才会返回的。由于在发送文件流时,因为文本字符始终不会超过一个SOAP包而必须等待。所在在这种编码方式下异步调用和同步调用没有差别。
2、 基于同步传输的异步回调模型:
同步传输是指方法在调用过程中一直阻塞到方法调用结束返回结果才会让程序继续向前执行,这种行为比较耗费资源,因为网络访问在等待方法完成的时间内是阻塞的。而且如果远程对象的调用时花费的时间会更长,所以这种时间的浪费让人是不可接受的,这在大文件传输中尤为明显。于是一种让方法的异步调用的机制便产生了。这种方法的内部处理中使用线程池中的一个线程接管这个调用,程序可以获得异步调用的返回信息而继续向前执行。
WCF编程模型中采用了一种让同步传输中使用异步回调的方式来提高应用程序的响应。具体是在每个操作契约中可以选择生成异步方法的调用,具体是在同步方法的前面加上
IAsyncResult Begin…..(params param,AsyncCallback,object state)形式表明这是一个异步调用。并且生成相应的void End……(IAsyncResult state)来返回结果。
2.1定义契约和实现相应的同步方法
这里在服务契约中定义了相应的同步方法,用这个调用FileStream类的同步方法Read()和Write()方法对文件进行读写操作,以实现将文件传输到服务的机器上。这里在服务契约中通过设置属性CallbackContract来实现客户端的回调功能。来其相应的代码如下:
[ServiceContract(CallbackContract = typeof(IUploadCallback))]
public interface IUploadFileService
{
//同步传输的接口
[OperationContract]
void FileUpload(string localFilePath, string netPath);
}
public interface IUploadCallback
{
[OperationContract(IsOneWay = true)]
void ReportFileUpload(int length);//这个回调函数是文件传输完成时发布一个通知
}
//实现文件读写的服务器方法
public class UploadFileService : IUploadFileService
{
static int startLength;
static int fileLength;
static int bytesLength;
static byte[] bytes;
static int maxLength=4096;
static FileStream inStream = null;
static FileStream outStream = null;
IUploadCallback client = null;
public void FileUpload(string localFilePath, string netPath)
{
//获得客户端代理的回调
client = OperationContext.Current.GetCallbackChannel<IUploadCallback>();
//得到原始文件名
string fileName = localFilePath.Substring(localFilePath.LastIndexOf("\\") + 1);
string netFile = netPath + fileName;
bytes = new byte[maxLength];//设置缓冲区
try
{
outStream = new FileStream(netFile, FileMode.OpenOrCreate, FileAccess.Write);
inStream = File.OpenRead(localFilePath);//打开文件读
int length;
while ((length = inStream.Read(bytes, 0, maxLength)) > 0)
{
fileLength += length;
outStream.Write(bytes, 0, length);
}
}
catch (Exception e)
{
Console.WriteLine("文件上传错误:" + e.Message);
inStream.Close();
outStream.Close();
}
finally
{
client.ReportFileUpload(fileLength);//使用回调报告文件的状态
inStream.Close();
outStream.Close();
}
}
2.2在客户端调用方法
在客户端调用BeginFileUpload()和EndFileUpload()方法来实现客户端的异步回调。并在这些方法完成后服务调用客户回调ReportFileUpload()报告给客户端相应的信息。
3、 基于异步传输的异步模型:
在同步方式处理中,文件传输的时间是和文件的长度密切相关的,对于一个大容量的文件传输,如果全部在主线程中执行,那么应用程序可能会等待很长的时间,因此我们给予文件流以异步方法读写的方法来实现性能的改进。这只调用了文件操作的异步处理。第二种模式一样这也是采用线程池来完成的。这实际上是利用了文件流的异步方法。
在这儿我们仍然使用第二种模型的WCF框架,只是我们这儿使用了FileStream对象BeginWrite();BeginRead()方法及相应的EndWrite();EndRead()方法。这儿我们只给出了服务器的方法实现:
public void AsyncFileUpload(string localFilePath, string netPath)
{
//获得客户端代理的回调
client = OperationContext.Current.GetCallbackChannel<IUploadCallback>();
//得到原始文件名
string fileName = localFilePath.Substring(localFilePath.LastIndexOf("\\") + 1);
string netFile = netPath + fileName;
bytes = new byte[maxLength];//设置缓冲区
try{
outStream = new
FileStream(netFile, FileMode.OpenOrCreate, FileAccess.Write);
inStream = File.OpenRead(localFilePath);//打开文件读
inStream.BeginRead(bytes, 0, maxLength, CallbackOnRead, null);
}
catch (Exception e){
inStream.Close();
outStream.Close();
}
}
void CallbackOnRead(IAsyncResult result)
{
int length =inStream.EndRead(result);
if (length >= 0){
fileLength += length;
outStream.BeginWrite(bytes, 0, length, CallbackOnWrite, null);
}
else{
client.ReportFileUpload(fileLength);//使用回调
inStream.Close();
outStream.Close();
}
}
void CallbackOnWrite(IAsyncResult result){
outStream.EndWrite(result);
inStream.BeginRead(bytes, 0, maxLength, CallbackOnRead, null);
}
通过以上的分析, 基于MTOM编码的文件流传输时,可以提高传输性能,而对于后两种方式的前提是必须是普通的文本消息编码才会有效果,才可以提高程序的响应性能。也就是说后两种方式只是一种提高WCF应用程序响应性能的方式,它的传输数据量会有明显的膨胀。具体设计中要看在传输效率和响应性能两者取舍来选取其一而用。