对于WCF应用来说,传输前压缩请求消息和回复消息,不但可以降低网络流量,也可以提高网络传输的性能

一、消息压缩方案
二、用于数据压缩与解压缩组件
三、用于消息压缩与解压的组件
四、用于对请求/回复消息压缩和解压缩的组件
五、将CompressionMessageFormatter用于WCF运行时框架的操作行为
六、查看结构压缩后的消息
七、扩展

 

一、消息压缩方案
  消息压缩在WCF中的实现其实很简单,我们只需要在消息(请求消息/回复消息)被序列化之后,发送之前进行压缩;在接收之后,反序列化之前进行解压缩即可。针对压缩/解压缩使用的时机,有三种典型的解决方案。通过自定义MessageEncoder和MessageEncodingBindingElement 来完成。

1.将编码后的字节流压缩传输

2.创建用于压缩和解压缩的信道

3. 自定义MessageFormatter实现序列化后的压缩和法序列化前的解压缩

这里要介绍的解决方案3。  

二、用于数据压缩与解压缩组件
  我们支持两种方式的压缩,Dflate和GZip。两种不同的压缩算法通过如下定义的CompressionAlgorithm枚举表示。

1     public enum CompressionAlgorithm2     {3         GZip,4         Deflate5     }

而如下定义的DataCompressor负责基于上述两种压缩算法实际上的压缩和解压缩工作。

 1     internal class DataCompressor 2     { 3         public static byte[] Compress(byte[] decompressedData, CompressionAlgorithm algorithm) 4         { 5             using (MemoryStream stream = new MemoryStream()) 6             { 7                 if (algorithm == CompressionAlgorithm.Deflate) 8                 { 9                     GZipStream stream2 = new GZipStream(stream, CompressionMode.Compress, true);10                     stream2.Write(decompressedData, 0, decompressedData.Length);11                     stream2.Close();12                 }13                 else14                 {15                     DeflateStream stream3 = new DeflateStream(stream, CompressionMode.Compress, true);16                     stream3.Write(decompressedData, 0, decompressedData.Length);17                     stream3.Close();18                 }19                 return stream.ToArray();20             }21         }22 23         public static byte[] Decompress(byte[] compressedData, CompressionAlgorithm algorithm)24         {25             using (MemoryStream stream = new MemoryStream(compressedData))26             {27                 if (algorithm == CompressionAlgorithm.Deflate)28                 {29                     using (GZipStream stream2 = new GZipStream(stream, CompressionMode.Decompress))30                     {31                         return LoadToBuffer(stream2);32                     }33                 }34                 else35                 {36                     using (DeflateStream stream3 = new DeflateStream(stream, CompressionMode.Decompress))37                     {38                         return LoadToBuffer(stream3);39                     }40                 }41             }42         }43 44         private static byte[] LoadToBuffer(Stream stream)45         {46             using (MemoryStream stream2 = new MemoryStream())47             {48                 int num;49                 byte[] buffer = new byte[0x400];50                 while ((num = stream.Read(buffer, 0, buffer.Length)) > 0)51                 {52                     stream2.Write(buffer, 0, num);53                 }54                 return stream2.ToArray();55             }56         }57     }

