深入理解WCF系统体系(之二:WCF客户端如何构建?(上))

  前一节(《WCF体系架构(之一:Client与Server信息交互处理流程初略介绍)》)大致介绍了WCF服务消息处理的大致流程,WCF服务的消费者可以是WEB客户端、也可以是其他语言开发的应用程序。
  对于WEB客户端以及其他语言的应用程序,跨平台的性能主要是通过HTTP协议+SOAP消息机制实现。本节主要详细介绍消息在WCF客户端应用程序消息处理流程
------------------------------------------------------------------
-目录:
-1、WCF通过客户端代理调用 服务
-2、实际代理如何映射到服务目标对象上
-3、WCF客户端框架的核心ClientRuntime建立过程
-4、ImmutableClientRuntime对象的作用
-5、客户端操作选择器MethodInfoOperationSelector
-6、ProxyOperationRuntime的作用
------------------------------------------------------------------


1、WCF如何处理客户端消息调                          


  如果有.Net Remoting开发经验,大家一定还记得在Remoting的客户端,调用服务通过透明代理(TransparentProxy)来对服务进行调用,然后透明代理将对服务的调用转交给实际代理(RealProxy)。在WCF同样如此。那透明代理对应的实际代理又是什么类型的呢。?看看下面的测试例子就知道了。

var instanceContext = new InstanceContext(new CalculatorCallback());
using (var channkeFactory = new DuplexChannelFactory<ICalculator>(instanceContext, "calculator"))
{
    ICalculator proxy = channkeFactory.CreateChannel();
    Console.WriteLine("是否是透明代   理:"+RemotingServices.IsTransparentProxy(proxy));
    Console.WriteLine("透明代理类型:" + proxy.GetType());
}

 输出结果如下:

 

2、实际代理如何映射到服务目标对象上              


首先看看实际代理对象ServiceChannelProxy字段的定义:

internal sealed class ServiceChannelProxy : RealProxy, IRemotingTypeInfo
{
  // Fields
  private const string activityIdSlotName = "E2ETrace.ActivityID";
  private Type interfaceType;
  private MethodDataCache methodDataCache;
  private MbrObject objectWrapper;
  private Type proxiedType;
  private ImmutableClientRuntime proxyRuntime;
  private ServiceChannel serviceChannel;
  /*
  */
  public override IMessage Invoke(IMessage message)
  {
    IMessage message3;
    try
    {
      IMethodCallMessage methodCall = message as IMethodCallMessage;
      if (methodCall == null)
      {
        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR.GetString("SFxExpectedIMethodCallMessage")));
      }
      MethodData methodData = this.GetMethodData(methodCall);
      switch (methodData.MethodType)
      {
        case MethodType.Service:
          return this.InvokeService(methodCall, methodData.Operation);
 
        case MethodType.BeginService:
          return this.InvokeBeginService(methodCall, methodData.Operation);
 
        case MethodType.EndService:
          return this.InvokeEndService(methodCall, methodData.Operation);
 
        case MethodType.Channel:
          return this.InvokeChannel(methodCall);
 
        case MethodType.Object:
          return this.InvokeObject(methodCall);
 
        case MethodType.GetType:
          return this.InvokeGetType(methodCall);
       }
      throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Invalid proxy method type", new object[0])));
    }
    catch (Exception exception)
    {
      if (Fx.IsFatal(exception))
      {
        throw;
      }
      message3 = this.CreateReturnMessage(exception, message as IMethodCallMessage);
    }
    return message3;
}
 
  private IMethodReturnMessage InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
{
  object[] objArray;
  object[] ins = operation.MapSyncInputs(methodCall, out objArray);
  object ret = this.serviceChannel.Call(operation.Action, operation.IsOneWay, operation, ins, objArray);
  object[] returnArgs = operation.MapSyncOutputs(methodCall, objArray, ref ret);
  return this.CreateReturnMessage(ret, returnArgs, methodCall);
  }
}

  先对这几个对象做个介绍:

  1. interfaceType与proxiedType就是服务契约的Type,methodDataCache存放方法信息的容器
  2. objectWrapper:建立代理对象与服务对象之间映射关系的对象
  3. proxyRuntime:是恒定客户端运行时(这个是我自己翻译的,只在此有意义。为了区分它与客户端运行时ClientRuntime的区别而已)。同ClientRuntime一样,它也是WCF客户端体系中很重要的一个对象,它涉及到调用信息的序列化、反序列化、IClientOperationsSelector、IClientMessageInspector、IChannelInitializer等核心对象,稍后会介绍到。
  4. serviceChannel:服务信道。

其中有个 MbrObject类型的 objectWrapper定义。MbrObject的定义如下:

private class MbrObject : MarshalByRefObject
{
  // Fields
  private RealProxy proxy;
  private Type targetType;
 
