WCF代理是怎麼工做的?用代碼說話

 

1.WCF生成代理的方式html

2.WCF代理原理緩存

 

 

 

第一個問題引用 一篇Robin's博文[WCF生成客戶端對象方式解析] 講述了建立客戶端服務對象的方法app

1.代理構造法async

       a.開啓服務後,添加服務引用ide

       b.知道元數據地址,經過svcutli.exe生成代理類和配置文件函數

       c.從服務契約DLL中導出元數據,而後更具本地的元數據文件生成代理類和配置文件測試

       d.知道元數據地址,本身編寫代碼生成(使用ServiceContractGenerator類等),生成代理類和配置文件優化

2.通道工廠(ChannelFactory<T>)ui

       a.知道終結點地址,綁定協議(ABC中的A和B)this

       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 ();
107
108         return CreateChannel (Endpoint.Address);
109     }

CreateChannel方法的一個重載方法

133     public virtual TChannel CreateChannel (EndpointAddress address, Uri via)
134     {
135
   #if MONOTOUCH
136         throw new InvalidOperationException ("MonoTouch does not support dynamic proxy code generation. Override this method or its caller to return specific client proxy instance");
137
  #else
138         var existing = Endpoint.Address;
139         try {
140
141         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 have
147         // no special magic), we have to use different approach
148         // 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             else
155                 throw;
156         } finally {
157             Endpoint.Address = existing;
158         }
159
  #endif
160     }

翻一下一下注釋:在.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         }
077
078         string modname = "dummy";
079         Type crtype =
080
#if !NET_2_1
081             duplex ? typeof (DuplexClientRuntimeChannel) :
082
#endif
083             typeof (ClientRuntimeChannel);
084
085         // 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 ());
093
094         //
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);
116
117         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 implementation
129         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_1
133             if (od.SyncMethod != null)
134                 GenerateMethodImpl (c, crtype.GetMethod ("Process", bf), od.Name, od.SyncMethod);
135
#endif
136             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         }
141
142         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 ();
264
265         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_PROXY
284         CodePropertyReference argMethodInfo = GetOperationMethod (m, b, name, "EndMethod");
285
#else
286         CodeMethodCall argMethodInfo = new CodeMethodCall (typeof (MethodBase), "GetCurrentMethod");
287
#endif
288         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 parameters
302         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 = 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 = 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(待續)

相關文章
相關標籤/搜索