现在的位置: 首页 > 综合 > 正文

WCF技术剖析之十九:深度剖析消息编码(Encoding)实现(上篇)

2011年11月19日 ⁄ 综合 ⁄ 共 9405字 ⁄ 字号 评论关闭

[爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道《天天山海经》为此录制的节目视频(苏州话)]]消息作为WCF进行通信的唯一媒介,最终需要通过写入传输层进行传递。而对消息进行传输的一个前提或者是一项必不可少的工作是对消息进行相应的编码。WCF提供了一系列可供选择的编码方式,它们分别在互操作和性能各具优势。在本篇文章我们将对各种编码方式进行消息的讨论。

从互操作性的角度来看,编码方法很大程度上决定了跨平台支持的能力。有的编码方式是平台无关的,有的则仅限于某种特定的平台。WCF提供了3种典型的编码方式:Binary、Text和MTOM。Binrary以二进制的方式进行消息的编码,但是仅限于.NET平台之间的通信;Text则提供平台无关的基于文本的编码方式。MTOM编码基于WS-MTOM规范,对于改善大规模二进制数据在SOAP消息的传输性能具有重大的意义,既然该编码方式遵循相应的规范,无疑这也是一种跨平台的编码方式。

在正式介绍WCF消息编码之前,我们很有必要了解如下几个实现编码的核心对象:XmlDictionary、XmlDictionary和XmlDIctionaryWriter。

一、XmlDictionary

XmlDictionary,顾名思义,它是一个字典,它是从事编码和解码双方共享的一份“词汇表”。这样的说法可能有点抽象,我们不妨做一个类比。比如我说“WCF是.NET平台下基于SOA的消息通信框架”,对于各位读者来说,这句话很好理解。如果我向另一个对计算机一窍不通的人说这句话,毫无疑问,对方是无论如何不能理解的。读者和我之间之所以能够通过这样的语言进行交流,是因为我们之间具有相似的知识背景,在我们之间共享相同的词汇表,对每个单词的含义具有一致的理解。而别人不能理解,是在于我和他之间的信息不对称,如果要使它能都理解,我必须用他所能理解的方式进行交流,在这种情形之下,我可能要花很多文字对这句话的一些术语进行详细的解释,比如什么是.NET平台,什么是SOA,什么又是通信框架。所以,交流的前提是双方具有相同的“词汇表”,双方就某个主题共享越多的“词汇”,交流就越容易,你说的话将越简洁。

数据的编码也像我们日常的沟通和交流一样,编码的一方是“说”的一方,解码的一方是“听”的一方。说的一方按照它所掌握的“词汇表”对信息进行编码,对方只有具有相同的“词汇表”才能正常地解码。如果这个“词汇表”越详尽,编码后的内容容量就越小。内容的浓缩意味着什么?意味着网络流量的减少,意味着为你节省更多的带宽。而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,这可以从最终输出结果中看出来。

   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

二、XmlDictionaryWriter

System.Xml.XmlDictionaryWriter和后面介绍的System.Xml.XmlDictionaryReader,在WCF编码(解码)过程中具有举足轻重的地位,因为最终的编码和解码工作分别落在这个两个类上面。XmlDictionaryWriter将XML InfoSet进行编码写入到流中,而XmlDictionaryReader将数据从流中读出并进行解码,生成相应的XML InfoSet。

XmlDictionaryWriter是一个继承自System.Xml.XmlWriter的抽象类,WCF中定义了一系列具体的XmlDictionaryWriter,它们直接或者间接地继承自XmlDictionaryWriter,为编码和解码提供了不同的实现。典型的XmlDictionaryWriter包括以下3个:

  • XmlUTF8TextWriter:提供基于文本的编码和解码实现;
  • XmlBinaryWriter:提供基于二进制的编码和解码实现;
  • XmlMtomWriter提供基于MTOM(Message Transmission Optimized Mechanism)的编码和解码实现。

上面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经过编码后,产生的内容到底有何不同。

1、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>

2、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: //其他操作

抱歉!评论已关闭.