最近使用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, , 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 = ;
byte[] buffer = new byte[];
while ((readBytes = gStream.Read(buffer, , buffer.Length)) > )
{
mStream.Write(buffer, , 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 > )
{
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) > -;
} 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, , 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") >= )
{
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是一致的,是什么原因导致它们之间有巨大的性能差异就不得而知了……

WCF 消息压缩性能问题及解决方法的更多相关文章

  1. Silverlight 安装失败 提示 消息 ID 1603 的解决方法

    消息 ID: 1603 安装过程中出现错误.请执行以下步骤 原因是在以前安装过silverlight,没有安装成功或者没有彻底卸载干净,遗留了一些文件,尤其是安装时突然中断的时候会出现这个问题. 解决 ...

  2. WCF已超过传入消息(65536)的最大消息大小配额的解决方法

    在服务端和客户端的配置文件中添加修改节点 maxReceivedMessageSize="1000000000" ; 或者通过编程设置bind.MaxReceivedMessage ...

  3. 部署wcf出现的问题与解决方法

    我将本机作为服务器开发时,没出什么问题,将wcf服务端寄缩到另一台电脑上时,出现了一些问题,这里总结下: 1.wcf服务器和另一个网站应用出问题 服务器的iis上有一个网站应用,当我将wcf服务寄缩到 ...

  4. WCF Restful JQuery 跨域解决方法

    <?xml version="1.0"?> <!-- For more information on how to configure your ASP.NET ...

  5. WCF局域网内使用代理无法访问解决方法

    问题描述 在大部分事业单位上网都是需要使用代理的,前几天带着一个同事写的程序过来部署,部署以后各个客户端通过WCF相互通讯,那么其中一个地方在本地局域网测试是没有问题的. 后发现一部分是原因是由于代理 ...

  6. WCF服务无法访问DateTime类型的解决方法

    在WCF服务中,如果entity类含有DateTime类型的字段,那么接口将会被执行两次,从而出现无法访问的情况.如下图所示: 原因是WCF中DateTime无法转换成序列化JSON字符串,DateT ...

  7. 启动 SQL Server 管理 Studio 在 SQL Server 2008R2 中的错误消息:"无法读取此系统上以前注册的服务器的列表" 解决方法

    问题: 服务器被人直接停掉,重启后,发现sqlserver2008r2 启动管理器报错: "无法读取此系统上以前注册的服务器的列表" 如图: 点击继续,进入后: 解决方法: 点击上 ...

  8. 微信公众平台消息接口开发(2)你的服务器没有正确响应Token验证的解决方法

    你的服务器没有正确响应Token验证,请阅读消息接口使用指南 微信 微信公众平台开发模式 平台 消息 接口 启用 URL Token作者:http://txw1958.cnblogs.com/ 本系统 ...

  9. 服务器:消息18456,级别16,状态1 用户‘sa’登录失败解决方法

    无法连接到服务器**:  服务器:消息18456,级别16,状态1   [Microsoft][ODBC   SQL   Server   Driver][Sql   server]   用户 'sa ...

随机推荐

  1. HashMap、HashSet源代码分析其 Hash 存储机制

    集合和引用 就像引用类型的数组一样,当我们把 Java 对象放入数组之时,并不是真正的把 Java 对象放入数组中,只是把对象的引用放入数组中,每个数组元素都是一个引用变量. 实际上,HashSet ...

  2. Nodejs v4.x.0API文档学习(2)Assert断言测试模块

    文档参考地址:https://nodejs.org/dist/latest-v4.x/docs/api/ Assert(断言) assert模块提供了一组简单的断言测试方法,可以拥有测试不变量.该模块 ...

  3. JavaScript入门(10)

    一.Window对象 window对象是BOM的核心,window对象指当前的浏览器窗口 window对象方法 二.JavaScript计时器 在JavaScript中,我们可以在设定的时间间隔之后来 ...

  4. 【转】spring - ioc和aop

    [转]spring - ioc和aop 1.程序中为什么会用到spring的ioc和aop 2.什么是IOC,AOP,以及使用它们的好处,即详细回答了第一个问题 3.原理 关于1: a:我们平常使用对 ...

  5. JAXB - Annotations, Annotations for Enums: XmlEnum, XmlEnumValue

    An enum type is annotated with XmlEnum. It has an optional element value of type java.lang.Class whi ...

  6. Apache Mesos总体架构

    http://developer.51cto.com/art/201401/426507.htm 1. 前言 同其他大部分分布式系统一样,Apache Mesos为了简化设计,也是采用了master/ ...

  7. ASP生成静态文件编码为UTF-8格式的HTML文件

    一般在ASP环境下,运行动生静操作时都用到的是FSO,FSO是专门对文件进行操作的一个组件,FSO的编码属性只有三种,系统默认,Unicode,ASCII,并没有utf-8,所以一般中文系统上使用FS ...

  8. angularjs开发总结

    使用AngularJS有差不多一年时间了,前前后后也用了不少库和指令,整理了一下,分成四大类列出.有demo地址的,就直接连接到demo地址,其它的直接链到github托管库中. 图片视频类 angu ...

  9. 如何入侵Linux操作系统

    我发现了一个网站,于是常规入侵.很好,它的FINGER开着,于是我编了一个SHELL,aaa帐号试到zzz(by the way,这是我发现的一个网上规律,那就是帐号的长度与口令的强度成正比, 如果一 ...

  10. (已实现)相似度到大数据查找之Mysql 文章匹配的一些思路与提高查询速度

    需求,最近实现了文章的原创度检测功能,处理思路一是分词之后做搜索引擎匹配飘红,另一方面是量化词组,按文章.段落.句子做数据库查询,功能基本满足实际需求. 接下来,还需要在海量大数据中快速的查找到与一句 ...