您的位置:首页 > 其它

在 WCF 中使用高效的 BinaryFormatter 序列化

2013-06-26 21:06 357 查看
本文将定义一个 WCF 终结点行为扩展,以在 WCF 中使用更高效的 BinaryFormatter 进行二进制序列化,并实现对是否使用传统二进制序列化功能的可配置。
介绍
实现步骤
使用方法
效果

介绍


OEA 框架中,是使用 WCF 作为数据传输框架。但是使用 WCF 内部的二进制序列化,序列化后的数据大小,要比使用传统的 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter 类进行序列化后的数据大小要大得多。作为使用 .NET 框架的系统内部互联,往往期望在使用 WCF 获取统一传输方案的同时,还能得到 BinaryFormatter 类的序列化性能。所以本篇文章将设计一个 WCF 终结点行为扩展,来配置是否使用 BinaryFormatter 进行数据的序列化。


Tip
只能在操作上添加二进制序列化的行为。这是因为 WCF 的扩展点中,只有操作才支持设置 IClientMessageFormatter 及 IDispatchMessageFormatter。
WCF 中,要实现替换操作的序列化器,最直接的方式应该是使用一个实现 IOperationBehavior 的特性(Attribute),并将该特性直接标记到操作方法上。但是,这样会导致该方法在所有的终结点都使用 BinaryFormatter 来进行序列化。这并不是我们所想要的,所以只能使用配置的方法来对 WCF 进行扩展。

实现步骤



封装 BinaryFormatter
首先,需要对 BinaryFormatter 进行一个简单的封装。该类使用 BinaryFormatter 来实现对象到二进制流的序列化及反序列化。
1:  /// <summary>
2:  /// 序列化门户 API
3:  /// </summary>
4:  public static class Serializer
5:  {
6:      /// <summary>
7:      /// 使用二进制序列化对象。
8:      /// </summary>
9:      /// <param name="value"></param>
10:      /// <returns></returns>
11:      public static byte[] SerializeBytes(object value)
12:      {
13:          if (value == null) return null;
14:
15:          var stream = new MemoryStream();
16:          new BinaryFormatter().Serialize(stream, value);
17:
18:          //var dto = Encoding.UTF8.GetString(stream.GetBuffer());
19:          var bytes = stream.ToArray();
20:          return bytes;
21:      }
22:
23:      /// <summary>
24:      /// 使用二进制反序列化对象。
25:      /// </summary>
26:      /// <param name="bytes"></param>
27:      /// <returns></returns>
28:      public static object DeserializeBytes(byte[] bytes)
29:      {
30:          if (bytes == null) return null;
31:
32:          //var bytes = Encoding.UTF8.GetBytes(dto as string);
33:          var stream = new MemoryStream(bytes);
34:
35:          var result = new BinaryFormatter().Deserialize(stream);
36:
37:          return result;
38:      }
39:  }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; } .csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
添加 BinaryFormatterAdapter
添加一个 BinaryFormatterAdapter 类型,该类实现了从 WCF 序列化器到 BinaryFormatter 的甜适配。它实现 IClientMessageFormatter 及 IDispatchMessageFormatter 两个接口,并调用 Serializer 来进行二进制序列化。

