今天繼續學習WCF4.0新特性體驗(8):自定義綁定實作位元組流編碼,也就是簡化位元組流編碼的新特性,Simple byte stream encoding )。WCF4.0之前的版本中已經提供了三種編碼器:Text、Binary 和 MTOM 消息編碼器。但是有些時候我們想傳遞最原始的二進制資料,不進行任何的包裝,怎麼才能實作呢?WCF4.0中給出了一種新的編碼器。今天我們就來學習如何使用這個新的編碼器。本節内容會對WCF消息編碼器、自定義綁定也會做簡要介紹。
【1】WCF編碼器:
我們知道在WCF4.0以前的版本中已經提供了三種編碼器:Text、Binary 和 MTOM 消息編碼器。當然我們也可以實作自定義編碼器。Text消息編碼器同時支援純 XML 編碼和 SOAP 編碼。Text消息編碼器的純 XML 編碼模式稱為 POX(“Plain Old XML”)編碼器,以便與基于文本的 SOAP 編碼進行區分。關于三種編碼器類型如下:
編碼器類型
描述
<a href="http://msdn.microsoft.com/zh-cn/library/system.servicemodel.channels.textmessageencodingbindingelement.aspx" target="_blank">TextMessageEncodingBindingElement</a>
文本消息編碼器,同時支援純 POX 編碼和 SOAP 編碼。使用文本消息編碼器可以與非 WCF 終結點互動操作。
<a href="http://msdn.microsoft.com/zh-cn/library/system.servicemodel.channels.binarymessageencodingbindingelement.aspx" target="_blank">BinaryMessageEncodingBindingElement</a>
二進制消息編碼器,使用精簡二進制格式優化通信, WCF 提供的所有編碼器中性能最佳的編碼器。
<a href="http://msdn.microsoft.com/zh-cn/library/system.servicemodel.channels.mtommessageencodingbindingelement.aspx" target="_blank">MTOMMessageEncodingBindingElement</a>
綁定元素,指定使用 MTOM 編碼的消息的字元編碼和消息版本。MTOM 編碼以文本形式傳輸大多數 XML,但是按原樣傳輸較大的二進制資料塊。就效率而言, MTOM 介于在文本編碼器(最慢)和二進制編碼器(最快)之間。
這些是目前我們使用的主要的三種消息編碼器類型。但無論什麼消息編碼器,它們都是MessageEncodingBindingElement 類的子類,必須實作WriteMessage和ReadMessage方法。這兩個方法是消息編碼的核心實作。《WCF技術内幕》綁定有比較深入的分析。它們的作用如下:
WriteMessage,此方法把Message 對象資料寫入到Stream 對象。
ReadMessage,此方法采從Stream 對象讀取資料,并建構一個新的Message 對象。
【2】位元組流編碼:
WCF4.0提供了一個新的編碼器類型:ByteStreamMessageEncodingBindingElement 。它也是MessageEncodingBindingElement的子類型,
public sealed class ByteStreamMessageEncodingBindingElement : MessageEncodingBindingElement
{......}
但是,沒有定義相對應的綁定支援這個編碼,是以,如果在WCF4.0中,我們想使用位元組流編碼機制,必須自己實作自定義綁定。也就是Creating Custom Binding。
【3】自定義綁定:
這裡我們先要了解自定義綁定的概念,然後再來介紹如何通過自定義綁定來使用位元組流編碼機制。雖然系統提供了許多的綁定,但是WCF架構允許使用者自己定義特定的綁定類型。
我們知道綁定由許多不同的綁定元素(BindingElement)組成,這些具有不同功能的綁定元素(BindingElement)累加起來,組成了一個具有完整功能的綁定(Binding)。
Binding按照功能劃分,可以看到有如下幾個主要層次:事務、可靠性、安全性、編碼和傳輸。
層
綁定元素
必需
事務
TransactionFlowBindingElement
否
可靠性
ReliableSessionBindingElement
安全性
SecurityBindingElement
編碼
文本、二進制、消息傳輸優化機制 (MTOM)、位元組流、自定義
是
傳輸
TCP、HTTP、HTTPS、命名管道(也稱為 IPC)、對等 (P2P)、消息隊列(也稱為 MSMQ)、自定義
這裡隻有編碼和傳輸層元素是必須的,其它的根據綁定要實作的功能來選擇是否添加進來。我們可以檢視一下常見綁定的綁定元素(BindingElement)組成。例如BasicHttpBinding和NetTcpBinding。它們的綁定元素構造如下:
我們能看到為什麼BasicHttpBinding的功能如此簡單,并且支援Text編碼的。而NetTcpBinding相比起來功能更加複雜。比如支援事務、可靠性會話、安全等特性的。這個在《WCF技術内幕》綁定一章裡做過詳細的介紹。
【4】自定義綁定實作簡化位元組流編碼:
下面我們來講解一下代碼的實作過程,這個實作的過程有點複雜。因為要通過自定義綁定來使用ByteStream編碼,還要注意一些限制,必須隻能使用http傳輸。另外還要使用消息契約MessageContract。過程就有些複雜,可能在實際的項目裡,使用的時候,大家都會考慮這個問題。隻能等WCF在下一個版本裡給出正式的支援在使用,也許會簡單很多,也許WCF5.0裡會出現一個新的ByteStreamHttpBinding。這樣開發的時候就會簡單很多。目前我們隻能通過自己的代碼實作。下面我們來分布介紹實作的過程。
【4.1】服務契約:
服務契約裡我們定義了一個操作為UploadImage。代碼如下:
//1.服務契約
[ServiceContract]
public interface IWCFService
{
//操作契約
[OperationContract(Action = "*", ReplyAction = "*")]
Message UploadImage(Message request);
}
服務實作的這個操作,它會再把原始的圖檔傳回給用戶端。通過Message來完成。接受用戶端消息,并傳回消息給用戶端,消息體裡就是圖檔的資料。也就是放在<Binary>圖檔資料</Binary>節點裡的。代碼如下:
//2.服務類,繼承接口。實作服務契約定義的操作
public class WCFService : IWCFService
//實作接口定義的方法
public Message ProcessRequest(Message request)
{
Console.WriteLine("接受圖檔資料");
//設定傳回消息的屬性
HttpResponseMessageProperty httpResponseProperty = new HttpResponseMessageProperty();
httpResponseProperty.Headers.Add("Content-Type", "application/octet-stream");
request.Properties.Add(HttpResponseMessageProperty.Name, httpResponseProperty);
//列印時間
Console.WriteLine("傳回圖檔資料 {0}", DateTime.Now.ToLongTimeString());
return request;
}
這裡的代碼很簡單,就是對請求消息做了一下簡單地轉換,傳回給客戶單,我們隻監控消息的傳回時間。
【4.2】自定義綁定:
這裡我們就假定這個綁定的名字為ByteStreamHttpBinding。這個綁定隻有兩個綁定元素,一個是負責編碼的ByteStreamMessageEncodingBindingElement ,另外一個就是負責傳輸的HttpTransportBindingElement。但是要設定一些屬性。比如最大傳輸消息大小等等。代碼如下:
// Create a custom binding containing two binding elements
//建立自定義綁定,隻能使用HttpTransportBindingElement,并且MessageVersion為None
ByteStreamMessageEncodingBindingElement byteStreamBindingElement = new ByteStreamMessageEncodingBindingElement();
byteStreamBindingElement.ReaderQuotas.MaxArrayLength = 900000;
byteStreamBindingElement.ReaderQuotas.MaxBytesPerRead = 4096;
byteStreamBindingElement.ReaderQuotas.MaxDepth = 64;
byteStreamBindingElement.ReaderQuotas.MaxStringContentLength = 900000;
byteStreamBindingElement.ReaderQuotas.MaxNameTableCharCount = 900000;
HttpTransportBindingElement transport = new HttpTransportBindingElement();
transport.TransferMode = TransferMode.Streamed;
transport.MaxReceivedMessageSize = 900000;
transport.MaxBufferSize = 900000;
CustomBinding binding = new CustomBinding(byteStreamBindingElement, transport);
這個自定義綁定的實作,也可以通過配置檔案來完成。另外也可以封裝在一個單獨的綁定類型ByteStreamHttpBinding。以便重複使用這些代碼。
【4.3】宿主:
宿主使用自定義的綁定來托管WCF服務。代碼如下:
CustomBinding binding = new CustomBinding(byteStreamBindingElement, transport);
// // Add an endpoint using that binding
////使用自定義綁定增加一個終結點
host.AddServiceEndpoint(typeof(WCFService.IWCFService), binding, "ByteStreamWCFService");
//判斷是否以及打開連接配接,如果尚未打開,就打開偵聽端口
if (host.State !=CommunicationState.Opening)
host.Open();
//顯示運作狀态
【4.4】用戶端:
用戶端這裡需要通過Message類型來設定消息。首先我們會從一個犀利哥的圖檔中讀取資料,onst string TestFileName = "./http://www.cnblogs.com/xilige.jpg";這裡處理消息體元素生成工作的就是ByteStreamBodyWriter類型,他繼承自抽象類BodyWriter,重寫了void OnWriteBodyContents(XmlDictionaryWriter writer)方法。這個機制是消息序列化的一個重要步驟。writer會控制每個消息體元素的生成工作。ByteStreamBodyWriter的實作代碼如下:
public class ByteStreamBodyWriter : BodyWriter
string testFileName;
public ByteStreamBodyWriter(string testFileName)
: base(false)
this.testFileName = testFileName;
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
//生成初始節點<Binary>
writer.WriteStartElement("Binary");
//寫資料
FileStream fs = new FileStream(this.testFileName, FileMode.Open);
byte[] tmp = new byte[fs.Length];
fs.Read(tmp, 0, tmp.Length);
writer.WriteBase64(tmp, 0, (int)tmp.Length);
//生成結束标簽</Binary>
writer.WriteEndElement();
fs.Close();
這裡用戶端代碼就是生成一個消息,消息體裡讀取的是犀利哥的圖檔,然後使用自定義綁定建立通道發送消息。代碼如下:
const string TestFileName = "./http://www.cnblogs.com/xilige.jpg";
//ByteStreamBodyWriter writer = new ByteStreamBodyWriter(TestFileName);
Message message = Message.CreateMessage(MessageVersion.None, "*", new ByteStreamBodyWriter(TestFileName));
HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty();
httpRequestProperty.Headers.Add("Content-Type", "application/octet-stream");
message.Properties.Add(HttpRequestMessageProperty.Name, httpRequestProperty);
//// Create a channel using that binding
////建立通道
EndpointAddress address = new EndpointAddress("http://localhost:8000/ByteStreamWCFService");
ChannelFactory<IWCFService> channelFactory = new ChannelFactory<IWCFService>(binding, address);
IWCFService channel = channelFactory.CreateChannel();
Console.WriteLine("Client calling service");
Message result = channel.UploadImage(message);
基本就是這個過程。
【4.5】運作結果:
我們可以啟動宿主,然後單步執行用戶端代碼,看看運作的結果,截圖如下:
我們看到用戶端上傳了一個圖檔,服務端收到以後又傳回給用戶端,犀利哥的這個照片大小為41.474K。并且資料是放在消息體體的節點<Binary>裡。
【5】總結:
這個新的特性,被稱為位元組流編碼的特性,有點讓人無奈,功能是好功能,但是隻提供了一個綁定元素,如果我們要使用的話必須自己實作自定義綁定。這個對于大多數開發者來說,可能有點複雜。這裡還有幾點需要值得注意,就是:
(1)位元組流編碼ByteStreamMessageEncodingBindingElement目前WCF4.0裡隻能通過自定義綁定實作。
(2)ByteStreamMessageEncodingBindingElement 隻能和Http傳輸結合,不支援其他的傳輸。
(3)與流傳輸一樣,位元組流不支援可靠性會話,也不支援消息安全。因為資料交大。加密解密處理會耗費較多的資源。
(5)Message類型設定的一個屬性"application/octet-stream" ,是是與MIME附件規範,這個表示消息内容是二進制檔案。
(6)它與二進制編碼器不同的是,二進制編碼器是對消息資料做精簡二進制編碼,而位元組流編碼則不同,可以傳遞最原始的二進制資料。
參考資料:
5.《WCF技術内幕》:綁定
本文轉自 frankxulei 51CTO部落格,原文連結:http://blog.51cto.com/frankxulei/320272,如需轉載請自行聯系原作者