  // Methods
  internal MbrObject(RealProxy proxy, Type targetType)
  {
    this.proxy = proxy;
    this.targetType = targetType;
  }
 
  public override bool Equals(object obj)
  {
    return object.ReferenceEquals(obj, this.proxy.GetTransparentProxy());
  }
 
  public override int GetHashCode()
  {
    return this.proxy.GetHashCode();
  }
 
  public override string ToString()
  {
    return this.targetType.ToString();
  }
}

  看看它的构造函数使用的参数可知:通过实际代理对象以及目标代理类型,将实际代理对象映射到了实现契约接口的对象上。

  还有个问题:RealProxy是映射到了最终服务对象上,那是通过什么样的方式或者说是如何进行映射的呢。?
ServiceChannelProxy对象在WCF体系内部构造ServiceChannelProxy对象时有个构造函数,它建立了代理对象与实际服务对象:this.objectWrapper = new MbrObject(this, proxiedType);这样就建立了代理对象同服务对象之间的映射

  查看代理信息就发现了他们之间的关系,如下图:

 


客户端调用服务端方法最终通过ServiceChannelProxy进行调用。以上只列出了同步调用的方法,在ServiceChannelFactory中还有异步调用的方法,详情请参见ServiceChannelFactory类。
从InvokeService中可以看出,进行调用的时候,使用了serviceChannel.Call进行调用。同样,在异步方法中也是通过ServiceChannel对象的BeginInvoke与EndInvoke进行调用。也就是说最终的调用是通过ServiceChannel完成。

 

3、WCF客户端框架的核心ClientRuntime建立过程


  ClientRuntime是与WCF服务端框架中DispatchRuntime对应的客户端框架的核心。那么ClientRuntime是如何建立的。?

  ClientRuntime建立的过程比较复杂。下面通过序号标明ClientRuntime建立的过程。

3.1、建立Channel                                                       


  无论在WCF的客户端还是服务端,我们通常都会选择一种或者多种通信协议。绑定协议包含许多绑定元素(BindingElementCollection)。以NetTcpBinding来说,它就包含 以下四种绑定元素:
TransactionFlowBindingElement context;
BinaryMessageEncodingBindingElement encoding;
ReliableSessionBindingElement session;
TcpTransportBindingElement transport;

每个绑定元素穿件信道工厂,BindingElementCollection创建的是信道工厂堆栈,信道就是由这些ChannelFactory Stack按照顺序依次建立起来的Channel Stack。


3.2、建立过程ChannelFactory                                       


  先看看以下例子:var channkeFactory = new DuplexChannelFactory<ICalculator>(instanceContext, "calculator");这样就信道工厂就建立。在这个例子中用回调对象对象与EndpointName为参数建立ChannelFactory。

DuplexChannelFactory有很多构造器,
public class DuplexChannelFactory<TChannel> : ChannelFactory<TChannel>
{
  // Methods
  public DuplexChannelFactory(object callbackObject) : base(typeof(TChannel))
  {
    using (ServiceModelActivity activity = DiagnosticUtility.ShouldUseActivity ? ServiceModelActivity.CreateBoundedActivity() : null)
    {
      if (DiagnosticUtility.ShouldUseActivity)
      {
        ServiceModelActivity.Start(activity, SR.GetString("ActivityConstructChannelFactory", new object[] { TraceUtility.CreateSourceString(this) }), ActivityType.Construct);
      }
      if (callbackObject == null)
      {
        throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("callbackObject");
      }
      this.CheckAndAssignCallbackInstance(callbackObject);
      base.InitializeEndpoint((string) null, null);
    }
  }
 
public DuplexChannelFactory(object callbackObject, string endpointConfigurationName) : this(callbackObject, endpointConfigurationName, (EndpointAddress) null)
{
}
 
public DuplexChannelFactory(object callbackObject, string endpointConfigurationName, EndpointAddress remoteAddress) : base(typeof(TChannel))
{
  using (ServiceModelActivity activity = DiagnosticUtility.ShouldUseActivity ? ServiceModelActivity.CreateBoundedActivity() : null)
  {
    if (DiagnosticUtility.ShouldUseActivity)
    {
      ServiceModelActivity.Start(activity, SR.GetString("ActivityConstructChannelFactory", new object[] { TraceUtility.CreateSourceString(this) }), ActivityType.Construct);
    }
    if (callbackObject == null)
    {
      throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("callbackObject");
    }
    if (endpointConfigurationName == null)
    {
      throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpointConfigurationName");
    }
    this.CheckAndAssignCallbackInstance(callbackObject);
    base.InitializeEndpoint(endpointConfigurationName, remoteAddress);
  }
 }
/*
......
*/
}

  

以上列举了上述例子中使用的构造器。从中可知:在创建ChannelFactory的过程中,通过ChannelFactory<TChannel>的InitializeEndpoint对Endpoint进行了初始化。