1:  namespace OEA.WCF
2:  {
3:      /// <summary>
4:      /// 在内部序列化器的基础上添加 Remoting 二进制序列化的功能。
5:      /// </summary>
6:      internal class BinaryFormatterAdapter : IClientMessageFormatter, IDispatchMessageFormatter
7:      {
8:          private IClientMessageFormatter _innerClientFormatter;
9:          private IDispatchMessageFormatter _innerDispatchFormatter;
10:          private ParameterInfo[] _parameterInfos;
11:          private string _operationName;
12:          private string _action;
13:
14:       /// <summary> 
15:          /// for client
16:          /// </summary>
17:       /// <param name="operationName"></param> 
18:          /// <param name="parameterInfos"></param>
19:          /// <param name="innerClientFormatter"></param>
20:          /// <param name="action"></param>
21:          public BinaryFormatterAdapter(
22:           string operationName,
23:              ParameterInfo[] parameterInfos,
24:              IClientMessageFormatter innerClientFormatter,
25:              string action
26:              )
27:          {
28:              if (operationName == null) throw new ArgumentNullException("methodName");
29:              if (parameterInfos == null) throw new ArgumentNullException("parameterInfos");
30:              if (innerClientFormatter == null) throw new ArgumentNullException("innerClientFormatter");
31:           if (action == null) throw new ArgumentNullException("action");
32:
33:              this._innerClientFormatter = innerClientFormatter;
34:           this._parameterInfos = parameterInfos;
35:              this._operationName = operationName;
36:           this._action = action;
37:          }
38:
39:          /// <summary>
40:          /// for server
41:          /// </summary>
42:          /// <param name="operationName"></param>
43:          /// <param name="parameterInfos"></param>
44:          /// <param name="innerDispatchFormatter"></param>
45:          public BinaryFormatterAdapter(
46:              string operationName,
47:              ParameterInfo[] parameterInfos,
48:              IDispatchMessageFormatter innerDispatchFormatter
49:              )
50:          {
51:              if (operationName == null) throw new ArgumentNullException("operationName");
52:              if (parameterInfos == null) throw new ArgumentNullException("parameterInfos");
53:              if (innerDispatchFormatter == null) throw new ArgumentNullException("innerDispatchFormatter");
54:
55:              this._innerDispatchFormatter = innerDispatchFormatter;
56:              this._operationName = operationName;
57:              this._parameterInfos = parameterInfos;
58:          }
59:
60:          Message IClientMessageFormatter.SerializeRequest(MessageVersion messageVersion, object[] parameters)
61:          {
62:              var result = new object[parameters.Length];
63:              for (int i = 0; i < parameters.Length; i++) { result[i] = Serializer.SerializeBytes(parameters[i]); }
64:
65:              return _innerClientFormatter.SerializeRequest(messageVersion, result);
66:          }
67:
68:          object IClientMessageFormatter.DeserializeReply(Message message, object[] parameters)
69:          {
70:              var result = _innerClientFormatter.DeserializeReply(message, parameters);
71:
72:              for (int i = 0; i < parameters.Length; i++) { parameters[i] = Serializer.DeserializeBytes(parameters[i] as byte[]); }
73:              result = Serializer.DeserializeBytes(result as byte[]);
74:
75:              return result;
76:          }
77:
78:          void IDispatchMessageFormatter.DeserializeRequest(Message message, object[] parameters)
79:          {
80:              _innerDispatchFormatter.DeserializeRequest(message, parameters);
81:
82:              for (int i = 0; i < parameters.Length; i++) { parameters[i] = Serializer.DeserializeBytes(parameters[i] as byte[]); }
83:          }
84:
85:          Message IDispatchMessageFormatter.SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
86:          {
87:              var seralizedParameters = new object[parameters.Length];
88:              for (int i = 0; i < parameters.Length; i++) { seralizedParameters[i] = Serializer.SerializeBytes(parameters[i]); }
89:              var serialzedResult = Serializer.SerializeBytes(result);
90:
91:              return _innerDispatchFormatter.SerializeReply(messageVersion, seralizedParameters, serialzedResult);
92:          }
93:      }
94:  }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
添加 BinaryFormatterOperationBehavior
添加 BinaryFormatterOperationBehavior 操作行为类。这个类会设置客户端、服务端的操作的序列化器。
1:  namespace OEA.WCF
2:  {
3:      /// <summary>
4:      /// 在原始 Formatter 的基础上装饰 BinaryFormatterAdapter
5:      /// <remarks>
6:      /// BinaryFormatterOperationBehavior 为什么要实现为操作的行为:
7:      /// 因为只有当操作的 DataContractSerializerBehavior 行为应用功能后,才能拿到 DataContractSerializerFormatter 并包装到 BinaryFormatterAdapter 中。
8:      ///
9:      /// 由于一个操作的操作契约在系统中只有一份。而我们期望序列化的行为只影响指定的终结点,所以这个行为在应用时,会检查是否传入的运行时,即是添加时的运行时。
10:      /// </remarks>
11:      /// </summary>
12:      internal class BinaryFormatterOperationBehavior : IOperationBehavior
13:   {
14:       private object _runtime;
15:
16:          internal BinaryFormatterOperationBehavior(object runtime)
17:       {
18:              _runtime = runtime;
19:          }
20:
21:          /// <summary>
22:       /// 本行为只为这个运行时起作用。 
23:          /// </summary>
24:          public object ParentRuntime
25:          {
26:              get { return _runtime; }
27:          }
28:
29:          public void ApplyClientBehavior(OperationDescription description, ClientOperation runtime)
30:          {
31:           if (_runtime == runtime.Parent)
32:           {
33:                  //在之前的创建的 Formatter 的基础上,装饰新的 Formatter
34:               runtime.Formatter = new BinaryFormatterAdapter(description.Name, runtime.SyncMethod.GetParameters(), runtime.Formatter, runtime.Action);
35:              }
36:       }
37:
38:       public void ApplyDispatchBehavior(OperationDescription description, DispatchOperation runtime)
39:          {
40:              if (_runtime == runtime.Parent)
41:              {
42:                  runtime.Formatter = new BinaryFormatterAdapter(description.Name, description.SyncMethod.GetParameters(), runtime.Formatter);
43:              }
44:          }
45:
46:          public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters) { }
47:
48:          public void Validate(OperationDescription description) { }
49:      }
50:  }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
添加终结点行为 EnableBinaryFormatterBehavior
添加终结点行为 EnableBinaryFormatterBehavior,实现为该终结点下的所有操作添加 BinaryFormatterOperationBehavior 的逻辑。
1:  namespace OEA.WCF
2:  {
3:      class EnableBinaryFormatterBehavior : IEndpointBehavior
4:      {
5:          public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
6:          {
7:              foreach (var operation in endpoint.Contract.Operations)
8:              {
9:                  DecorateFormatterBehavior(operation, clientRuntime);
10:              }
11:          }
12:
13:       public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
14:       {
15:           foreach (var operation in endpoint.Contract.Operations)
16:              {
17:               DecorateFormatterBehavior(operation, endpointDispatcher.DispatchRuntime);
18:              }
19:          }
20:
21:          public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
22:
23:          public void Validate(ServiceEndpoint endpoint) { }
24:
25:          private static void DecorateFormatterBehavior(OperationDescription operation, object runtime)
26:          {
27:              //这个行为附加一次。
28:           var dfBehavior = operation.Behaviors.Find<BinaryFormatterOperationBehavior>();
29:              if (dfBehavior == null)
30:              {
31:               //装饰新的操作行为 
32:               //这个行为是操作的行为,但是我们期望只为当前终结点做操作的序列化,所以传入 runtime 进行区分。
33:                  dfBehavior = new BinaryFormatterOperationBehavior(runtime);
34:               operation.Behaviors.Add(dfBehavior);
35:              }
36:       }
37:   }
38:  }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
添加行为扩展元素 EnableBinaryFormatterBehaviorElement
添加终结点行为扩展类,使得该类在配置文件可以使用。它指定了对应的运行时行为类型是 EnableBinaryFormatterBehavior
1:  namespace OEA.WCF
2:  {
3:      /// <summary>
4:      /// 启用旧的 BinaryFormatter 来对数据进行序列化。
5:      /// </summary>
6:      public class EnableBinaryFormatterBehaviorElement : BehaviorExtensionElement
7:      {
8:          public override Type BehaviorType
9:          {
10:              get { return typeof(EnableBinaryFormatterBehavior); }
11:          }
12:
13:       protected override object CreateBehavior()
14:       {
15:           return new EnableBinaryFormatterBehavior();
16:          }
17:   }
18:  }