三、用于消息压缩与解压的组件  

  而针对消息的压缩和解压缩通过如下一个MessageCompressor来完成。具体来说,我们通过上面定义的DataCompressor对消息的主体部分内容进行压缩,并将压缩后的内容存放到一个预定义的 元素中(名称和命名空间分别为CompressedBody和http://www.yswenli.net/comporession/),同时添加相应的MessageHeader表示消息经过了压缩,以及采用的压缩算法。对于解压缩,则是通过消息是否具有相应的MessageHeader判断该消息是否经过压缩,如果是则根据相应的算法对其进行解压缩。

具体的实现如下:

 1     public class MessageCompressor 2     { 3         public MessageCompressor(CompressionAlgorithm algorithm) 4         { 5             this.Algorithm = algorithm; 6         } 7         public Message CompressMessage(Message sourceMessage) 8         { 9             byte[] buffer;10             using ( DictionaryReader reader1 = sourceMessage.GetReaderAtBodyContents())11             {12                 buffer = Encoding.UTF8.GetBytes(reader1.ReadOuter ());13             }14             if (buffer.Length == 0)15             {16                 Message emptyMessage = Message.CreateMessage(sourceMessage.Version, (string)null);17                 sourceMessage.Headers.CopyHeadersFrom(sourceMessage);18                 sourceMessage.Properties.CopyProperties(sourceMessage.Properties);19                 emptyMessage.Close();20                 return emptyMessage;21             }22             byte[] compressedData = DataCompressor.Compress(buffer, this.Algorithm);23             string copressedBody = CompressionUtil.CreateCompressedBody(compressedData);24              TextReader reader = new  TextReader(new StringReader(copressedBody), new Na ble());25             Message message2 = Message.CreateMessage(sourceMessage.Version, null, ( Reader)reader);26             message2.Headers.CopyHeadersFrom(sourceMessage);27             message2.Properties.CopyProperties(sourceMessage.Properties);28             message2.AddCompressionHeader(this.Algorithm);29             sourceMessage.Close();30             return message2;31         }32 33         public Message DecompressMessage(Message sourceMessage)34         {35             if (!sourceMessage.IsCompressed())36             {37                 return sourceMessage;38             }39             CompressionAlgorithm algorithm = sourceMessage.GetCompressionAlgorithm();40             sourceMessage.RemoveCompressionHeader();41             byte[] compressedBody = sourceMessage.GetCompressedBody();42             byte[] decompressedBody = DataCompressor.Decompress(compressedBody, algorithm);43             string newMessage  = Encoding.UTF8.GetString(decompressedBody);44              TextReader reader2 = new  TextReader(new StringReader(newMessage ));45             Message newMessage = Message.CreateMessage(sourceMessage.Version, null, reader2);46             newMessage.Headers.CopyHeadersFrom(sourceMessage);47             newMessage.Properties.CopyProperties(sourceMessage.Properties);48             return newMessage;49         }50         public CompressionAlgorithm Algorithm { get; private set; }51     }

下面是针对Message类型而定义了一些扩展方法和辅助方法。

 1     public static class CompressionUtil 2     { 3         public const string CompressionMessageHeader = "Compression"; 4         public const string CompressionMessageBody = "CompressedBody"; 5         public const string Namespace = "http://www.yswenli.net/compression"; 6  7         public static bool IsCompressed(this Message message) 8         { 9             return message.Headers.FindHeader(CompressionMessageHeader, Namespace) > -1;10         }11 12         public static void AddCompressionHeader(this Message message, CompressionAlgorithm algorithm)13         {14             message.Headers.Add(MessageHeader.CreateHeader(CompressionMessageHeader, Namespace, string.Format("algorithm = "{0}"", algorithm)));15         }16 17         public static void RemoveCompressionHeader(this Message message)18         {19             message.Headers.RemoveAll(CompressionMessageHeader, Namespace);20         }21 22         public static CompressionAlgorithm GetCompressionAlgorithm(this Message message)23         {24             if (message.IsCompressed())25             {26                 var algorithm = message.Headers.GetHeader<string>(CompressionMessageHeader, Namespace);27                 algorithm = algorithm.Replace("algorithm =", string.Empty).Replace(""", string.Empty).Trim();28                 if (algorithm == CompressionAlgorithm.Deflate.ToString())29                 {30                     return CompressionAlgorithm.Deflate;31                 }32 33                 if (algorithm == CompressionAlgorithm.GZip.ToString())34                 {35                     return CompressionAlgorithm.GZip;36                 }37                 throw new InvalidOperationException("Invalid compression algrorithm!");38             }39             throw new InvalidOperationException("Message is not compressed!");40         }41 42         public static byte[] GetCompressedBody(this Message message)43         {44             byte[] buffer;45             using ( Reader reader1 = message.GetReaderAtBodyContents())46             {47                 buffer = Convert.From 64String(reader1.ReadElementString(CompressionMessageBody, Namespace));48             }49             return buffer;50         }51 52         public static string CreateCompressedBody(byte[] content)53         {54             StringWriter output = new StringWriter();55             using ( Writer writer2 =  Writer.Create(output))56             {57                 writer2.WriteStartElement(CompressionMessageBody, Namespace);58                 writer2.Write 64(content, 0, content.Length);59                 writer2.WriteEndElement();60             }61             return output.ToString();62         }63     }

四、用于对请求/回复消息压缩和解压缩的组件  

  消息的序列化和反序列化最终是通过MessageFormatter来完成的。具体来说,客户端通过ClientMessageFormatter实现对请求消息的序列化和对回复消息的序列化,而服务端通过DispatchMessageFormatter实现对请求消息的反序列化和对回复消息的序列化。

  在默认的情况下,WCF选用的MessageFormatter为DataContractSerializerOperationFormatter,它采用DataContractSerializer进行实际的序列化和法序列化操作。我们自定义的MessageFormatter实际上是对DataContractSerializerOperationFormatter的封装,我们依然使用它来完成序列化和反序列化工作,额外实现序列化后的压缩和法序列化前的解压缩。

  因为DataContractSerializerOperationFormatter是一个internal类型,我们只有通过反射的方式来创建它。如下的代码片断为用于进行消息压缩与解压缩的自定义MessageFormatter,即CompressionMessageFormatter的定义。

 1     public class CompressionMessageFormatter : IDispatchMessageFormatter, IClientMessageFormatter 2     { 3         private const string DataContractSerializerOperationFormatterTypeName = "System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter, System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"; 4  5         public IDispatchMessageFormatter InnerDispatchMessageFormatter { get; private set; } 6         public IClientMessageFormatter InnerClientMessageFormatter { get; private set; } 7         public MessageCompressor MessageCompressor { get; private set; } 8  9         public CompressionMessageFormatter(CompressionAlgorithm algorithm, OperationDe ion de ion, DataContractFormatAttribute dataContractFormatAttribute, DataContractSerializerOperationBehavior serializerFactory)10         {11             this.MessageCompressor = new MessageCompressor(algorithm);12             Type innerFormatterType = Type.GetType(DataContractSerializerOperationFormatterTypeName);13             var innerFormatter = Activator.CreateInstance(innerFormatterType, de ion, dataContractFormatAttribute, serializerFactory);14             this.InnerClientMessageFormatter = innerFormatter as IClientMessageFormatter;15             this.InnerDispatchMessageFormatter = innerFormatter as IDispatchMessageFormatter;16         }17 18         public void DeserializeRequest(Message message,  [] parameters)19         {20             message = this.MessageCompressor.DecompressMessage(message);21             this.InnerDispatchMessageFormatter.DeserializeRequest(message, parameters);22         }23 24         public Message SerializeReply(MessageVersion messageVersion,  [] parameters,   result)25         {26             var message = this.InnerDispatchMessageFormatter.SerializeReply(messageVersion, parameters, result);27             return this.MessageCompressor.CompressMessage(message);28         }29 30         public   DeserializeReply(Message message,  [] parameters)31         {32             message = this.MessageCompressor.DecompressMessage(message);33             return this.InnerClientMessageFormatter.DeserializeReply(message, parameters);34         }35 36         public Message SerializeRequest(MessageVersion messageVersion,  [] parameters)37         {38             var message = this.InnerClientMessageFormatter.SerializeRequest(messageVersion, parameters);39             return this.MessageCompressor.CompressMessage(message);40         }41     }

 

五、将CompressionMessageFormatter用于WCF运行时框架的操作行为  

  ClientMessageFormatter和DispatchMessageFormatter实际上属于ClientOperation和DispatchOperation的组件。我们可以通过如下一个自定义的操作行为CompressionOperationBehaviorAttribute将其应用到相应的操作上。

 1     [AttributeUsage(AttributeTargets.Method)] 2     public class CompressionOperationBehaviorAttribute : Attribute, IOperationBehavior 3     { 4         public CompressionAlgorithm Algorithm { get; set; } 5  6         public void AddBindingParameters(OperationDe ion operationDe ion, BindingParameterCollection bindingParameters) { } 7  8         public void ApplyClientBehavior(OperationDe ion operationDe ion, ClientOperation clientOperation) 9         {10             clientOperation.SerializeRequest = true;11             clientOperation.DeserializeReply = true;12             var dataContractFormatAttribute = operationDe ion.SyncMethod.GetCustomAttributes(typeof(DataContractFormatAttribute), true).FirstOrDefault() as DataContractFormatAttribute;13             if (null == dataContractFormatAttribute)14             {15                 dataContractFormatAttribute = new DataContractFormatAttribute();16             }17 18             var dataContractSerializerOperationBehavior = operationDe ion.Behaviors.Find<DataContractSerializerOperationBehavior>();19             clientOperation.Formatter = new CompressionMessageFormatter(this.Algorithm, operationDe ion, dataContractFormatAttribute, dataContractSerializerOperationBehavior);20         }21 22         public void ApplyDispatchBehavior(OperationDe ion operationDe ion, DispatchOperation dispatchOperation)23         {24             dispatchOperation.SerializeReply = true;25             dispatchOperation.DeserializeRequest = true;26             var dataContractFormatAttribute = operationDe ion.SyncMethod.GetCustomAttributes(typeof(DataContractFormatAttribute), true).FirstOrDefault() as DataContractFormatAttribute;27             if (null == dataContractFormatAttribute)28             {29                 dataContractFormatAttribute = new DataContractFormatAttribute();30             }31             var dataContractSerializerOperationBehavior = operationDe ion.Behaviors.Find<DataContractSerializerOperationBehavior>();32             dispatchOperation.Formatter = new CompressionMessageFormatter(this.Algorithm, operationDe ion, dataContractFormatAttribute, dataContractSerializerOperationBehavior);33         }34 35         public void Validate(OperationDe ion operationDe ion) { }36     }

六、查看结构压缩后的消息
为了验证应用了CompressionOperationBehaviorAttribute特性的操作方法对应的消息是否经过了压缩,我们可以通过一个简单的例子来检验。我们采用常用的计算服务的例子,下面是服务契约和服务类型的定义。我们上面定义的CompressionOperationBehaviorAttribute应用到服务契约的Add操作上。

 1     [ServiceContract(Namespace = "http://www.yswenli.net/")] 2     public interface ICalculator 3     { 4         [OperationContract] 5         [CompressionOperationBehavior] 6         double Add(double x, double y); 7     } 8     public class CalculatorService : ICalculator 9     {10         public double Add(double x, double y)11         {12             return x + y;13         }14     }

我们采用BasicHttpBinding作为终结点的绑定类型(具体的配置请查看源代码),下面是通过Fiddler获取的消息的内容,它们的主体部分都经过了基于压缩的编码。

1     <s:Envelope  ns:s="http://schemas. soap.org/soap/envelope/">2       <s:Header>3         <Compression  ns="http://www.yswenli.net/compression">algorithm = "GZip"</Compression>4       </s:Header>5       <s:Body>6         <CompressedBody  ns="http://www.yswenli.net/compression">7L0HYBx ... CQAA//8=</CompressedBody>7       </s:Body>8     </s:Envelope>

回复消息

1     <s:Envelope  ns:s="http://schemas. soap.org/soap/envelope/">2       <s:Header>3         <Compression  ns="http://www.yswenli.net/compression">algorithm = "GZip"</Compression>4       </s:Header>5       <s:Body>6         <CompressedBody  ns="http://www.yswenli.net/compression">7L0H...PAAAA//8=</CompressedBody>7       </s:Body>8     </s:Envelope>

七、扩展

如果不想使微软自带的序列化或者因为某些原因(emoji字符异常等)可以使用自定义的IDispatchMessageInspector。由于CompressionMessageFormatter使用基于DataContractSerializer序列化器的DataContractSerializerOperationFormatter进行消息的序列化和发序列化工作,而DataContractSerializer仅仅是WCF用于序列化的一种默认的选择(WCF还可以采用传统的 Seriaizer);为了让CompressionMessageFormatter能够使用其他序列化器,可以对于进行相应的修正。

 

 


转载请标明本文来源:http://www.cnblogs.com/yswenli/p/6670081.html
更多内容欢迎我的的github:https://github.com/yswenli
如果发现本文有什么问题和任何建议,也随时欢迎交流~

 
收藏 打印