WCF消息壓縮

来源:http://www.cnblogs.com/yswenli/archive/2017/04/05/6670081.html
-Advertisement-
Play Games

對於WCF應用來說,傳輸前壓縮請求消息和回覆消息,不但可以降低網路流量,也可以提高網路傳輸的性能 一、消息壓縮方案 二、用於數據壓縮與解壓縮組件 三、用於消息壓縮與解壓的組件 四、用於對請求/回覆消息壓縮和解壓縮的組件 五、將CompressionMessageFormatter用於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
如果發現本文有什麼問題和任何建議,也隨時歡迎交流~

 

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、Jenins+GitHub "參考" 另外需要配置Global Tool Configuration 如果沒有安裝git,需下載安裝, "下載地址" 二、jenkins發佈donet core應用 1.配置MSBuild 1.1安裝Visual Studio 2017生成工具, "下載地址" 1 ...
  • 使用 StackExchange.Redis 封裝屬於自己的 RedisHelper 目錄 核心類 ConnectionMultiplexer 字元串(String) 哈希(Hash) 列表(List) 有序集合(sorted set) Key 操作 發佈訂閱 其他 簡介 目前 .NET 使用訪問 ...
  • 下拉列表 以性別為例 綁定可以了,可以顯示了,但有些地方就能傳值,有些地方就會出錯提示,如有大神請指教。。。。 錯誤如下: 具有鍵“sex”的 ViewData 項屬於類型“YTgoShopping.Utilities.sex”,但它必須屬於類型“IEnumerable<SelectListItem ...
  • 一、屌絲也有春天 "親愛的,在不?" "妹子,你電腦又感覺慢了麽?您以後裝軟體的時候註意點行不,能不能不要裝上7-8個瀏覽器,3-4個殺毒軟體,啥配置的機子你都感覺卡。以後別到處瞎找動作類電影看,網上一般都掛馬騙你們這些小白的,實在想看找我要種子不就行了。" "沒有啦,人家電腦好著呢,您不是做軟體的 ...
  • 如果大家研究一些開源項目,會發現無處不在的DI(Dependency Injection依賴註入)。 本篇文章將會詳細講述如何在MVC中使用Ninject實現DI 文章提綱 場景描述 & 問題引出 第一輪重構 引入Ninject 第二輪重構 總結 場景描述 & 問題引出 DI是一種實現組件解耦的設計 ...
  • 度娘許久,找不到我滿意的答案,於是自己東湊西湊實現一個。 DynamicObject擴展--實現JSON和DynamicObject的序列化與反序列化,親測良好。 看代碼 ...
  • 跨平臺系列彙總:http://www.cnblogs.com/dunitian/p/4822808.html#linux 上次說了安裝VSCode(http://www.cnblogs.com/dunitian/p/6661644.html)和sogou(http://www.cnblogs.com ...
  • 本章內容和大家分享的是Asp.NetCore組件寫法,在netcore中很多東西都以提供組件的方式來使用,比如MVC架構,Session,Cache,資料庫引用等; 這裡我也通過調用驗證碼介面來自定義個組件以此說明如何使用,以及使用時需要註意的場景; Middleware之hello world 對 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...