3.3、根据建立的Endpoint创建ServiceChannelFactory              

 

 

从ClientRuntime clientRuntime = DispatcherBuilder.BuildProxyBehavior(serviceEndpoint, out parameters);

可知:ClientRuntime 由DispatcherBuilder创建。
注:ServiceChannel由ServiceChannelFactory创建,同ServiceChannelProxy使用。在创建ServiceChannel的过程中对Channel进行初始化。处理函数如下:

public object CreateChannel(Type channelType, EndpointAddress address, Uri via)
{
    if (via == null)
    {
      via = this.ClientRuntime.Via;
      if (via == null)
      {
        via = address.Uri;
      }
    }
    ServiceChannel serviceChannel = this.CreateServiceChannel(address, via);
    serviceChannel.Proxy = CreateProxy(channelType, channelType, MessageDirection.Input, serviceChanne);
    serviceChannel.ClientRuntime.GetRuntime().InitializeChannel((IClientChannel) serviceChannel.Proxy);
    OperationContext current = OperationContext.Current;
    if ((current != null) && (current.InstanceContext != null))
    {
      current.InstanceContext.WmiChannels.Add((IChannel) serviceChannel.Proxy);
      serviceChannel.WmiInstanceContext = current.InstanceContext;
    }
    return serviceChannel.Proxy;
}

 

3.4、DispatcherBuilder创建ClientRuntime

internal class DispatcherBuilder
{
  internal static ClientRuntime BuildProxyBehavior(ServiceEndpoint serviceEndpoint, out BindingParameterCollection parameters)
  {
    parameters = new BindingParameterCollection();
    SecurityContractInformationEndpointBehavior.ClientInstance.AddBindingParameters(serviceEndpoint, parameters);
    AddBindingParameters(serviceEndpoint, parameters);
    ContractDescription contract = serviceEndpoint.Contract;
    ClientRuntime parent = new ClientRuntime(contract.Name, contract.Namespace);
    parent.ContractClientType = contract.ContractType;
    IdentityVerifier property = serviceEndpoint.Binding.GetProperty<IdentityVerifier>(parameters);
    if (property != null)
    {
      parent.IdentityVerifier = property;
    }
    for (int i = 0; i < contract.Operations.Count; i++)
    {
      OperationDescription operation = contract.Operations[i];
      if (!operation.IsServerInitiated())
      {
        BuildProxyOperation(operation, parent);
      }
      else
      {
        BuildDispatchOperation(operation, parent.CallbackDispatchRuntime, null);
      }
    }
    ApplyClientBehavior(serviceEndpoint, parent);
    return parent;
  }
/**/
}

  由Endpoint信息可以获取到ContractDescription,进而获取到Operations等等,从而赋值给ClientRuntime对象,完成ClientRuntime对象的建立。

以上还有个问题:DispatchRuntime 是与ClientRuntime相对象的WCF服务端分发运行时,同ClientRuntime一样,它是WCF服务端核心对象。在客户端怎么会后服务端的的分发运行时呢。?原因很简单,在WCF数据包模式以及Request-Reply模式下,DispatchRuntime是不需要的,但是在双工模式时,Server端与Client端已经不明确,
Server与Client互发消息,即是服务端也是客户端。所以不仅在ClientRuntime中存在DispatchRuntime,在DispatchRuntime同样存在ClientRuntime。

ClientRuntime作为客户端框架的核心,它决定着消息的格式化(IClientMessageFormatter)、客户端操作选择器(IClientOperationSelector)、客户端消息检查器(IClientMessageInspectors)等等。

4、ImmutableClientRuntime对象的作用                   


  客户端对服务端的操作是通过TransparentProxy到RealProxy,也就是ServiceChannelProxy对象中。上面提到过,客户端调用服务的实际代理ServiceChannelProxy对象有一个名称为proxyRuntime的字段,类型就是ImmutableClientRuntime。
  ImmutableClientRuntime对象依赖于ClientRuntime,这点可以从ImmutableClientRuntime的构造函数中就可以看出。

internal ImmutableClientRuntime(ClientRuntime behavior)
{
    this.channelInitializers = EmptyArray<IChannelInitializer>.ToArray(behavior.ChannelInitializers);
    this.interactiveChannelInitializers = EmptyArray<IInteractiveChannelInitializer>.ToArray(behavior.InteractiveChannelInitializers);
    this.messageInspectors = EmptyArray<IClientMessageInspector>.ToArray(behavior.MessageInspectors);
    this.operationSelector = behavior.OperationSelector;
    this.useSynchronizationContext = behavior.UseSynchronizationContext;
    this.validateMustUnderstand = behavior.ValidateMustUnderstand;
    this.unhandled = new ProxyOperationRuntime(behavior.UnhandledClientOperation, this);
    this.addTransactionFlowProperties = behavior.AddTransactionFlowProperties;
    this.operations = new Dictionary<string, ProxyOperationRuntime>();
    for (int i = 0; i < behavior.Operations.Count; i++)
    {
      ClientOperation operation = behavior.Operations[i];
      ProxyOperationRuntime runtime = new ProxyOperationRuntime(operation, this);
      this.operations.Add(operation.Name, runtime);
    }
    this.correlationCount = this.messageInspectors.Length + behavior.MaxParameterInspectors;
}

 

