1. 程式人生 > 其它 >利用WCF改進檔案流傳輸的三種方式

利用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應用程式響應效能的方式,它的傳輸資料量會有明顯的膨脹。具體設計中要看在傳輸效率和響應效能兩者取捨來選取其一而用。