使用方法
要使用这个扩展,只需要在客户端、服务端做相应的配置即可:
服务端配置 在 system.serviceModel 中添加扩展及行为配置:
1:  <system.serviceModel>
2:      <behaviors>
3:          <endpointBehaviors>
4:              <behavior name="enableRemotingBinarySerialization">
5:                  <remotingBinarySerialization/>
6:              </behavior>
7:          </endpointBehaviors>
8:      </behaviors>
9:      <extensions>
10:          <behaviorExtensions>
11:              <add name="remotingBinarySerialization" type="OEA.WCF.EnableBinaryFormatterBehaviorElement, OEA"/>
12:       </behaviorExtensions> 
13:   </extensions> 
14:  </system.serviceModel>
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

为服务终结点添加行为配置 behaviorConfiguration="enableRemotingBinarySerialization"。

1:  <system.serviceModel>
2:      <services>
3:          <service name="OEA.Server.Hosts.WcfPortal" behaviorConfiguration="includesException">
4:              <endpoint address="/Binary" binding="customBinding" bindingConfiguration="compactBindingConfig"
5:                          behaviorConfiguration="enableRemotingBinarySerialization"
6:                          contract="OEA.Server.Hosts.IWcfPortal"/>
7:          </service>
8:      </services>
9:  </system.serviceModel>
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
客户端 客户端同样添加相应的扩展及行为配置,并添加到服务终结点上即可。



效果
效果图:



以上是使用公司目前正在开发的系统的数据量进行测试的结果。可以看到,使用 WCF 直接二进制序列化时,32000 行数据序列化后大小是 28.34M(黄底),而启用这个扩展进行序列化后大小是 13.89M(浅绿底)。当同时使用 WCF 二进制序列化及 BinaryFormatter 序列化后,数据大小是10.42 M(绿底)。


Note
同时使用多次序列化,虽然数据量会更小,但是序列化时间却增多。使用时,需要根据实际情况来调整。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