首页 > 代码库 > WCF 代理 是怎么工作的?用代码说话

WCF 代理 是怎么工作的?用代码说话

 

1.WCF生成代理的方式

2.WCF代理原理

 

 

 

第一个问题引用 一篇Robin‘s博文[WCF生成客户端对象方式解析] 讲述了创建客户端服务对象的方法

1.代理构造法

       a.开启服务后,添加服务引用

       b.知道元数据地址,通过svcutli.exe生成代理类和配置文件

       c.从服务契约DLL中导出元数据,然后更具本地的元数据文件生成代理类和配置文件

       d.知道元数据地址,自己编写代码生成(使用ServiceContractGenerator类等),生成代理类和配置文件

2.通道工厂(ChannelFactory<T>)

       a.知道终结点地址,绑定协议(ABC中的A和B)

       b.只知道元数据终结点地址(代码中使用MetadataResover类获取服务信息)

          文章最后附有代码:代码,可以下下来运行测试等。

 

下边来看看生成的代理是什么样的。

1.添加服务引用 生成了一个 继承自 System.ServiceModel.ClientBase<Wcf.Client.ServiceReference1.IService> 的 ServiceClient

public interface IService {                [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IService/DoWork", ReplyAction="http://tempuri.org/IService/DoWorkResponse")]        void DoWork();                [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IService/GetData", ReplyAction="http://tempuri.org/IService/GetDataResponse")]        Wcf.Client.ServiceReference1.MyData GetData(int field);    } [System.Diagnostics.DebuggerStepThroughAttribute()]    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]    public partial class ServiceClient : System.ServiceModel.ClientBase<Wcf.Client.ServiceReference1.IService>, Wcf.Client.ServiceReference1.IService {                public ServiceClient() {        }                public ServiceClient(string endpointConfigurationName) :                 base(endpointConfigurationName) {        }                public ServiceClient(string endpointConfigurationName, string remoteAddress) :                 base(endpointConfigurationName, remoteAddress) {        }                public ServiceClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :                 base(endpointConfigurationName, remoteAddress) {        }                public ServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :                 base(binding, remoteAddress) {        }                public void DoWork() {            base.Channel.DoWork();        }                public Wcf.Client.ServiceReference1.MyData GetData(int field) {            return base.Channel.GetData(field);        }    }}

生成的代码中有接口,服务客户端(ServiceClient)

调用DoWork()和GetData(int field) 方法,是调用的 ClientBase<T>中Channel对象的DoWork()和GetData(field)方法

ClientBase<T>

public abstract class ClientBase<TChannel> : ICommunicationObject, IDisposable where TChannel : class{    ……//其他内容       protected TChannel Channel { get; }    public ChannelFactory<TChannel> ChannelFactory { get; }}

Channel 的类型是TChannel ,同时可以发现内部有个ChannelFactory。

2.使用ChannelFactory<T>

ChannelFactory<Proxys.IService> channelFactory = new ChannelFactory<Proxys.IService>(bind);var channel = channelFactory.CreateChannel(address); using (channel as IDisposable) {    channel.DoWork();    Wcf.Proxys.MyData myData = channel.GetData(10);}

每次我们看代码都只能看到这里,却不能知道ClientBase<T>,ChannelFatctory<T>是怎么起到代理作用的,怎么把方法的调用转换成底层的交互的。

 

我们来找找源头:(本来是想反编译ServiceModel.dll,翻遍以后有不能看到代码内容,好在Mono下有相应的模块内容,可以在“开源中国社区”看到一些代码)

ClientBase<T> Mono开源代码地址

212  protected TChannel Channel {213  get { return (TChannel) (object) InnerChannel; }214 }

看到212行代码,channel返回的属性InnerChannel

204  public IClientChannel InnerChannel {205  get {206         if (inner_channel == null)207                inner_channel = (IClientChannel) (object) CreateChannel ();208              return inner_channel;209          }210     }

通过CreateChannel ()创建的对象

304     protected virtual TChannel CreateChannel ()305     {306         return ChannelFactory.CreateChannel ();307     }

CreateChannel ()方法是用ChannelFactory.CreateChannel (); 创建对象

188     public ChannelFactory<TChannel> ChannelFactory {189         get { return factory; }190         internal set {191             factory = value;192             factory.OwnerClientBase = this;193         }194     }

跳转到 ChannelFactory<T>类 地址

104     public TChannel CreateChannel ()105     {106         EnsureOpened ();107108         return CreateChannel (Endpoint.Address);109     }

CreateChannel方法的一个重载方法

133     public virtual TChannel CreateChannel (EndpointAddress address, Uri via)134     {135   #if MONOTOUCH136         throw new InvalidOperationException ("MonoTouch does not support dynamic proxy code generation. Override this method or its caller to return specific client proxy instance");137  #else138         var existing = Endpoint.Address;139         try {140141         Endpoint.Address = address;142         EnsureOpened ();143         Endpoint.Validate ();144         Type type = ClientProxyGenerator.CreateProxyType (typeof (TChannel), Endpoint.Contract, false);145         // in .NET and SL2, it seems that the proxy is RealProxy.146         // But since there is no remoting in SL2 (and we have147         // no special magic), we have to use different approach148         // that should work either.149         object proxy = Activator.CreateInstance (type, new object [] {Endpoint, this, address ?? Endpoint.Address, via});150         return (TChannel) proxy;151         } catch (TargetInvocationException ex) {152             if (ex.InnerException != null)153                 throw ex.InnerException;154             else155                 throw;156         } finally {157             Endpoint.Address = existing;158         }159  #endif160     }

翻一下一下注释:在.NET和SL2.0中,看来好像这个代理是真实代理,因为在SL2.0中没有是用Remoting(并且也没有独特魔力),我们必须是用不同的方式使它依然能够工作运行起来。

 

这里注意两点:      

    1.Type type = ClientProxyGenerator.CreateProxyType (typeof (TChannel), Endpoint.Contract, false);

    2.object proxy = Activator.CreateInstance (type, new object [] {Endpoint, this, address ?? Endpoint.Address, via});

先来看看简单一点的第二个方法的解释:

     MSDN中对对该Activator.CreateInstance(type,object[]) 的解释是”使用与指定参数匹配程度最高的构造函数创建指定类型的实例”  

两个参数分别是:需要生成的对象的类;构造函数传入的参数,系统会更具参数的数量、顺序和类型的匹配程度调用相应的构造函数。

看看第一个方法原型是什么样子:

069     public static Type CreateProxyType (Type requestedType, ContractDescription cd, bool duplex)070     {071         ClientProxyKey key = new ClientProxyKey (requestedType, cd, duplex);072         Type res;073         lock (proxy_cache) {074             if (proxy_cache.TryGetValue (key, out res))075                 return res;076         }077078         string modname = "dummy";079         Type crtype =080#if !NET_2_1081             duplex ? typeof (DuplexClientRuntimeChannel) :082#endif083             typeof (ClientRuntimeChannel);084085         // public class __clientproxy_MyContract : (Duplex)ClientRuntimeChannel, [ContractType]086         var types = new List<Type> ();087         types.Add (requestedType);088         if (!cd.ContractType.IsAssignableFrom (requestedType))089             types.Add (cd.ContractType);090         if (cd.CallbackContractType != null && !cd.CallbackContractType.IsAssignableFrom (requestedType))091             types.Add (cd.CallbackContractType);092         CodeClass c = new CodeModule (modname).CreateClass ("__clientproxy_" + cd.Name, crtype, types.ToArray ());093094         //095         // public __clientproxy_MyContract (096         //  ServiceEndpoint arg1, ChannelFactory arg2, EndpointAddress arg3, Uri arg4)097         //  : base (arg1, arg2, arg3, arg4)098         // {099         // }100         //101         Type [] ctorargs = new Type [] {typeof (ServiceEndpoint), typeof (ChannelFactory), typeof (EndpointAddress), typeof (Uri)};102         CodeMethod ctor = c.CreateConstructor (103             MethodAttributes.Public, ctorargs);104         CodeBuilder b = ctor.CodeBuilder;105         MethodBase baseCtor = crtype.GetConstructors (106             BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) [0];107         if (baseCtor == null) throw new Exception ("INTERNAL ERROR: ClientRuntimeChannel.ctor() was not found.");108         b.Call (109             ctor.GetThis (),110             baseCtor,111             new CodeArgumentReference (typeof (ServiceEndpoint), 1, "arg0"),112             new CodeArgumentReference (typeof (ChannelFactory), 2, "arg1"),113             new CodeArgumentReference (typeof (EndpointAddress), 3, "arg2"),114             new CodeArgumentReference (typeof (Uri), 4, "arg3"));115         res = CreateProxyTypeOperations (crtype, c, cd);116117         lock (proxy_cache) {118             proxy_cache [key] = res;119         }120         return res;121     }122 }123

注意内容:

1.内部使用了缓存。

2.使用了动态编译.

3.根据具不同的duplex [in]生成不同的类(代理)类型:

4.看看res = CreateProxyTypeOperations (crtype, c, cd); 这句话发生了什么

126     protected static Type CreateProxyTypeOperations (Type crtype, CodeClass c, ContractDescription cd)127     {128         // member implementation129         BindingFlags bf = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;130         foreach (OperationDescription od in cd.Operations) {131             // FIXME: handle properties and events.132#if !NET_2_1133             if (od.SyncMethod != null)134                 GenerateMethodImpl (c, crtype.GetMethod ("Process", bf), od.Name, od.SyncMethod);135#endif136             if (od.BeginMethod != null)137                 GenerateBeginMethodImpl (c, crtype.GetMethod ("BeginProcess", bf), od.Name, od.BeginMethod);138             if (od.EndMethod != null)139                 GenerateEndMethodImpl (c, crtype.GetMethod ("EndProcess", bf), od.Name, od.EndMethod);140         }141142         Type ret = c.CreateType ();143         return ret;144     }
259     static void GenerateEndMethodImpl (CodeClass c, MethodInfo endProcessMethod, string name, MethodInfo mi)260     {261         CodeMethod m = c.ImplementMethod (mi);262         CodeBuilder b = m.CodeBuilder;263         ParameterInfo [] pinfos = mi.GetParameters ();264265         ParameterInfo p = pinfos [0];266         CodeArgumentReference asyncResultRef = m.GetArg (0);267          268         CodeVariableDeclaration paramsDecl = new CodeVariableDeclaration (typeof (object []), "parameters");269         b.CurrentBlock.Add (paramsDecl);270         CodeVariableReference paramsRef = paramsDecl.Variable;271         b.Assign (paramsRef,272               new CodeNewArray (typeof (object), new CodeLiteral (pinfos.Length - 1)));273         /**274         for (int i = 0; i < pinfos.Length - 2; i++) {275             ParameterInfo par = pinfos [i];276             if (!par.IsOut)277                 b.Assign (278                     new CodeArrayItem (paramsRef, new CodeLiteral (i)),279                     new CodeCast (typeof (object),280                         new CodeArgumentReference (par.ParameterType, par.Position + 1, "arg" + i)));281         }282         */283#if USE_OD_REFERENCE_IN_PROXY284         CodePropertyReference argMethodInfo = GetOperationMethod (m, b, name, "EndMethod");285#else286         CodeMethodCall argMethodInfo = new CodeMethodCall (typeof (MethodBase), "GetCurrentMethod");287#endif288         CodeLiteral argOperName = new CodeLiteral (name);289          290         CodeVariableReference retValue = null;291        if (mi.ReturnType == typeof (void))292             b.Call (m.GetThis (), endProcessMethod, argMethodInfo, argOperName, paramsRef, asyncResultRef);293         else {294             CodeVariableDeclaration retValueDecl = new CodeVariableDeclaration (mi.ReturnType, "retValue");295             b.CurrentBlock.Add (retValueDecl);296             retValue = retValueDecl.Variable;297             b.Assign (retValue,298                 new CodeCast (mi.ReturnType,299                     b.CallFunc (m.GetThis (), endProcessMethod, argMethodInfo, argOperName, paramsRef, asyncResultRef)));300         }301         // FIXME: fill out parameters302         if (retValue != null)303             b.Return (retValue);304     }

CreateProxyTypeOperations  看方法名就大概清楚:创建代理的操作(方法)。

GenerateEndMethodImpl  方法的具体实现。(最后这个方法很重要,不过部分代码我没有看明白,还请看明白的人还望不吝赐教)

 

ClientBase<T> 内部使用了 ChannelFactory<T>

这里使用的Mono代码说明、如果与.Net有区别也是有可能的。

 

至少基本说明了一个问题,代理是根据描述(元数据、接口等来源)动态生成“接口的对象”,

该对象的方法体(方法名、参数、返回值)都与接口一致,方法体对方法传入的值进行了处理。

只要获取到了传递过来的方法、参数、返回值信息等,就可以想怎么样就怎么样、为所欲为了。

 

C#客户端在调用WCF服务的时候,很多情况使用Remoting下RealProxy,RealProxy具有一个抽象的Invoke方法:public abstract IMessage Invoke(IMessage msg);

MSDN对RealProxy.Invoke(IMessage msg)方法的解释是 地址

当调用受 RealProxy 支持的透明代理时,它将调用委托给 Invoke 方法。 Invoke 方法将 msg 参数中的消息转换为 IMethodCallMessage,并将其发送至 RealProxy 的当前实例所表示的远程对象。

 

比如:

public class CalculatorServiceRealProxy : RealProxy    {        public CalculatorServiceRealProxy():base(typeof(ICalculatorService)){}        public override IMessage Invoke(IMessage msg)        {            IMethodReturnMessage methodReturn = null;            IMethodCallMessage methodCall = (IMethodCallMessage)msg;            var client = new ChannelFactory<ICalculatorService>("CalculatorService");            var channel = client.CreateChannel();            try            {                object[] copiedArgs = Array.CreateInstance(typeof(object), methodCall.Args.Length) as object[];                methodCall.Args.CopyTo(copiedArgs, 0);                object returnValue =http://www.mamicode.com/ methodCall.MethodBase.Invoke(channel, copiedArgs);                methodReturn = new ReturnMessage(returnValue,                                                copiedArgs,                                                copiedArgs.Length,                                                methodCall.LogicalCallContext,                                                methodCall);                //TODO:Write log            }            catch (Exception ex)            {                var exception = ex;                if (ex.InnerException != null)                    exception = ex.InnerException;                methodReturn = new ReturnMessage(exception, methodCall);            }            finally            {                var commObj = channel as ICommunicationObject;                if (commObj != null)                {                    try                    {                        commObj.Close();                    }                    catch (CommunicationException)                    {                        commObj.Abort();                    }                    catch (TimeoutException)                    {                        commObj.Abort();                    }                    catch (Exception)                    {                        commObj.Abort();                        //TODO:Logging exception                        throw;                    }                }            }            return methodReturn;        }    }
static void Main(string[] args)        {            ICalculatorService proxy = (ICalculatorService)new CalculatorServiceRealProxy().GetTransparentProxy();            Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 1, 2, proxy.Add(1, 2));            Console.WriteLine("x - y = {2} when x = {0} and y = {1}", 1, 2, proxy.Subtract(1, 2));            Console.WriteLine("x * y = {2} when x = {0} and y = {1}", 1, 2, proxy.Multiply(1, 2));            Console.WriteLine("x / y = {2} when x = {0} and y = {1}", 1, 2, proxy.Divide(1, 2));            Console.ReadKey();        }

ICalculatorService proxy = (ICalculatorService)new CalculatorServiceRealProxy().GetTransparentProxy();  获取到动态“实现”ICalculatorService  的 实体对象,

每次调用的时候都会调用实现的RealProxy的Invoke方法

IMethodCallMessage methodCall = (IMethodCallMessage)msg;  把msg转换成 IMethodCallMessage ;

var channel = client.CreateChannel();这句话创建了代理对象;

object returnValue = http://www.mamicode.com/methodCall.MethodBase.Invoke(channel, copiedArgs); 执行这个代理对象;

methodReturn = new ReturnMessage(returnValue,
                                                copiedArgs,
                                                copiedArgs.Length,
                                                methodCall.LogicalCallContext,
                                                methodCall); 返回值处理

这样的情况下 使用了两次代理。

这样做就具有了AOP的特征,好处也很多

       1.做日志记录;

       2.异常处理;

       3.这个地方做了所有的远程连接操作,对调用这来讲,与通常的非服务调用没有区别,比如这里的“关闭连接”;

      

上边的代码值得优化一下,比如:

1.CalculatorServiceRealProxy 应该改成CalculatorServiceRealProxy <T>,ICalculatorService 通过 T传入,这样灵活性增加不少。

2.endpointConfigurationName 应该也放到CalculatorServiceRealProxy 的构造函数上;或者系统做默认规则处理,比如:配置节点名称就是去掉相应接口名称前的“I”,等等。

 

希望与大家多多交流  QQ:373934650;群Q:227231436

如果有用请大伙儿帮忙推荐一下,谢谢!

2014-7-24 03:00:43(待续)