• 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是一致的,是什么原因导致它们之间有巨大的性能差异就不得而知了……

  • 相关阅读:
    ORACLE数据库表解锁record is locked by another user
    Oracle11gR2设置连接数process与会话session值
    Oracle 11g用exp无法导出空表的处理方法
    Oracle随机选择一条记录SQL
    Oracle取查询结果数据的第一条记录SQL
    Hibernate 一对多查询对set的排序
    Windows平台下Oracle实例启动过程中日志输出
    Windows平台下Oracle监听服务启动过程中日志输出
    Windows平台下Oracle 11g R2监听文件日志过大,造成客户端无法连接的问题处理
    WebSphere设置会话超时时间
  • 原文地址:https://www.cnblogs.com/myjece/p/3269980.html
Copyright © 2020-2023  润新知