天天看點

WCF消息壓縮

對于WCF應用來說,傳輸前壓縮請求消息和回複消息,不但可以降低網絡流量,也可以提高網絡傳輸的性能

一、消息壓縮方案

二、用于資料壓縮與解壓縮元件

三、用于消息壓縮與解壓的元件

四、用于對請求/回複消息壓縮和解壓縮的元件

五、将CompressionMessageFormatter用于WCF運作時架構的操作行為

六、檢視結構壓縮後的消息

七、擴充

一、消息壓縮方案

  消息壓縮在WCF中的實作其實很簡單,我們隻需要在消息(請求消息/回複消息)被序列化之後,發送之前進行壓縮;在接收之後,反序列化之前進行解壓縮即可。針對壓縮/解壓縮使用的時機,有三種典型的解決方案。通過自定義MessageEncoder和MessageEncodingBindingElement 來完成。

1.将編碼後的位元組流壓縮傳輸

2.建立用于壓縮和解壓縮的信道

3. 自定義MessageFormatter實作序列化後的壓縮和法序列化前的解壓縮

這裡要介紹的解決方案3。  

二、用于資料壓縮與解壓縮元件

  我們支援兩種方式的壓縮,Dflate和GZip。兩種不同的壓縮算法通過如下定義的CompressionAlgorithm枚舉表示。

1     public enum CompressionAlgorithm
2     {
3         GZip,
4         Deflate
5     }      

而如下定義的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                 else
14                 {
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                 else
35                 {
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對消息的主體部分内容進行壓縮,并将壓縮後的内容存放到一個預定義的XML元素中(名稱和命名空間分别為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 (XmlDictionaryReader reader1 = sourceMessage.GetReaderAtBodyContents())
11             {
12                 buffer = Encoding.UTF8.GetBytes(reader1.ReadOuterXml());
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             XmlTextReader reader = new XmlTextReader(new StringReader(copressedBody), new NameTable());
25             Message message2 = Message.CreateMessage(sourceMessage.Version, null, (XmlReader)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 newMessageXml = Encoding.UTF8.GetString(decompressedBody);
44             XmlTextReader reader2 = new XmlTextReader(new StringReader(newMessageXml));
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 (XmlReader reader1 = message.GetReaderAtBodyContents())
46             {
47                 buffer = Convert.FromBase64String(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 (XmlWriter writer2 = XmlWriter.Create(output))
56             {
57                 writer2.WriteStartElement(CompressionMessageBody, Namespace);
58                 writer2.WriteBase64(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, OperationDescription description, DataContractFormatAttribute dataContractFormatAttribute, DataContractSerializerOperationBehavior serializerFactory)
10         {
11             this.MessageCompressor = new MessageCompressor(algorithm);
12             Type innerFormatterType = Type.GetType(DataContractSerializerOperationFormatterTypeName);
13             var innerFormatter = Activator.CreateInstance(innerFormatterType, description, dataContractFormatAttribute, serializerFactory);
14             this.InnerClientMessageFormatter = innerFormatter as IClientMessageFormatter;
15             this.InnerDispatchMessageFormatter = innerFormatter as IDispatchMessageFormatter;
16         }
17 
18         public void DeserializeRequest(Message message, object[] parameters)
19         {
20             message = this.MessageCompressor.DecompressMessage(message);
21             this.InnerDispatchMessageFormatter.DeserializeRequest(message, parameters);
22         }
23 
24         public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
25         {
26             var message = this.InnerDispatchMessageFormatter.SerializeReply(messageVersion, parameters, result);
27             return this.MessageCompressor.CompressMessage(message);
28         }
29 
30         public object DeserializeReply(Message message, object[] parameters)
31         {
32             message = this.MessageCompressor.DecompressMessage(message);
33             return this.InnerClientMessageFormatter.DeserializeReply(message, parameters);
34         }
35 
36         public Message SerializeRequest(MessageVersion messageVersion, object[] 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(OperationDescription operationDescription, BindingParameterCollection bindingParameters) { }
 7 
 8         public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
 9         {
10             clientOperation.SerializeRequest = true;
11             clientOperation.DeserializeReply = true;
12             var dataContractFormatAttribute = operationDescription.SyncMethod.GetCustomAttributes(typeof(DataContractFormatAttribute), true).FirstOrDefault() as DataContractFormatAttribute;
13             if (null == dataContractFormatAttribute)
14             {
15                 dataContractFormatAttribute = new DataContractFormatAttribute();
16             }
17 
18             var dataContractSerializerOperationBehavior = operationDescription.Behaviors.Find<DataContractSerializerOperationBehavior>();
19             clientOperation.Formatter = new CompressionMessageFormatter(this.Algorithm, operationDescription, dataContractFormatAttribute, dataContractSerializerOperationBehavior);
20         }
21 
22         public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
23         {
24             dispatchOperation.SerializeReply = true;
25             dispatchOperation.DeserializeRequest = true;
26             var dataContractFormatAttribute = operationDescription.SyncMethod.GetCustomAttributes(typeof(DataContractFormatAttribute), true).FirstOrDefault() as DataContractFormatAttribute;
27             if (null == dataContractFormatAttribute)
28             {
29                 dataContractFormatAttribute = new DataContractFormatAttribute();
30             }
31             var dataContractSerializerOperationBehavior = operationDescription.Behaviors.Find<DataContractSerializerOperationBehavior>();
32             dispatchOperation.Formatter = new CompressionMessageFormatter(this.Algorithm, operationDescription, dataContractFormatAttribute, dataContractSerializerOperationBehavior);
33         }
34 
35         public void Validate(OperationDescription operationDescription) { }
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 xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
2       <s:Header>
3         <Compression xmlns="http://www.yswenli.net/compression">algorithm = "GZip"</Compression>
4       </s:Header>
5       <s:Body>
6         <CompressedBody xmlns="http://www.yswenli.net/compression">7L0HYBx ... CQAA//8=</CompressedBody>
7       </s:Body>
8     </s:Envelope>      

回複消息

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

如果不想使微軟自帶的序列化或者因為某些原因(emoji字元異常等)可以使用自定義的

IDispatchMessageInspector

。由于CompressionMessageFormatter使用基于DataContractSerializer序列化器的DataContractSerializerOperationFormatter進行消息的序列化和發序列化工作,而DataContractSerializer僅僅是WCF用于序列化的一種預設的選擇(WCF還可以采用傳統的XmlSeriaizer);為了讓CompressionMessageFormatter能夠使用其他序列化器,可以對于進行相應的修正。

轉載請标明本文來源: http://www.cnblogs.com/yswenli/p/6670081.html 更多内容歡迎我的的github: https://github.com/yswenli 如果發現本文有什麼問題和任何建議,也随時歡迎交流~

感謝您的閱讀,如果您對我的部落格所講述的内容有興趣,請繼續關注我的後續部落格,我是yswenli 。

繼續閱讀