基於WCF的通道網絡傳輸數據壓縮技術的應用研究

本文及程序不是介紹WCF怎麼用,而是研究如何在WCF通信時的通道兩端自動進行數據壓縮和解壓縮,從而增加分佈式數據傳輸速度。
 
而且,這個過程是完全透明的,用戶及編程人員根本不需要知道它的存在,相當於HOOK在兩端的一個組件。可以使用中網絡帶寬較小
的網絡環境中。當WCF在兩個實體間通訊的時候,便自動創建一個信息通道轉接通訊,這個消息包含數據請求和相應。WCF使用特殊的
編碼器將請求和響應數據轉換成一系列的字節。
 
    我所帶的項目裏遇到大文件分佈式傳輸問題,經過分析考慮採用WCF通道壓縮技術來解決此問題。執行這樣的編碼是需要傳輸大文件(XML格式)由一臺機器到另一臺機器傳輸,而連接有速度限制。經過查看了一些國外英文網站,發現我不用寫一個特殊的函數邊壓縮和邊解壓,而是配置傳輸通道可以做到這一點,這種方式壓縮可重複使用的任何契約。我發現自己編寫的消息編碼器是最簡單的方式來實現功能,真正的問題是如何編寫信息編碼器,在MSDN上沒有找到任何關於此應用的實例。消息契約編碼器的想法是Hook連接兩端發送和接收信息的渠道。程序是採用Microsoft Visual Studio 2008 WCF設計。
              
                                  圖1 WCF消息通道編碼過程時序圖    
 
發送方:代碼中加入方法,該方法及其參數的序列化成SOAP消息,消息編碼序列化的信息將成爲一個字節數組,字節數組發送傳輸層。
 接收方:傳輸層接收字節數組,消息編碼器並行化字節數組到一條消息,該方法及其參數並行化到一個SOAP消息,方法是被監聽的。 當加入壓縮信息編碼器,該方法要求有一點改變:發送方:代碼中加入方法,該方法及其參數的序列化成SOAP消息,消息契約編碼讓其內在的信息編碼序列的信息成爲一個字節數組,消息契約編碼壓縮的字節數組第二個字節數組,字節數組發送傳輸層。

     接收方:傳輸層接收字節數組,消息契約編碼的字節數組解壓到第二字節數組,消息契約編碼讓其內在的信息編碼化的第二個字節數組消息,該方法及其參並行化到SOAP消息,方法是被監聽的。
 
     這個消息契約編碼分爲幾個類:

     CompactMessageEncoder //這個類提供了信息編碼實施。

     CompactMessageEncoderFactory //這個類是負責提供契約信息編碼實例。

     CompactMessageEncodingBindingElement //這個類負責通道的協議約束規範。
 
     CompactMessageEncodingElement //這個類使信息編碼通過增加應用程序配置文件。
                
                              圖2 消息通道編碼器靜態類圖 
 
     壓縮方法:契約消息編碼器是使用gzip壓縮的NET Framework範圍內執行的,是調用System.IO.Compression.GZipStream名字空間類中。

     加入引用CompactMessageEncoder.dll,修改app.config文件引用,應用程序必須要在客戶端和服務器端。
