CompactMessageEncoder
CompactMessageEncoder 可在通信通道上透明地压缩消息,从而降低网络使用量。
引言
有很多关于 WCF 如何工作的文章,所以在这篇文章中我将跳过这部分。本文代码的目的是让用户能够压缩使用 WCF 在机器之间传输的数据。(为简单起见,我使用 WCF 这个术语,而不是指具体的组件。)
当 WCF 用于两个实体之间的通信时,它会创建一个通信通道来传输消息。消息包含请求和响应的数据。WCF 使用特殊的编码器将请求和响应数据转换为字节数组。
CompactMessageEncoder
会连接到客户端和服务器两端的通道。当消息传输时,编码器会在发送端进行压缩,在接收端进行解压缩,因此是透明的。
背景
我实现此类编码器的驱动力是需要将一个大文件(XML 格式)从一台机器传输到另一台机器,而连接速度有限。
我选择通过 WCF 来实现这一点(传输完成后还有更多工作要做)。我不想编写一个在发送端压缩、在接收端解压缩的特殊函数,而是希望配置通道本身来完成这项工作。这样,压缩就可以在任何契约上重用。
在互联网上搜索和研究后,我发现编写消息编码器是最简单的方法。真正的问题是如何编写消息编码器,因为 MSDN 上没有任何示例(至少我没找到)。我找到的任何示例都已过时,因为它们是在 WCF 正式发布之前编写的。然而,这些示例给了我如何编写消息编码器的坚实基础。
我的实现基于我在机器上安装的一个示例,但我不知道它的来源。我在 C:\Program Files\Microsoft SDKs\WinFX\samples\Allsamples\Indigo\TechnologySamples\Extensibility\
Channels\MessageEncoder\Compression\CS 中找到了它。该示例正是我最初想要的,但可惜它无法工作。它为 WCF 的早期版本编写,包含已不再存在的接口和类。从这个示例中,我获得了如何压缩和解压缩消息的知识。我还注意到实现中有两个小错误,我已经修复了它们。
我从 Nicholas Allen 的博客 http://blogs.msdn.com/drnick/archive/2006/05/09/592933.aspx 中获得了一些帮助。但是,它不包含如何将消息编码器添加到配置文件中的代码。
我还使用了 Reflector 来查看其他消息编码器是如何工作的,例如
System.ServiceModel.Channels.BinaryMessageEncoderFactory.BinaryMessageEncoder.
工作原理
Compact message encoder 的想法是在通道上挂钩消息的发送和接收,在发送端压缩它们,然后在接收端解压缩它们。要植入消息编码器,需要在 customBinding
中添加一个绑定元素。
由于压缩本身不需要进行编码,它会使用另一个消息编码器来完成,例如 BinaryMessageEncoder
。
正常服务方法执行
发送端
- 在代码中调用一个方法。
- 方法及其参数被序列化为 SOAP 消息。
- 消息编码器将消息序列化为字节数组。
- 字节数组通过传输层发送。
接收端
- 传输层接收字节数组。
- 消息编码器将字节数组反序列化为消息。
- 方法及其参数被反序列化为 SOAP 消息。
- 调用实际的方法。
当添加了 compact message encoder 时,方法调用会稍有改变
发送端
- 在代码中调用一个方法。
- 方法及其参数被序列化为 SOAP 消息。
- Compact message encoder 让其内部消息编码器将消息序列化为字节数组。
- Compact message encoder 将字节数组压缩为第二个字节数组。
- 字节数组通过传输层发送。
接收端
- 传输层接收字节数组。
- Compact message encoder 将字节数组解压缩为第二个字节数组。
- Compact message encoder 让其内部消息编码器将第二个字节数组反序列化为消息。
- 方法及其参数被反序列化为 SOAP 消息。
- 调用实际的方法。
Compact message encoder 分为几个类
CompactMessageEncoder
- 此类提供消息编码器实现。
CompactMessageEncoderFactory
- 此类负责提供 Compact message encoder 实例。
CompactMessageEncodingBindingElement
- 这是参与通道绑定堆栈的类。
CompactMessageEncodingElement
- 这是允许通过应用程序配置文件添加消息编码器的类。
压缩
CompactMessageEncoder
使用 .NET Framework 中实现的 GZip 压缩。这是通过 System.IO.Compression.GZipStream
实现的。
如何使用
添加对 CompactMessageEncoder.dll 的引用
在更改 app.config 之前,您必须添加对 CompactMessageEncoder.dll 的引用。这必须在客户端和服务器应用程序上都完成。
服务器配置更改
这是添加 compact message encoder 之前的 app.config 示例
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="Server.MyService">
<endpoint
address="net.tcp://:1234/MyService"
binding="netTcpBinding"
contract="Server.IMyService" />
</service>
</services>
</system.serviceModel>
</configuration>
这是添加 compact message encoder 之后的 app.config 示例
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="Server.MyService">
<!-- Set the binding of the endpoint to customBinding -->
<endpoint
address="net.tcp://:1234/MyService"
binding="customBinding"
contract="Server.IMyService" />
</service>
</services>
<!-- Defines a new customBinding that contains the compactMessageEncoding -->
<bindings>
<customBinding>
<binding name="compactBinding">
<compactMessageEncoding>
<!-- Defines the inner message encoder as binary encoder -->
<binaryMessageEncoding />
</compactMessageEncoding>
<tcpTransport />
</binding>
</customBinding>
</bindings>
<!-- Adds the extension DLL so the WCF can find the compactMessageEncoding -->
<extensions>
<bindingElementExtensions>
<add name="compactMessageEncoding"
type="Amib.WCF.CompactMessageEncodingElement,
CompactMessageEncoder, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null" />
</bindingElementExtensions>
</extensions>
</system.serviceModel>
</configuration>
客户端配置更改
这是添加 compact message encoder 之前的 app.config 示例
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<client>
<endpoint
address="net.tcp://:1234/MyService"
binding="customBinding"
bindingConfiguration="compactBinding"
contract="Client.IMyService" />
</client>
</system.serviceModel>
</configuration>
This is an example of the app.config after adding the compact message encoder:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<client>
<endpoint
address="net.tcp://:1234/MyService"
binding="customBinding"
bindingConfiguration="compactBinding"
contract="Client.IMyService" />
</client>
<!-- Defines a new customBinding that contains the compactMessageEncoding -->
<bindings>
<customBinding>
<binding name="compactBinding">
<compactMessageEncoding>
<binaryMessageEncoding/>
</compactMessageEncoding>
<tcpTransport />
</binding>
</customBinding>
</bindings>
<!-- Adds the extension DLL so the WCF can find the compactMessageEncoding -->
<extensions>
<bindingElementExtensions>
<add name="compactMessageEncoding"
type="Amib.WCF.CompactMessageEncodingElement,
CompactMessageEncoder, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null" />
</bindingElementExtensions>
</extensions>
</system.serviceModel>
</configuration>
限制 & 解决方法
- 所有消息都会在通道上进行压缩,即使这会导致消息膨胀。当消息很小时会发生这种情况。
CompactMessageEncoder
仅支持 Buffered 传输,不支持 Streamed 传输。- 在同一台机器上运行的客户端和服务器上使用
CompactMessageEncoder
可能会降低性能。 - WCF 配置编辑器对
CompactMessageEncoder
的支持不完全,因此部分配置必须使用 XML 文本编辑器手动完成。 - 每次 WCF 配置编辑器打开 app.config 文件时,它都会询问 CompactMessageEncoder.dll 的安全性。我不知道如何摆脱这种行为。
binaryMessageEncoding
和textMessageEncoding
的配置无法在 WCF 配置编辑器中进行编辑。要解决此问题,请从 app.config 中删除compactMessageEncoding
元素(保留其子元素),然后使用 WCF 配置编辑器打开它。之后,再将compactMessageEncoding
元素添加回来。
免责声明
本代码和信息“按原样”提供,不附带任何形式的保修,无论是明示的还是暗示的,包括但不限于对适销性和/或特定用途适用性的暗示保证。
历史
- 2007年9月5日:初始版本