zoukankan      html  css  js  c++  java
  • WCF中自定义消息编码器:压缩编码器的使用

    通过抓包知道WCF在提交、返回数据的时候大多使用XML进行数据交互,如果返回DataTable那么这些数据将变得很大,通过查询找到一个对数据压缩的方法:

    http://msdn.microsoft.com/zh-cn/library/ms751458(v=vs.110).aspx

    新增项目GZipEncoder,GzipEncoder中增加三个文件 :

    GZipMessageEncoderFactory.cs

    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Text;
    using System.IO;
    using System.IO.Compression;
    using System.Runtime.Serialization;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Configuration;
    using System.Xml;
    using System.ServiceModel.Security;
    using System.ServiceModel.Description;
    
    namespace GZipEncoder
    {
        //This class is used to create the custom encoder (GZipMessageEncoder)
        internal class GZipMessageEncoderFactory : MessageEncoderFactory
        {
            MessageEncoder encoder;
    
            //The GZip encoder wraps an inner encoder
            //We require a factory to be passed in that will create this inner encoder
            public GZipMessageEncoderFactory(MessageEncoderFactory messageEncoderFactory)
            {
                if (messageEncoderFactory == null)
                    throw new ArgumentNullException("messageEncoderFactory", "A valid message encoder factory must be passed to the GZipEncoder");
                encoder = new GZipMessageEncoder(messageEncoderFactory.Encoder);
    
            }
    
            //The service framework uses this property to obtain an encoder from this encoder factory
            public override MessageEncoder Encoder
            {
                get { return encoder; }
            }
    
            public override MessageVersion MessageVersion
            {
                get { return encoder.MessageVersion; }
            }
    
            //This is the actual GZip encoder
            class GZipMessageEncoder : MessageEncoder
            {
                static string GZipContentType = "application/x-gzip";
    
                //This implementation wraps an inner encoder that actually converts a WCF Message
                //into textual XML, binary XML or some other format. This implementation then compresses the results.
                //The opposite happens when reading messages.
                //This member stores this inner encoder.
                MessageEncoder innerEncoder;
    
                //We require an inner encoder to be supplied (see comment above)
                internal GZipMessageEncoder(MessageEncoder messageEncoder)
                    : base()
                {
                    if (messageEncoder == null)
                        throw new ArgumentNullException("messageEncoder", "A valid message encoder must be passed to the GZipEncoder");
                    innerEncoder = messageEncoder;
                }
    
                //public override string CharSet
                //{
                //    get { return ""; }
                //}
    
                public override string ContentType
                {
                    get { return GZipContentType; }
                }
    
                public override string MediaType
                {
                    get { return GZipContentType; }
                }
    
                //SOAP version to use - we delegate to the inner encoder for this
                public override MessageVersion MessageVersion
                {
                    get { return innerEncoder.MessageVersion; }
                }
    
                //Helper method to compress an array of bytes
                static ArraySegment<byte> CompressBuffer(ArraySegment<byte> buffer, BufferManager bufferManager, int messageOffset)
                {
                    MemoryStream memoryStream = new MemoryStream();
                    memoryStream.Write(buffer.Array, 0, messageOffset);
    
                    using (GZipStream gzStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
                    {
                        gzStream.Write(buffer.Array, messageOffset, buffer.Count);
                    }
    
    
                    byte[] compressedBytes = memoryStream.ToArray();
                    byte[] bufferedBytes = bufferManager.TakeBuffer(compressedBytes.Length);
    
                    Array.Copy(compressedBytes, 0, bufferedBytes, 0, compressedBytes.Length);
    
                    bufferManager.ReturnBuffer(buffer.Array);
                    ArraySegment<byte> byteArray = new ArraySegment<byte>(bufferedBytes, messageOffset, bufferedBytes.Length - messageOffset);
    
                    return byteArray;
                }
    
                //Helper method to decompress an array of bytes
                static ArraySegment<byte> DecompressBuffer(ArraySegment<byte> buffer, BufferManager bufferManager)
                {
                    MemoryStream memoryStream = new MemoryStream(buffer.Array, buffer.Offset, buffer.Count - buffer.Offset);
                    MemoryStream decompressedStream = new MemoryStream();
                    int totalRead = 0;
                    int blockSize = 1024;
                    byte[] tempBuffer = bufferManager.TakeBuffer(blockSize);
                    using (GZipStream gzStream = new GZipStream(memoryStream, CompressionMode.Decompress))
                    {
                        while (true)
                        {
                            int bytesRead = gzStream.Read(tempBuffer, 0, blockSize);
                            if (bytesRead == 0)
                                break;
                            decompressedStream.Write(tempBuffer, 0, bytesRead);
                            totalRead += bytesRead;
                        }
                    }
                    bufferManager.ReturnBuffer(tempBuffer);
    
                    byte[] decompressedBytes = decompressedStream.ToArray();
                    byte[] bufferManagerBuffer = bufferManager.TakeBuffer(decompressedBytes.Length + buffer.Offset);
                    Array.Copy(buffer.Array, 0, bufferManagerBuffer, 0, buffer.Offset);
                    Array.Copy(decompressedBytes, 0, bufferManagerBuffer, buffer.Offset, decompressedBytes.Length);
    
                    ArraySegment<byte> byteArray = new ArraySegment<byte>(bufferManagerBuffer, buffer.Offset, decompressedBytes.Length);
                    bufferManager.ReturnBuffer(buffer.Array);
    
                    return byteArray;
                }
    
    
                //One of the two main entry points into the encoder. Called by WCF to decode a buffered byte array into a Message.
                public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
                {
                    //Decompress the buffer
                    ArraySegment<byte> decompressedBuffer = DecompressBuffer(buffer, bufferManager);
                    //Use the inner encoder to decode the decompressed buffer
                    Message returnMessage = innerEncoder.ReadMessage(decompressedBuffer, bufferManager);
                    returnMessage.Properties.Encoder = this;
                    return returnMessage;
                }
    
                //One of the two main entry points into the encoder. Called by WCF to encode a Message into a buffered byte array.
                public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)
                {
                    //Use the inner encoder to encode a Message into a buffered byte array
                    ArraySegment<byte> buffer = innerEncoder.WriteMessage(message, maxMessageSize, bufferManager, messageOffset);
                    //Compress the resulting byte array
                    return CompressBuffer(buffer, bufferManager, messageOffset);
                }
    
                public override Message ReadMessage(System.IO.Stream stream, int maxSizeOfHeaders, string contentType)
                {
                    GZipStream gzStream = new GZipStream(stream, CompressionMode.Decompress, true);
                    return innerEncoder.ReadMessage(gzStream, maxSizeOfHeaders);
                }
    
                public override void WriteMessage(Message message, System.IO.Stream stream)
                {
                    using (GZipStream gzStream = new GZipStream(stream, CompressionMode.Compress, true))
                    {
                        innerEncoder.WriteMessage(message, gzStream);
                    }
    
                    // innerEncoder.WriteMessage(message, gzStream) depends on that it can flush data by flushing 
                    // the stream passed in, but the implementation of GZipStream.Flush will not flush underlying
                    // stream, so we need to flush here.
                    stream.Flush();
                }
            }
        }
    }
    

      GZipMessageEncodingBindingElement.cs

    using System;
    using System.Xml;
    using System.ServiceModel;
    using System.Configuration;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Configuration;
    using System.ServiceModel.Description;
    
    namespace GZipEncoder
    {
        // This is constants for GZip message encoding policy.
        static class GZipMessageEncodingPolicyConstants
        {
            public const string GZipEncodingName = "GZipEncoding";
            public const string GZipEncodingNamespace = "http://schemas.microsoft.com/ws/06/2004/mspolicy/netgzip1";
            public const string GZipEncodingPrefix = "gzip";
        }
    
        //This is the binding element that, when plugged into a custom binding, will enable the GZip encoder
        public sealed class GZipMessageEncodingBindingElement
                            : MessageEncodingBindingElement //BindingElement
                            , IPolicyExportExtension
        {
    
            //We will use an inner binding element to store information required for the inner encoder
            MessageEncodingBindingElement innerBindingElement;
    
            //By default, use the default text encoder as the inner encoder
            public GZipMessageEncodingBindingElement()
                : this(new TextMessageEncodingBindingElement()) { }
    
            public GZipMessageEncodingBindingElement(MessageEncodingBindingElement messageEncoderBindingElement)
            {
                this.innerBindingElement = messageEncoderBindingElement;
            }
    
            public MessageEncodingBindingElement InnerMessageEncodingBindingElement
            {
                get { return innerBindingElement; }
                set { innerBindingElement = value; }
            }
    
            //Main entry point into the encoder binding element. Called by WCF to get the factory that will create the
            //message encoder
            public override MessageEncoderFactory CreateMessageEncoderFactory()
            {
                return new GZipMessageEncoderFactory(innerBindingElement.CreateMessageEncoderFactory());
            }
    
            public override MessageVersion MessageVersion
            {
                get { return innerBindingElement.MessageVersion; }
                set { innerBindingElement.MessageVersion = value; }
            }
    
            public override BindingElement Clone()
            {
                return new GZipMessageEncodingBindingElement(this.innerBindingElement);
            }
    
            public override T GetProperty<T>(BindingContext context)
            {
                if (typeof(T) == typeof(XmlDictionaryReaderQuotas))
                {
                    return innerBindingElement.GetProperty<T>(context);
                }
                else
                {
                    return base.GetProperty<T>(context);
                }
            }
    
            public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
            {
                if (context == null)
                    throw new ArgumentNullException("context");
    
                context.BindingParameters.Add(this);
                return context.BuildInnerChannelFactory<TChannel>();
            }
    
            public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
            {
                if (context == null)
                    throw new ArgumentNullException("context");
    
                context.BindingParameters.Add(this);
                return context.BuildInnerChannelListener<TChannel>();
            }
    
            public override bool CanBuildChannelListener<TChannel>(BindingContext context)
            {
                if (context == null)
                    throw new ArgumentNullException("context");
    
                context.BindingParameters.Add(this);
                return context.CanBuildInnerChannelListener<TChannel>();
            }
    
            void IPolicyExportExtension.ExportPolicy(MetadataExporter exporter, PolicyConversionContext policyContext)
            {
                if (policyContext == null)
                {
                    throw new ArgumentNullException("policyContext");
                }
                XmlDocument document = new XmlDocument();
                policyContext.GetBindingAssertions().Add(document.CreateElement(
                    GZipMessageEncodingPolicyConstants.GZipEncodingPrefix,
                    GZipMessageEncodingPolicyConstants.GZipEncodingName,
                    GZipMessageEncodingPolicyConstants.GZipEncodingNamespace));
            }
        }
    
        //This class is necessary to be able to plug in the GZip encoder binding element through
        //a configuration file
        public class GZipMessageEncodingElement : BindingElementExtensionElement
        {
            public GZipMessageEncodingElement()
            {
            }
    
            //Called by the WCF to discover the type of binding element this config section enables
            public override Type BindingElementType
            {
                get { return typeof(GZipMessageEncodingBindingElement); }
            }
    
            //The only property we need to configure for our binding element is the type of
            //inner encoder to use. Here, we support text and binary.
            [ConfigurationProperty("innerMessageEncoding", DefaultValue = "textMessageEncoding")]
            public string InnerMessageEncoding
            {
                get { return (string)base["innerMessageEncoding"]; }
                set { base["innerMessageEncoding"] = value; }
            }
    
            //Called by the WCF to apply the configuration settings (the property above) to the binding element
            public override void ApplyConfiguration(BindingElement bindingElement)
            {
                GZipMessageEncodingBindingElement binding = (GZipMessageEncodingBindingElement)bindingElement;
                PropertyInformationCollection propertyInfo = this.ElementInformation.Properties;
                if (propertyInfo["innerMessageEncoding"].ValueOrigin != PropertyValueOrigin.Default)
                {
                    switch (this.InnerMessageEncoding)
                    {
                        case "textMessageEncoding":
                            binding.InnerMessageEncodingBindingElement = new TextMessageEncodingBindingElement();
                            break;
                        case "binaryMessageEncoding":
                            binding.InnerMessageEncodingBindingElement = new BinaryMessageEncodingBindingElement();
                            break;
                    }
                }
            }
    
            //Called by the WCF to create the binding element
            protected override BindingElement CreateBindingElement()
            {
                GZipMessageEncodingBindingElement bindingElement = new GZipMessageEncodingBindingElement();
                this.ApplyConfiguration(bindingElement);
                return bindingElement;
            }
        }
    }
    

      GZipMessageEncodingBindingElementImporter.cs

    using System;
    using System.Xml;
    using System.ServiceModel.Description;
    using System.Xml.Schema;
    using System.Collections.ObjectModel;
    using System.Collections.Generic;
    using System.Text;
    
    namespace GZipEncoder
    {
        public class GZipMessageEncodingBindingElementImporter : IPolicyImportExtension
        {
            public GZipMessageEncodingBindingElementImporter()
            {
            }
    
            void IPolicyImportExtension.ImportPolicy(MetadataImporter importer, PolicyConversionContext context)
            {
                if (importer == null)
                {
                    throw new ArgumentNullException("importer");
                }
    
                if (context == null)
                {
                    throw new ArgumentNullException("context");
                }
    
                ICollection<XmlElement> assertions = context.GetBindingAssertions();
                foreach (XmlElement assertion in assertions)
                {
                    if ((assertion.NamespaceURI == GZipMessageEncodingPolicyConstants.GZipEncodingNamespace) &&
                        (assertion.LocalName == GZipMessageEncodingPolicyConstants.GZipEncodingName)
                        )
                    {
                        assertions.Remove(assertion);
                        context.BindingElements.Add(new GZipMessageEncodingBindingElement());
                        break;
                    }
                }
            }
        }
    }
    

      WCF Server 和 Client 都引用项目 GZipEncoder,然后修改config文件

    Server web.config 中增加:

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
    
      <appSettings>
        <!-- 省略  -->
      </appSettings>
    
      <system.web>
        <compilation debug="true" targetFramework="4.0" />
        <customErrors mode="Off"/>    
      </system.web>
    
      <system.serviceModel>
    
        <!-- GZipEncoder 加密部分 Begin -->
        <services><!-- name:命名空间.类名 -->
          <service name="WcfService.action">
            <endpoint address="" 
                      binding="customBinding" bindingConfiguration="BufferedHttpSampleServer" 
                      bindingName="BufferedHttpSampleServer" 
                      contract="WcfService.Iaction" />
          </service>
        </services><!-- 注册 gzipMessageEncoding  -->
        <extensions>
          <bindingElementExtensions>
            <add name="gzipMessageEncoding" 
                 type="GZipEncoder.GZipMessageEncodingElement, GZipEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
          </bindingElementExtensions>
        </extensions>
        <bindings>
          <customBinding>
            <binding name="BufferedHttpSampleServer"><!-- 处理程序映射 -->
              <gzipMessageEncoding innerMessageEncoding="textMessageEncoding" />
              <httpTransport hostNameComparisonMode="StrongWildcard"
                             manualAddressing="False"
                             maxReceivedMessageSize="65536"
                             authenticationScheme="Anonymous"
                             bypassProxyOnLocal="False"
                             realm=""
                             useDefaultWebProxy="True" />
            </binding>
          </customBinding>
        </bindings>
        <!-- GZipEncoder 加密部分 End -->
        
        <behaviors>
          <serviceBehaviors>
            <behavior>
              <!-- 为避免泄漏元数据信息,请在部署前将以下值设置为 false 并删除上面的元数据终结点 -->
              <serviceMetadata httpGetEnabled="true"/>
              <!-- 要接收故障异常详细信息以进行调试,请将以下值设置为 true。在部署前设置为 false 以避免泄漏异常信息 -->
              <serviceDebug includeExceptionDetailInFaults="false"/>
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
    
      </system.serviceModel>
      
     <system.webServer>
        <modules runAllManagedModulesForAllRequests="true"/>
     </system.webServer>
      
    </configuration>
    

      客户端的app.config 配置:

    <?xml version="1.0"?>
    <configuration>
    
      <startup useLegacyV2RuntimeActivationPolicy="true">
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
        <supportedRuntime version="v2.0.50727"/>
      </startup>
      
      <system.serviceModel>
        <bindings>
          <customBinding>
            <binding name="WSHttpBinding_SwfBuilderService">
              <gzipMessageEncoding innerMessageEncoding="textMessageEncoding" />
              <httpTransport manualAddressing="false" authenticationScheme="Anonymous"
                bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
                proxyAuthenticationScheme="Anonymous" realm="" useDefaultWebProxy="true" />
            </binding>
          </customBinding>
        </bindings>
        <client>
          
          <endpoint address="http://192.168.1.212/action.svc" binding="customBinding"
                  bindingConfiguration="WSHttpBinding_SwfBuilderService"
                  contract="WcfService.Iaction"
                  name="WSHttpBinding_SwfBuilderService">
          </endpoint>
          <metadata>
            <policyImporters>
              <extension type="GZipEncoder.GZipMessageEncodingBindingElementImporter, GZipEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
            </policyImporters>
          </metadata>
          
        </client>
    
        <!-- gzip 扩展 -->
        <extensions>
          <bindingElementExtensions>
            <add name="gzipMessageEncoding" type="GZipEncoder.GZipMessageEncodingElement, GZipEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
          </bindingElementExtensions>
        </extensions>
        
      </system.serviceModel>
     
    </configuration>
    

      这样就完成了WCF的数据压缩,其他代码都不用动,直接可以使用

          通过抓包看到Send和Recv的数据都为 application/x-gzip 格式

          

          

     另外出现第一次调用WCF很慢的解决方法是将 useDefaultWebProxy 这个属性改成false,不让它找代理。这样就会在程序第一次调用WCF的时候快很多。

  • 相关阅读:
    51nod1238. 最小公倍数之和 V3(数论)
    LOJ565. 「LibreOJ Round #10」mathematican 的二进制(NTT)
    【LCA】BZOJ1776-[Usaco2010 Hol]cowpol 奶牛政坛
    【单调队列】BZOJ1342-[Baltic2007]Sound静音问题
    【并查集&&带权并查集】BZOJ3296&&POJ1182
    【DFS好题】BZOJ1999- [Noip2007]Core树网的核(数据加强版)
    【Floyd矩阵乘法】BZOJ1706- [usaco2007 Nov]relays 奶牛接力跑
    【搜索+DP】codevs1066-引水入城
    【扩展欧几里得】BZOJ1477-青蛙的约会
    【扩展欧几里得】codevs1200-同余方程
  • 原文地址:https://www.cnblogs.com/relax/p/3423089.html
Copyright © 2011-2022 走看看