壓縮緩衝代碼:
private static ArraySegment<byte> CompressBuffer(ArraySegment<byte> buffer, BufferManager bufferManager, int messageOffset)
        {
            
// Create a memory stream for the final message
            MemoryStream memoryStream = new MemoryStream();

            
// Copy the bytes that should not be compressed into the stream
            memoryStream.Write(buffer.Array, 0, messageOffset);

            
// Compress the message into the stream
            using (GZipStream gzStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
            {
                gzStream.Write(buffer.Array, messageOffset, buffer.Count);
            }

            
// Convert the stream into a bytes array
            byte[] compressedBytes = memoryStream.ToArray();

            
// Allocate a new buffer to hold the new bytes array
            byte[] bufferedBytes = bufferManager.TakeBuffer(compressedBytes.Length);

            
// Copy the compressed data into the allocated buffer
            Array.Copy(compressedBytes, 0, bufferedBytes, 0, compressedBytes.Length);

             
// Release the original buffer we got as an argument
            bufferManager.ReturnBuffer(buffer.Array);

            
// Create a new ArraySegment that points to the new message buffer
            ArraySegment<byte> byteArray = new ArraySegment<byte>(bufferedBytes, messageOffset, compressedBytes.Length - messageOffset);

            
return byteArray;
        }
解壓緩衝代碼:
Code
   
private static ArraySegment<byte> DecompressBuffer(ArraySegment<byte> buffer, BufferManager bufferManager)
        {
            
// Create a new memory stream, and copy into it the buffer to decompress
            MemoryStream memoryStream = new MemoryStream(buffer.Array, buffer.Offset, buffer.Count);

            
// Create a memory stream to store the decompressed data
            MemoryStream decompressedStream = new MemoryStream();

            
// The totalRead stores the number of decompressed bytes
            int totalRead = 0;

            
int blockSize = 1024;

            
// Allocate a temporary buffer to use with the decompression
            byte[] tempBuffer = bufferManager.TakeBuffer(blockSize);

            
// Uncompress the compressed data
            using (GZipStream gzStream = new GZipStream(memoryStream, CompressionMode.Decompress))
            {
                
while (true)
                {
                    
// Read from the compressed data stream
                    int bytesRead = gzStream.Read(tempBuffer, 0, blockSize);
                    
if (bytesRead == 0)
                        
break;
                    
// Write to the decompressed data stream
                    decompressedStream.Write(tempBuffer, 0, bytesRead);
                    totalRead 
+= bytesRead;
                }
            }
            
// Release the temporary buffer
            bufferManager.ReturnBuffer(tempBuffer);

            
// Convert the decompressed data stream into bytes array
            byte[] decompressedBytes = decompressedStream.ToArray();

            
// Allocate a new buffer to store the message 
            byte[] bufferManagerBuffer = bufferManager.TakeBuffer(decompressedBytes.Length + buffer.Offset);

            
// Copy the bytes that comes before the compressed message in the buffer argument
            Array.Copy(buffer.Array, 0, bufferManagerBuffer, 0, buffer.Offset);

            
// Copy the decompressed data
            Array.Copy(decompressedBytes, 0, bufferManagerBuffer, buffer.Offset, decompressedBytes.Length);

            
// Create a new ArraySegment that points to the new message buffer
            ArraySegment<byte> byteArray = new ArraySegment<byte>(bufferManagerBuffer, buffer.Offset, decompressedBytes.Length);

            
// Release the original message buffer
            bufferManager.ReturnBuffer(buffer.Array);

            
return byteArray;
        }
改變服務端配置
   加入消息契約編碼器之前app.config的實例:
<?xml version="1.0" encoding="utf-8" ?>
<configuration> 
    
<system.serviceModel> 
        
<services> 
            
<service name="Server.MyService">
                
<endpoint 
                    
address="net.tcp://localhost:1234/MyService" 
                    binding
="netTcpBinding"
                    contract
="Server.IMyService" /> 
            
</service> 
        
</services> 
    
</system.serviceModel>
</configuration>
加入消息契約編碼器後app.config的例子:
Code
<?xml version="1.0" encoding="utf-8" ?>
<configuration> 
    
<system.serviceModel> 
        
<services> 
            
<service name="Server.MyService">

            
<!-- Set the binding of the endpoint to customBinding -->                 
            
<endpoint 
                    
address="net.tcp://localhost:1234/MyService" 
                    binding
="customBinding"
                    contract
="Server.IMyService" /> 
            
</service> 
        
</services> 

        
<!-- Defines a new customBinding that contains the compactMessageEncoding -->         
        
<bindings> 
            
<customBinding> 
                
<binding name="compactBinding"> 
                    
<compactMessageEncoding>

                
<!-- Defines the inner message encoder as binary encoder -->                        
                
<binaryMessageEncoding /> 
                    
</compactMessageEncoding> 
                    
<tcpTransport /> 
                
</binding> 
            
</customBinding> 
        
</bindings> 

    
<!-- Adds the extension dll so the WCF can find the compactMessageEncoding -->
        
<extensions> 
            
<bindingElementExtensions> 
                
<add name="compactMessageEncoding" type="Amib.WCF.CompactMessageEncodingElement, CompactMessageEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
            
</bindingElementExtensions> 
        
</extensions> 

     
</system.serviceModel>
</configuration>
客戶端配置變化
加入消息契約編碼器之前app.config的實例:
Code
加入消息契約編碼器後app.config的例子:
Code
  
這種壓縮方法,消息堵塞的機率很小。使用CompactMessageEncoder在同一臺機器運行客戶端和服務器上可能會降低效率。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章