WCF 消息壓縮性能問題及解決方法


最近使用WCF作為通迅框架開發一套信息系統,系統使用傳統C/S框架,系統有可能會部署在互聯網上,因此決定對傳輸的數據進行GZIP壓縮,原來在使用.NET Remoting時,可以使用插入自定義的ChannelSink來實現數據壓縮,作為.NET Remoting的替代方案的WCF,實現起來也很容易,且方法不止一種,主要解決方法主要有以下四種:

相比較,第三和第四實現相對簡單,配置很簡單,它們的內部實現方法很類似,我的消息壓縮類也來源於WCF大師Artech的博客《通過WCF擴展實現消息壓縮》的消息壓縮類,區別在於第三在自定義MessageFormatter中對消息進行壓縮和解壓縮,而第四是在自定義MessageInspector中對消息進行壓縮和解壓縮。下面給出第四種實現方法(網絡上也很多):

一、Compress-壓縮與解壓縮類

/// <summary>
    /// 壓縮解壓縮類
    /// </summary>
    public class Compress
    {

        public static byte[] Zip(byte[] sourceBytes)
        {
            using (MemoryStream mStream = new MemoryStream())
            {
                GZipStream gStream = new GZipStream(mStream, CompressionMode.Compress);
                gStream.Write(sourceBytes, 0, sourceBytes.Length);
                gStream.Close();
                return mStream.ToArray();
            }
        }

        public static byte[] UnZip(byte[] sourceBytes)
        {
            using (MemoryStream mStream = new MemoryStream())
            {
                using (GZipStream gStream = new GZipStream(new MemoryStream(sourceBytes), CompressionMode.Decompress))
                {
                    int readBytes = 0;
                    byte[] buffer = new byte[1024];
                    while ((readBytes = gStream.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        mStream.Write(buffer, 0, readBytes);
                    }
                    return mStream.ToArray();
                }
            }
        }
  }
壓縮與解壓縮類

二、MessageCompressor—消息壓縮與解壓類

/// <summary>
    /// 消息壓縮類
    /// </summary>
    public static class MessageCompress
    {
        public static string Namespace = "http://myjece";
        public static Message CompressMessage(Message sourceMessage)
        {
            byte[] buffer;
            string sourceBody;
            using (XmlDictionaryReader reader1 = sourceMessage.GetReaderAtBodyContents())
            {
                sourceBody = reader1.ReadOuterXml();
                buffer = Encoding.UTF8.GetBytes(sourceBody);
            }

            XmlTextReader reader;
            if (buffer.Length > 256)
            {
                byte[] compressedData = Compress.Zip(buffer);
                string compressedBody = CreateCompressedBody(compressedData);
                reader = new XmlTextReader(new StringReader(compressedBody), new NameTable());
                sourceMessage.AddCompressionHeader();
            }
            else
            {
                reader = new XmlTextReader(new StringReader(sourceBody), new NameTable());
            }
            Message message = Message.CreateMessage(sourceMessage.Version, null, (XmlReader)reader);
            message.Headers.CopyHeadersFrom(sourceMessage);
            message.Properties.CopyProperties(sourceMessage.Properties);
            sourceMessage.Close();
            return message;



        }

        public static Message DeCompressMessage(Message sourceMessage)
        {
            if (!sourceMessage.IsCompressed())
            {
                return sourceMessage;
            }
            else
            {
                sourceMessage.RemoveCompressionHeader();
                string deCompressedBody = Encoding.UTF8.GetString(Compress.UnZip(sourceMessage.GetCompressedBody()));

                XmlTextReader reader = new XmlTextReader(new StringReader(deCompressedBody), new NameTable());
                Message message = Message.CreateMessage(sourceMessage.Version, null, (XmlReader)reader);
                message.Headers.CopyHeadersFrom(sourceMessage);
                message.Properties.CopyProperties(sourceMessage.Properties);
                message.AddCompressionHeader();
                //sourceMessage.Close();
                return message;
            }
        }

        public static bool IsCompressed(this Message message)
        {
            return message.Headers.FindHeader("Compression", Namespace) > -1;
        }

        public static void AddCompressionHeader(this Message message)
        {
            message.Headers.Add(MessageHeader.CreateHeader("Compression", Namespace, "GZip"));
        }

        public static void RemoveCompressionHeader(this Message message)
        {
            message.Headers.RemoveAll("Compression", Namespace);
        }

        public static string CreateCompressedBody(byte[] content)
        {
            StringWriter output = new StringWriter();
            using (XmlWriter writer2 = XmlWriter.Create(output))
            {
                writer2.WriteStartElement("CompressedBody", Namespace);
                writer2.WriteBase64(content, 0, content.Length);
                writer2.WriteEndElement();
            }
            return output.ToString();
        }

        public static byte[] GetCompressedBody(this Message message)
        {
            byte[] buffer;
            using (XmlReader reader1 = message.GetReaderAtBodyContents())
            {
                buffer = Convert.FromBase64String(reader1.ReadElementString("CompressedBody", Namespace));
            }
            return buffer;
        }


    }
消息壓縮與解壓縮類

三、ClientCompressionInspector-客戶端對消息進行壓縮與解壓縮的消息檢查器

        private class ClientCompressionInspector : IClientMessageInspector
        {
            #region IClientMessageInspector Members
            public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
            {
                reply = MessageCompress.DeCompressMessage(reply);
            }

            public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
            {
                //加入一個消息頭,表明客戶端支持gzip消息壓縮與解壓縮
                request.Headers.Add(MessageHeader.CreateHeader("AcceptEncoding", "http://myjece", "gzip"));
                request = MessageCompress.CompressMessage(request);
                return null;
            }

            #endregion

        }
客戶端對消息進行壓縮與解壓縮的消息檢查器

 

public class ClientCompressionBehavior : BehaviorExtensionElement, IEndpointBehavior
    {
        public override Type BehaviorType
        {
            get
            {
                return typeof(ClientCompressionBehavior);
            }
        }

        protected override object CreateBehavior()
        {
            return new ClientCompressionBehavior();
        }

        #region IEndpointBehavior Members

        public void AddBindingParameters(ServiceEndpoint endpoint,
            System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
            return;
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint,
            System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
        {
            clientRuntime.MessageInspectors.Add(new ClientCompressInspector());
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
            System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
        {

        }

        public void Validate(ServiceEndpoint endpoint)
        {
            return;
        }
   }
客戶端用於插入壓縮消息檢查器的終節點行為器

四、ServiceCompressInspector-服務端對消息進行壓縮與解壓縮的消息檢查器

 public class ServiceCompressInspector : IDispatchMessageInspector
    {    

        public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
        {
            request = MessageCompress.DeCompressMessage(request);
            return null;
        }

        public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            if (GetHeader("AcceptEncoding") == "gzip")
            {
                reply = MessageCompress.CompressMessage(reply);
            }
        }
        public static string GetHeader(string headerName)
        {
            if (OperationContext.Current.IncomingMessageHeaders.FindHeader(headerName, "http://myjece") >= 0)
                {
                    return OperationContext.Current.IncomingMessageHeaders.GetHeader<string>(headerName, "http://myjece");
                }
                else
                {
                    return null;
                }
        }
    }    
服務端對消息進行壓縮與解壓縮的消息檢查器
 public class ServiceCompressBehavior : BehaviorExtensionElement, IServiceBehavior
    {
        public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
            //throw new NotImplementedException();
        }

       
        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {
            foreach (ChannelDispatcher chDisp in serviceHostBase.ChannelDispatchers)
            {                
                foreach (EndpointDispatcher epDisp in chDisp.Endpoints)
                {
                    epDisp.DispatchRuntime.MessageInspectors.Add(new ServiceCompressInspector());
                }
            }

        }

        public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
        {
            //throw new NotImplementedException();
        }

        public override Type BehaviorType
        {
            get { return typeof(ServiceCompressBehavior); }
        }

        protected override object CreateBehavior()
        {
            return new ServiceCompressBehavior();
        }
    }
服務端用於插入壓縮消息檢查器和服務端行為器

五、服務端配置

在system.serviceModel節點下添加:

<extensions>
      <behaviorExtensions>
        <add name="compressBehavior" type="ServiceCompressBehavior, Service, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      </behaviorExtensions>
    </extensions>
<behaviors>
      <serviceBehaviors>
        <behavior>
          <compressBehavior />         </behavior>
      </serviceBehaviors>
</behaviors>

好了,上面的基本的實現,可以通過在客戶端行為器中添加AcceptEncoding=gzip的消息頭來確定服務端返回的消息是否也可以(需要)進行壓縮,實際運行結果也正常,但后來發現在進行大量byte[]類型數據傳輸時,發現有延時,幾百K有數據,在局域網(排除網絡問題)內,盡然達到2秒左右延時,開始懷疑的GZIP壓縮類有問題,后發現,壓縮類的對數據進行壓縮時,耗時極小,一般幾毫秒到幾十毫秒之間,最后,只能逐語句進行排查,發現問題在消息壓縮類中的:

 using (XmlDictionaryReader reader1 = sourceMessage.GetReaderAtBodyContents())
            {
                sourceBody = reader1.ReadOuterXml();                 buffer = Encoding.UTF8.GetBytes(sourceBody);
            }

Message在經過每一層消息檢查器時,都是以Message方法進行傳遞,只有到達TransportBinding上編碼器時,才會對Message進行編碼,或文本,或二進制,但在我上面的消息壓縮中,先從原Message中獲取Body內容,然后對Body進行壓縮,再把壓縮Body封裝進新的Message中,問題就出在獲取Body內容中,XmlDictionaryReader 的ReadOuterXml()方法相當對Body進行了XML的編碼,所以導致了性能問題。解決問題的根本在於找到一個能獲取到Body內容,又能避免提前對Body內容進行XML編碼的方法。 

將上述代碼改為以下代碼后,性能得以大幅提升:

 MemoryStream ms = new MemoryStream();
            XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(ms, Encoding.UTF8);
            sourceMessage.WriteBodyContents(writer);
            writer.Flush();
            buffer=ms.ToArray();

但最終獲取到的buffer是一致的,是什么原因導致它們之間有巨大的性能差異就不得而知了……

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM