利用WCF改进文件流传输的三种方式

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应用程序响应性能的方式,它的传输数据量会有明显的膨胀。具体设计中要看在传输效率和响应性能两者取舍来选取其一而用。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏菩提树下的杨过

可运行jar包的几种打包/部署方式

java项目开发中,最终生成的jar,大概可分为二类,一类是一些通用的工具类(不包含main入口方法),另一类是可直接运行的jar包(有main入口方法),下面...

5142
来自专栏Python自动化测试

测试驱动之xml文件的处理

Xml是可扩展标记语言,关于xml的技术本人这里不在介绍,感兴趣的同学可以去w3c看看详细的资料,这里,我仅仅介绍的是如何获取xml文档结构中的...

1423
来自专栏葡萄城控件技术团队

程序员级别鉴定书(.NET面试问答集锦)

作为一个.NET程序员,应该知道的不仅仅是拖拽一个控件到设计时窗口中。就像一个赛车手,一定要了解他的爱车 – 能做什么不能做什么。 本文参考Scott Hans...

3757
来自专栏jeremy的技术点滴

py3_cookbook_notes_03

2823
来自专栏cnblogs

knockoutjs 上自己实现的flux

在knockoutjs 上实现 Flux 单向数据流 状态机,主要解决多个组件之间对数据的耦合问题。 一、其实简单 flux的设计理念和实现方案,很大程度上人借...

2208
来自专栏JavaEdge

高性能队列——Disruptor总论1 背景2 Java内置队列3 ArrayBlockingQueue的问题4 Disruptor的设计方案代码样例性能等待策略Log4j 2应用场景

这里所说的队列是系统内部的内存队列,而不是Kafka这样的分布式队列 Disruptor特性限于3.3.4

2063
来自专栏FD的专栏

一步步理解python的异步IO

看到越来越多的大佬都在使用python的异步IO,协程等概念来实现高效的IO处理过程,可是我对这些概念还不太懂,就学习了一下。 因为是初学者,在理解上有很多不到...

1532
来自专栏IT可乐

Spring详解(二)------IOC控制反转

  我相信提到 Spring,很多人会脱口而出IOC(控制反转)、DI(依赖注入)、AOP等等概念,这些概念也是面试官经常问到的知识点。那么这篇博客我们就来详细...

2125
来自专栏分布式系统进阶

Kafka集群Metadata管理Kafka源码分析-汇总

可以看到是调用了ReplicaManager.maybeUpdateMetadataCache方法, 里面又会调用到MetadataCache.updateCa...

2472
来自专栏向前进

【笔记】HybridApp中使用Promise化的JS-Bridge

背景: HybridApp,前端采用JS-bridge的方式调用Native的接口,如获取设备信息、拍照、人脸识别等 前端封装了调用库,每次调用Native接口...

3464

扫码关注云+社区

领取腾讯云代金券