[愛心連接:拯救一個25歲身患急性白血病的女孩[內有蘇州電視臺經濟頻道《每天山海經》爲此錄製的節目視頻(蘇州話)]]消息做爲WCF進行通訊的惟一媒介,最終須要經過寫入傳輸層進行傳遞。而對消息進行傳輸的一個前提或者是一項必不可少的工做是對消息進行相應的編碼。WCF提供了一系列可供選擇的編碼方式,它們分別在互操做和性能各具優點。在本篇文章咱們將對各類編碼方式進行消息的討論。 html
從互操做性的角度來看,編碼方法很大程度上決定了跨平臺支持的能力。有的編碼方式是平臺無關的,有的則僅限於某種特定的平臺。WCF提供了3種典型的編碼方式:Binary、Text和MTOM。Binrary以二進制的方式進行消息的編碼,可是僅限於.NET平臺之間的通訊;Text則提供平臺無關的基於文本的編碼方式。MTOM編碼基於WS-MTOM規範,對於改善大規模二進制數據在SOAP消息的傳輸性能具備重大的意義,既然該編碼方式遵循相應的規範,無疑這也是一種跨平臺的編碼方式。 數組
在正式介紹WCF消息編碼以前,咱們頗有必要了解以下幾個實現編碼的核心對象:XmlDictionary、XmlDictionary和XmlDIctionaryWriter。 網絡
1、XmlDictionarysession
XmlDictionary,顧名思義,它是一個字典,它是從事編碼和解碼雙方共享的一份「詞彙表」。這樣的說法可能有點抽象,咱們不妨作一個類比。好比我說「WCF是.NET平臺下基於SOA的消息通訊框架」,對於各位讀者來講,這句話很好理解。若是我向另外一個對計算機一竅不通的人說這句話,毫無疑問,對方是不管如何不能理解的。讀者和我之間之因此可以經過這樣的語言進行交流,是由於咱們之間具備類似的知識背景,在咱們之間共享相同的詞彙表,對每一個單詞的含義具備一致的理解。而別人不能理解,是在於我和他之間的信息不對稱,若是要使它能都理解,我必須用他所能理解的方式進行交流,在這種情形之下,我可能要花不少文字對這句話的一些術語進行詳細的解釋,好比什麼是.NET平臺,什麼是SOA,什麼又是通訊框架。因此,交流的前提是雙方具備相同的「詞彙表」,雙方就某個主題共享越多的「詞彙」,交流就越容易,你說的話將越簡潔。 app
數據的編碼也像咱們平常的溝通和交流同樣,編碼的一方是「說」的一方,解碼的一方是「聽」的一方。說的一方按照它所掌握的「詞彙表」對信息進行編碼,對方只有具備相同的「詞彙表」才能正常地解碼。若是這個「詞彙表」越詳盡,編碼後的內容容量就越小。內容的濃縮意味着什麼?意味着網絡流量的減小,意味着爲你節省更多的帶寬。而XmlDictionary就是這樣的一個詞彙表。 框架
XmlDictionary定義在System.Xml命名空間下,它是System.Xml.XmlDictionaryString的集合。XmlDictionaryString至關於一個KeyValuePair<int,string>對象,是一個鍵-值對,鍵和值的類型爲int和string。下面是XmlDictionaryString和XmlDictionary的定義。分佈式
1: public class XmlDictionaryString
2: {
3: //其餘成員
4: public XmlDictionaryString(IXmlDictionary dictionary, string value, int key);
5: public IXmlDictionary Dictionary { get; }
6: public static XmlDictionaryString Empty { get; }
7: public int Key { get; }
8: public string Value { get; }
9:
10: }
1: public class XmlDictionary : IXmlDictionary
2: {
3:
4: //其餘成員
5: public XmlDictionary();
6: public XmlDictionary(int capacity);
7: public virtual XmlDictionaryString Add(string value);
8:
9: public virtual bool TryLookup(int key, out XmlDictionaryString result);
10: public virtual bool TryLookup(string value, out XmlDictionaryString result);
11: public virtual bool TryLookup(XmlDictionaryString value, out XmlDictionaryString result);
12: }
經過下面的代碼,建立了一個XmlDictionary對象,經過Add方法添加了3個XmlDictionaryString。嚴格說來XmlDictionary並非一個集合對象,由於它沒有實現IEnumerable接口。經過Add方法你只能指定XmlDictionaryString的Value,Key的值會以自增加的方式自動賦上。因此Customer、Name、Company 3個元素的Key分別爲0,1,2,這能夠從最終輸出結果中看出來。post
1: IList<XmlDictionaryString> dictionaryStringList = new List<XmlDictionaryString>();
2: XmlDictionary dictionary = new XmlDictionary();
3: dictionaryStringList.Add(dictionary.Add("Customer"));
4: dictionaryStringList.Add(dictionary.Add("Name"));
5: dictionaryStringList.Add(dictionary.Add("Company"));
6: foreach (XmlDictionaryString dictionaryString in dictionaryStringList)
7: {
8: Console.WriteLine("Key:{0}\tValue:{1}", dictionaryString.Key, dictionaryString.Value);
9: }
輸出結果:性能
1: Key:0 Value:Customer
2: Key:1 Value:Name
3: Key:2 Value:Company
2、XmlDictionaryWriter優化
System.Xml.XmlDictionaryWriter和後面介紹的System.Xml.XmlDictionaryReader,在WCF編碼(解碼)過程當中具備舉足輕重的地位,由於最終的編碼和解碼工做分別落在這個兩個類上面。XmlDictionaryWriter將XML InfoSet進行編碼寫入到流中,而XmlDictionaryReader將數據從流中讀出並進行解碼,生成相應的XML InfoSet。
XmlDictionaryWriter是一個繼承自System.Xml.XmlWriter的抽象類,WCF中定義了一系列具體的XmlDictionaryWriter,它們直接或者間接地繼承自XmlDictionaryWriter,爲編碼和解碼提供了不一樣的實現。典型的XmlDictionaryWriter包括如下3個:
上面3個類型定義在System.Runtime.Serialization 程序集的internal類型,因此不通直接使用。XmlDictionaryWriter定義了一系列的工廠方法以方便開發者建立這些對象。其中上面3種類型XmlDictionaryWriter對應的工廠方法分別爲:CreateTextWriter、CreateBinaryWriter和CreateMtomWriter。
1: public abstract class XmlDictionaryWriter : XmlWriter
2: {
3: //其餘成員
4: public static XmlDictionaryWriter CreateBinaryWriter(Stream stream);
5: public static XmlDictionaryWriter CreateBinaryWriter(Stream stream, IXmlDictionary dictionary);
6: public static XmlDictionaryWriter CreateBinaryWriter(Stream stream, IXmlDictionary dictionary, XmlBinaryWriterSession session);
7: public static XmlDictionaryWriter CreateBinaryWriter(Stream stream, IXmlDictionary dictionary, XmlBinaryWriterSession session, bool ownsStream);
8:
9: public static XmlDictionaryWriter CreateDictionaryWriter(XmlWriter writer);
10:
11: public static XmlDictionaryWriter CreateMtomWriter(Stream stream, Encoding encoding, int maxSizeInBytes, string startInfo);
12:
13: public static XmlDictionaryWriter CreateMtomWriter(Stream stream, Encoding encoding, int maxSizeInBytes, string startInfo, string boundary, string startUri, bool writeMessageHeaders, bool ownsStream);
14: public static XmlDictionaryWriter CreateTextWriter(Stream stream);
15: public static XmlDictionaryWriter CreateTextWriter(Stream stream, Encoding encoding);
16: public static XmlDictionaryWriter CreateTextWriter(Stream stream, Encoding encoding, bool ownsStream);
17: }
這3種類型的XmlDictionaryWriter表明了WCF目前支持的3種典型的消息編碼方式:Text、Binary和MTOM。接下來,咱們將經過一個個具體的例子,來比較這3種不一樣的XmlDictionaryWriter通過編碼後,產生的內容到底有何不一樣。
一、XmlUTF8TextWriter(CreateTextWriter)
因爲基於純文本的編碼是平臺無關的,故而可以爲不一樣的廠商所支持,這和SOA跨平臺的互操做的主張一致,因此基於文本的編碼是最爲經常使用的編碼方式。WCF的BasicHttpBinding、WsHttpBinding以及WsDualHttpBinding都採用基於文本的編碼。在WCF中,全部基於文本的編碼工做最終都落在XmlUTF8TextWriter上面,因爲該類是一個內部類型,咱們只能經過XmlDictionaryWriter提供的3個靜態工廠方法CreateTextWriter來建立XmlUTF8TextWriter對象。CreateTextWriter方法的參數stream即是通過編碼的二進制數組須要寫入的流;encoding代表採用的字符編碼方式,在這裏只有兩種類型的字符編碼是支持的:UTF8和Unicode,這從XmlUTF8TextWriter的命名就能夠看出來;至於ownsStream,代表XmlUTF8TextWriter對象是否擁有對應的stream對象,若是是true,則代表XmlUTF8TextWriter是stream的擁有者,XmlUTF8TextWriter關閉將伴隨着stream的關閉,默認爲true。
1: public abstract class XmlDictionaryWriter : XmlWriter
2: {
3: //其餘成員
4: public static XmlDictionaryWriter CreateTextWriter(Stream stream);
5: public static XmlDictionaryWriter CreateTextWriter(Stream stream, Encoding encoding);
6: public static XmlDictionaryWriter CreateTextWriter(Stream stream, Encoding encoding, bool ownsStream);
7: }
下面是一個簡單地使用XmlUTF8TextWriter進行編碼的例子。在這裏我使用XmlDictionary的CreateTextWriter方法建立XmlUTF8TextWriter對象,對一個簡單的XML文檔(文檔中僅僅具備一個XML元素)進行編碼,而後輸出通過編碼後的字節長度、二進制表示和以文本顯示的文檔內容。代碼後面是真實的輸出。
1: MemoryStream stream = new MemoryStream();
2: using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(stream,Encoding.UTF8))
3: {
4: writer.WriteStartDocument();
5: writer.WriteElementString("Customer", "http://www.artech.com/", "Foo");
6: writer.Flush();
7:
8: long count = stream.Position;
9: byte[] bytes = stream.ToArray();
10: StreamReader reader = new StreamReader(stream);
11: stream.Position = 0;
12: string content = reader.ReadToEnd();
13:
14: Console.WriteLine("字節數爲:{0}", count);
15: Console.WriteLine("編碼後的二進制表示爲:\n{0}", BitConverter.ToString(bytes));
16: Console.WriteLine("編碼後的文本表示爲:\n{0}", content);
17: }
輸出結果:
字節數爲:93
編碼後的二進制表示爲:
3C-3F-78-6D-6C-20-76-65-72-73-69-6F-6E-3D-22-31-2E-30-22-20-65-6E-63-6F-64-69-6E-67-3D-22-75-74-66-2D-38-22-3F-3E-3C-43-75-73-74-6F-6D-65-72-20-78-6D-6C-6E-73-3D-22-68-74-74-70-3A-2F-2F-77-77-77-2E-61-72-74-65-63-68-2E-63-6F-6D-2F-22-3E-46-6F-6F-3C-2F-43-75-73-74-6F-6D-65-72-3E
編碼後的文本表示爲:
<?xml version="1.0" encoding="utf-8"?><Customer xmlns="http://www.artech.com/">Foo</Customer>
二、XmlBinaryWriter(CreateBinraryWriter)
XmlBinraryWriter經過二進制的方式進行編碼,因此它可以極大地減小編碼後字節的大小,在進行網絡傳輸的時候可以極大地節約網絡帶寬,得到最好的傳輸性能。可是,這種形式的編碼並不具有跨平臺的特性,僅限於客戶端和服務端採用WCF的應用場景。
爲了演示經過XmlBinaryWriter進行編碼,我將上面的代碼略加改動:經過調用CreateBinaryWriter建立XmlBinaryWriter對象。從最終的輸出結果咱們能夠看出來,較之經過TextUTF8TextWriter,經過XmlBinary編碼後的字節數獲得了極大的壓縮(從原來的93變成了39),壓縮率超過了50%。
1: MemoryStream stream = new MemoryStream();
2: using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream))
3: {
4: //省略成員
5: }
輸出結果:
字節數爲:39
編碼後的二進制表示爲:
40-08-43-75-73-74-6F-6D-65-72-08-16-68-74-74-70-3A-2F-2F-77-77-77-2E-61-72-74-65
-63-68-2E-63-6F-6D-2F-99-03-46-6F-6F
編碼後的文本表示爲:
[省略不可讀的編碼內容]
若是咱們查看XmlDictionaryWriter的WriteElementString方法,會發現其具備5個重載,其中3個是從XmlWriter中繼承下來的(咱們的代碼使用的就是XmlWriter定義的方法),其他兩個是XmlDictionaryWriter自定義成員。與XmlWriter中繼承下來的方法不一樣的是,元素名稱和命名空間經過XmlDictionaryString類型表示。實際上XmlDictionaryWriter的不少方法都同時提供以字符串和XmlDictionaryString表示的XML元素或屬性名稱和命名空間。在本節的開始咱們就說了,XmlDictionary是編碼和解碼雙方共享的「詞彙表」,經過在編碼過程當中有效地使用它,能夠在很大程度上壓縮編碼後的字節數。
1: public abstract class XmlDictionaryWriter : XmlWriter
2: {
3: //其餘成員
4: public void WriteElementString(XmlDictionaryString localName, XmlDictionaryString namespaceUri, string value);
5: public void WriteElementString(string prefix, XmlDictionaryString localName, XmlDictionaryString namespaceUri, string value);
6: }
相應地,XmlDictionary也反映在CreateBinaryWriter靜態方法上面。CreateBinaryWriter方法比CreateTextWriter多了一些重載,其中多了一個IXmlDictionary接口類型的參數dictionary。
1: public abstract class XmlDictionaryWriter : XmlWriter
2: {
3: //其餘成員
4: public static XmlDictionaryWriter CreateBinaryWriter(Stream stream);
5: public static XmlDictionaryWriter CreateBinaryWriter(Stream stream, IXmlDictionary dictionary);
6: public static XmlDictionaryWriter CreateBinaryWriter(Stream stream, IXmlDictionary dictionary, XmlBinaryWriterSession session);
7: public static XmlDictionaryWriter CreateBinaryWriter(Stream stream, IXmlDictionary dictionary, XmlBinaryWriterSession session, bool ownsStream);
8: }
在現有代碼的基礎上,我作了一些修正,先建立XmlDictionary對象,將後面使用到的XML元素名稱(Customer)和命名空間(http://www.artech.com/)定義成相應的XmlDictionaryString,並添加到XmlDictionary中。在調用CreateBinaryWriter的時候指定該XmlDictionary,並在調用WriteElementString方法的時候以DictionaryString的形式制定元素命名和命名空間。若是看了最終的輸出結果,你可能會不敢相信本身的眼睛,字節長度變成了9(93=>39=>9)。之因此使用了XmlDictionary後的編碼可以獲得如此高的壓縮率,就在於元素的名稱和命名空間經過Key-Value的形式表示在了XmlDictionary中,在編碼的時候會將XML中相應的Value內容替換成int型的Key,這樣作固然可以使得壓縮率獲得極大的提高了。
1: XmlDictionary dictionary = new XmlDictionary();
2: IList<XmlDictionaryString> dictionaryStrings = new List<XmlDictionaryString>();
3: dictionaryStrings.Add(dictionary.Add("Customer"));
4: dictionaryStrings.Add(dictionary.Add("http://www.artech.com/"));
5:
6: MemoryStream stream = new MemoryStream();
7: using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream, dictionary))
8: {
9: writer.WriteStartDocument();
10: writer.WriteElementString(dictionaryStrings[0], dictionaryStrings[1], "Foo");
11: writer.Flush();
12: //其餘操做
13: }
輸出結果:
字節數爲:9
編碼後的二進制表示爲:
42-00-0A-02-99-03-46-6F-6F
編碼後的文本表示爲:
[省略不可讀的編碼內容]
三、XmlMtomWriter(CreateMtomWriter)
在不少分佈式應用場景中,咱們會經過SOAP消息傳輸一些大規模的二進制數據,好比咱們上傳文件、圖片、MP3甚至是視頻。對於這些大塊的二進制內容,若是採用Binary的編碼方式,當然可以得到最好的編碼壓縮率,保證數據的快速傳輸,可是卻不能得到跨平臺的能力。若是採用純文本的編碼方式,基於Base64的編碼方式會使編碼後的內容顯得很是冗餘,並且這些冗餘的數據會直接置於SOAP消息的主體中,使得SOAP消息十分龐大,從而影響SOAP消息正常的傳輸。爲了解決這樣的問題,MTOM(Message Transmission Optimization Mechanism)應運而生。MTOM兼具文本編碼的跨平臺能力(由於MTOM是W3C制定一個規範),又具備Binary編碼高壓縮率的優點。要想深刻了解MTOM的消息傳輸優化機制,讀者能夠訪問W3C的官方網站下載相關的文檔。在這裏,我僅僅是對該機制的實現做一個簡單的介紹。
首先,二進制的內容仍然按照Base64的方式進行編碼,而後對包含<xs:base64binary>的元素進行傳輸優化(Transmission Optimization)。咱們能夠視這種優化爲經過一種標準的、高壓縮率的格式對其進行編碼,這種格式是基於XOP(XML-binary Optimizated Packaging)。SOAP消息在被傳輸的時候,經過一種稱爲MIME Multipart/Related XOP Package的形式發送。MIME Multipart/Related XOP Package,XOP是通過對<xs:base64binary>元素進行優化編碼後的數據包,Multipart/Related XOP就是多個關聯的XOP,每一個XOP數據包和SOAP封套(SOAP Envelope)是分開的,XOP並不內嵌於SOAP封套中,它做爲其附件(Attachment)單獨傳送,SOAP封套保留一份XOP數據包的引用。
在WCF中,全部關於MTOM編碼與解碼相關的功能都經過XmlMtomWriter來完成,XmlMtomWriter經過XmlDictionaryWriter的CreateMtomWriter靜態方法建立。當咱們經過XmlMtomWriter對於一個XML Infoset執行寫操做時,最終生成的是一個具備報頭(Header)和主體(Body)的MIME Multipart/Related XOP Package,XML Infoset的內容通過編碼被放到主體部分。參數startInfo表示該XML Infoset對應Content-Type的type屬性,對於SOAP天然就是「Application/soap+xml」,而boundary則表示分隔符,startUri做爲Content-ID,而writeMessageHeaders參數則表示是否寫入MIME Multipart/Related XOP Package的報頭內容。
1: public abstract class XmlDictionaryWriter : XmlWriter
2: {
3: //其餘成員
4: public static XmlDictionaryWriter CreateMtomWriter(Stream stream, Encoding encoding, int maxSizeInBytes, string startInfo);
5: public static XmlDictionaryWriter CreateMtomWriter(Stream stream, Encoding encoding, int maxSizeInBytes, string startInfo, string boundary, string startUri, bool writeMessageHeaders, bool ownsStream);
6: }
接下來我經過一個簡單的例子演示相同的XML元素經過XmlMtomWriter編碼後又將具備怎樣的格式。在現有演示代碼的基礎上,經過調用CreateMtomWriter方法建立XmlMtomWriter,並將startInfo、boundary和startUri分別指定爲"Application/soap+xml"、http://www.artech.com/binary和http://www.artech.com/contentid。從最後的結果咱們能夠看到:整個數據包包含兩個部分:報頭和主體,報頭的主要做用在於指定整個數據包的MIME版本和Content-Type。在Content-Type中multipart/related;type="application/xop+xml"是基於整個數據包,而boundary="http://www.artech.com/binary";start="<http://www.artech.com/ contentid>";start-info="Application/soap+xml"則針對主體部分。
1: MemoryStream stream = new MemoryStream();
2: using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateMtomWriter(stream, Encoding.UTF8, int.MaxValue, "Application/soap+xml", "http://www.artech.com/binary", "http://www.artech.com/contentid", true, true))
3: {
4: //省略操做
5: }
輸出結果:
字節數爲:517
編碼後的二進制表示爲:
4D-49-4D-45-2D-56-65-72-73-69-6F-6E-3A-20-31-(…省略…)0A
編碼後的文本表示爲:
MIME-Version: 1.0
Content-Type: multipart/related;type="application/xop+xml";boundary="http://www.
artech.com/binary";start="<http://www.artech.com/contentid>";start-info="Application/soap+xml"
--http://www.artech.com/binary
Content-ID: <http://www.artech.com/contentid>
Content-Transfer-Encoding: 8bit
Content-Type: application/xop+xml;charset=utf-8;type="Application/soap+xml"
<?xml version="1.0" encoding="utf-8"?>
<Customer xmlns="http://www.artech.com/">Foo</Customer>
--http://www.artech.com/binary—
因爲MTOM只有在針對大規模的二進制數據的傳輸時才能顯示出優化的能力,對於文本內容反而由於多了不少必須的結構化描述信息,使得最終編碼後的數據包都基於純文本編碼方式而冗餘。MOTM對於二進制數據的編碼,我會在後續的部分爲讀者做演示。
3、XmlDictionaryReader
有XmlDictionaryWriter就必然有XmlDictionaryReader,XmlDictionaryWriter對XML Infoset進行編碼並將編碼後的字節寫入流中,而XmlDictionaryReader則讀取二進制流並對其解碼生成相應的XML Infoset。WCF一樣定義了3個具體的XmlDictionaryReader:XmlUTF8TextReader、XmlBinaryReader和XmlMtomReader,他們經過定義在XmlDictionaryReader的靜態方法CreateTextReader、CreateBinaryReader和CreateMtomReader進行建立,在這裏就不一一細說了。