WCF中自定义消息编码器:压缩编码器的使用

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

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

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

WCF中自定义消息编码器:压缩编码器的使用

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中自定义消息编码器:压缩编码器的使用

WCF中自定义消息编码器:压缩编码器的使用

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

WCF中自定义消息编码器:压缩编码器的使用

上一篇:Python fractions模块 —— 分数相关函数


下一篇:hdoj 3157 Crazy Circuits 【有下界最小流】