ImmutableClientRuntime在RealProxy中起着至关重要的作用。如下图是RealProxy的信息:

由上图可知:

1、operations为Dictionary<string, ProxyOperationRuntime>类型,其中key为契约接口中标识位OperationContractAttribute的方法名,Value为ProxyOperationRuntime。

2、MessageInspector是实现了IClientMessageInspector接口的Microsoft.VisualStudio.Diagnostics.ServiceModelSink.StubClientEventSink类型。
3、客户端OperationSelector是MethodInfoOperationSelector类型的,MethodInfoOperationSelector实现了IClientOperationSelector接口。
4、客户端消息检查器IClientMessageInspectors为Microsoft.VisualStudio.Diagnostics.ServiceModelSink.StubClientEventSink类型。


5、客户端操作选择器MethodInfoOperationSelector。             


通过工具看看MethodInfoOperationSelector的定义如下:

internal class MethodInfoOperationSelector : IClientOperationSelector
{
  // Fields
  private Dictionary<object, string> operationMap = new Dictionary<object, string>();
 
  // Methods
  internal MethodInfoOperationSelector(ContractDescription description, MessageDirection directionThatRequiresClientOpSelection)
  {
    for (int i = 0; i < description.Operations.Count; i++)
    {
      OperationDescription description2 = description.Operations[i];
      if (description2.Messages[0].Direction == directionThatRequiresClientOpSelection)
      {
        if ((description2.SyncMethod != null) && !this.operationMap.ContainsKey(description2.SyncMethod.MethodHandle))
        {
          this.operationMap.Add(description2.SyncMethod.MethodHandle, description2.Name);
        }
        if ((description2.BeginMethod != null) && !this.operationMap.ContainsKey(description2.BeginMethod.MethodHandle))
        {
        this.operationMap.Add(description2.BeginMethod.MethodHandle, description2.Name);
        this.operationMap.Add(description2.EndMethod.MethodHandle, description2.Name);
        }
      }
    }
  }
 
  public string SelectOperation(MethodBase method, object[] parameters)
  {
    if (this.operationMap.ContainsKey(method.MethodHandle))
    {
      return this.operationMap[method.MethodHandle];
    }
    return null;
  }
 
  // Properties
  public bool AreParametersRequiredForSelection
  {
    get
    {
      return false;
    }
  }
}

  

operationMap为存放方法的字段,key方法句柄信息,Value为方法名。
MethodInfoOperationSelector通过SelectOperation根据方法句柄值获取方法名,然后通过方法名,获取ProxyOperationRuntime对象。


查看OperationSelector的operationMap中Add方法的句柄值如下图所示:


通过控制台输出契约接口中方法的句柄值如下:


-6、ProxyOperationRuntime的作用                                     

了解它的作用,还是从源码开始。

由上图可以得知:
1、对返回值进行序列化时,使用的类型是PrimitiveOperationFormatter,它同时实现了客户端序列化接口IClientMessageFormatter与服务端序列化IDispatchMessageFormatter接口
2、错误契约使用的序列化器是DataContractSerializerFaultFormatter。
3、对服务调用参数的序列化以及参数检查,对返回消息进行反序列化及参数检查通过ProxyOperationRuntime对象完成。

  对实际代理ServiceChannelProxy对象中的ImmutableClientRuntime类型的字段realProxy以及realProxy的MessageInspector、operations添加监视,查看其信息如下:

图1:realProxy监视信息

图2:realProxy的消息检查器MessageInspector监视信息

图3:realProxy的operations监视信息

由以上3个图中显示的信息可知:

1、operations为Dictionary<string, ProxyOperationRuntime>类型,其中key为契约接口中标识位OperationContractAttribute的方法名,Value为ProxyOperationRuntime。

2、MessageInspector是实现了IClientMessageInspector接口的客户端消息检查器Microsoft.VisualStudio.Diagnostics.ServiceModelSink.StubClientEventSink类型。
3、客户端OperationSelector是MethodInfoOperationSelector类型的,MethodInfoOperationSelector实现了IClientOperationSelector接口。

参考:http://www.cnblogs.com/artech/tag/WCF/

   《WCF揭秘》

posted @   tyb1222  阅读(3330)  评论(3编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示