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();
}
每次我们看代码都只能看到这里,却不能知道ClientBase<T>,ChannelFatctory<T>是怎么起到代理作用的,怎么把方法的调用转换成底层的交互的。
我们来找找源头:(本来是想反编译ServiceModel.dll,反编译以后却不能看到代码方法体,好在Mono下有相应的模块内容,可以在“开源中国社区”看到一些代码)
ClientBase<T> Mono开源代码地址
protected TChannel Channel {
get { return (TChannel) (object) InnerChannel; }
}
看到212行代码,channel返回的属性InnerChannel
204 public IClientChannel InnerChannel {
205 get {
206 if (inner_channel == null)
inner_channel = (IClientChannel) (object) CreateChannel ();
208 return inner_channel;
209 }
}
通过CreateChannel ()创建的对象
protected virtual TChannel CreateChannel ()
{
return ChannelFactory.CreateChannel ();
}
CreateChannel ()方法是用ChannelFactory.CreateChannel (); 创建对象
public ChannelFactory<TChannel> ChannelFactory {
get { return factory; }
internal set {
factory = value;
factory.OwnerClientBase = this;
}
}
跳转到 ChannelFactory<T>类 地址
public TChannel CreateChannel ()
{
EnsureOpened (); return CreateChannel (Endpoint.Address);
}
CreateChannel方法的一个重载方法
public virtual TChannel CreateChannel (EndpointAddress address, Uri via)
{ #if MONOTOUCH
throw new InvalidOperationException ("MonoTouch does not support dynamic proxy code generation. Override this method or its caller to return specific client proxy instance"); #else
var existing = Endpoint.Address;
try { Endpoint.Address = address;
EnsureOpened ();
Endpoint.Validate ();
Type type = ClientProxyGenerator.CreateProxyType (typeof (TChannel), Endpoint.Contract, false);
// in .NET and SL2, it seems that the proxy is RealProxy.
// But since there is no remoting in SL2 (and we have
// no special magic), we have to use different approach
// that should work either.
object proxy = Activator.CreateInstance (type, new object [] {Endpoint, this, address ?? Endpoint.Address, via});
return (TChannel) proxy;
} catch (TargetInvocationException ex) {
if (ex.InnerException != null)
throw ex.InnerException;
else
throw;
} finally {
Endpoint.Address = existing;
} #endif
}
翻一下一下注释:在.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[]) 的解释是”使用与指定参数匹配程度最高的构造函数创建指定类型的实例”
两个参数分别是:需要生成的对象的类;构造函数传入的参数,系统会更具参数的数量、顺序和类型的匹配程度调用相应的构造函数。
看看第一个方法原型是什么样子:
public static Type CreateProxyType (Type requestedType, ContractDescription cd, bool duplex)
{
ClientProxyKey key = new ClientProxyKey (requestedType, cd, duplex);
Type res;
lock (proxy_cache) {
if (proxy_cache.TryGetValue (key, out res))
return res;
} string modname = "dummy";
Type crtype = #if !NET_2_1
duplex ? typeof (DuplexClientRuntimeChannel) : #endif
typeof (ClientRuntimeChannel); // public class __clientproxy_MyContract : (Duplex)ClientRuntimeChannel, [ContractType]
var types = new List<Type> ();
types.Add (requestedType);
if (!cd.ContractType.IsAssignableFrom (requestedType))
types.Add (cd.ContractType);
if (cd.CallbackContractType != null && !cd.CallbackContractType.IsAssignableFrom (requestedType))
types.Add (cd.CallbackContractType);
CodeClass c = new CodeModule (modname).CreateClass ("__clientproxy_" + cd.Name, crtype, types.ToArray ()); //
// public __clientproxy_MyContract (
// ServiceEndpoint arg1, ChannelFactory arg2, EndpointAddress arg3, Uri arg4)
// : base (arg1, arg2, arg3, arg4)
// {
// }
//
Type [] ctorargs = new Type [] {typeof (ServiceEndpoint), typeof (ChannelFactory), typeof (EndpointAddress), typeof (Uri)};
CodeMethod ctor = c.CreateConstructor (
MethodAttributes.Public, ctorargs);
CodeBuilder b = ctor.CodeBuilder;
MethodBase baseCtor = crtype.GetConstructors (
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) [];
if (baseCtor == null) throw new Exception ("INTERNAL ERROR: ClientRuntimeChannel.ctor() was not found.");
b.Call (
ctor.GetThis (),
baseCtor,
new CodeArgumentReference (typeof (ServiceEndpoint), , "arg0"),
new CodeArgumentReference (typeof (ChannelFactory), , "arg1"),
new CodeArgumentReference (typeof (EndpointAddress), , "arg2"),
new CodeArgumentReference (typeof (Uri), , "arg3"));
res = CreateProxyTypeOperations (crtype, c, cd); lock (proxy_cache) {
proxy_cache [key] = res;
}
return res;
}
}
注意内容:
1.内部使用了缓存。
2.使用了动态编译.
3.根据具不同的duplex [in]生成不同的类(代理)类型:
4.看看res = CreateProxyTypeOperations (crtype, c, cd); 这句话发生了什么
protected static Type CreateProxyTypeOperations (Type crtype, CodeClass c, ContractDescription cd)
{
// member implementation
BindingFlags bf = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
foreach (OperationDescription od in cd.Operations) {
// FIXME: handle properties and events. #if !NET_2_1
if (od.SyncMethod != null)
GenerateMethodImpl (c, crtype.GetMethod ("Process", bf), od.Name, od.SyncMethod); #endif
if (od.BeginMethod != null)
GenerateBeginMethodImpl (c, crtype.GetMethod ("BeginProcess", bf), od.Name, od.BeginMethod);
if (od.EndMethod != null)
GenerateEndMethodImpl (c, crtype.GetMethod ("EndProcess", bf), od.Name, od.EndMethod);
} Type ret = c.CreateType ();
return ret;
}
static void GenerateEndMethodImpl (CodeClass c, MethodInfo endProcessMethod, string name, MethodInfo mi)
{
CodeMethod m = c.ImplementMethod (mi);
CodeBuilder b = m.CodeBuilder;
ParameterInfo [] pinfos = mi.GetParameters (); ParameterInfo p = pinfos [];
CodeArgumentReference asyncResultRef = m.GetArg (); CodeVariableDeclaration paramsDecl = new CodeVariableDeclaration (typeof (object []), "parameters");
b.CurrentBlock.Add (paramsDecl);
CodeVariableReference paramsRef = paramsDecl.Variable;
b.Assign (paramsRef,
new CodeNewArray (typeof (object), new CodeLiteral (pinfos.Length - )));
/**
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 */ #if USE_OD_REFERENCE_IN_PROXY
CodePropertyReference argMethodInfo = GetOperationMethod (m, b, name, "EndMethod"); #else
CodeMethodCall argMethodInfo = new CodeMethodCall (typeof (MethodBase), "GetCurrentMethod"); #endif
CodeLiteral argOperName = new CodeLiteral (name); CodeVariableReference retValue = null;
if (mi.ReturnType == typeof (void))
b.Call (m.GetThis (), endProcessMethod, argMethodInfo, argOperName, paramsRef, asyncResultRef);
else {
CodeVariableDeclaration retValueDecl = new CodeVariableDeclaration (mi.ReturnType, "retValue");
b.CurrentBlock.Add (retValueDecl);
retValue = retValueDecl.Variable;
b.Assign (retValue,
new CodeCast (mi.ReturnType,
b.CallFunc (m.GetThis (), endProcessMethod, argMethodInfo, argOperName, paramsRef, asyncResultRef)));
}
// FIXME: fill out parameters
if (retValue != null)
b.Return (retValue);
}
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, );
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}", , , proxy.Add(, ));
Console.WriteLine("x - y = {2} when x = {0} and y = {1}", , , proxy.Subtract(, ));
Console.WriteLine("x * y = {2} when x = {0} and y = {1}", , , proxy.Multiply(, ));
Console.WriteLine("x / y = {2} when x = {0} and y = {1}", , , proxy.Divide(, )); 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(待续)
WCF代理是怎么工作的?用代码说话的更多相关文章
- 从 python 中 axis 参数直觉解释 到 CNN 中 BatchNorm 的工作方式(Keras代码示意)
1. python 中 axis 参数直觉解释 网络上的解释很多,有的还带图带箭头.但在高维下是画不出什么箭头的.这里阐述了 axis 参数最简洁的解释. 假设我们有矩阵a, 它的shape是(4, ...
- jdk1.8 ConcurrentHashMap 的工作原理及代码实现,如何统计所有的元素个数
ConcurrentHashMap 的工作原理及代码实现: 相比于1.7版本,它做了两个改进 1.取消了segment分段设计,直接使用Node数组来保存数据,并且采用Node数组元素作为锁来实现每一 ...
- js 也来 - 【拉勾专场】抛弃简历!让代码说话!
前些日子谢亮兄弟丢了一个链接在群里,我当时看了下,觉得这种装逼题目没什么意思,因为每种语言都有不同的实现方法,你怎么能说你的方法一定比其他语言的好,所以要好的思路 + 好的语言特性运用才能让代码升华. ...
- 通过代码的方式完成WCF服务的寄宿工作
使用纯代码的方式进行服务寄宿 服务寄宿的目的是为了开启一个进程,为WCF服务提供一个运行的环境.通过为服务添加一个或者多个终结点,使之暴露给潜在的服务消费,服务消费者通过匹配的终结点对该服务进行调用, ...
- Struts2 源码分析——Action代理类的工作
章节简言 上一章笔者讲到关于如何加载配置文件里面的package元素节点信息.相信读者到这里心里面对struts2在启动的时候加载相关的信息有了一定的了解和认识.而本章将讲到关于struts2启动成功 ...
- Nginx系列一:正向代理和反向代理、Nginx工作原理、Nginx常用命令和升级、搭建Nginx负载均衡
转自https://www.cnblogs.com/leeSmall/p/9351343.html 仅供个人学习 一.什么是正向代理.什么是反向代理 1. 正向代理,意思是一个位于客户端和原始服务器( ...
- c# winform+wcf代理上网的处理
程序是.net开发的winform工具,分服务器端和客户端,用wcf技术实现数据交互. 客户端是大型公司,内部统一使用代理服务器上网.具体描述为:在IE中设置lan代理服务器才能查询网络数据:登录QQ ...
- 源码分析——Action代理类的工作
Action代理类的新建 通过<Struts2 源码分析——调结者(Dispatcher)之执行action>章节我们知道执行action请求,最后会落到Dispatcher类的serv ...
- Mac SVN 设置代理(Proxy)并 Checkout 代码
1. 设置代理 Finder 菜单里面 -> 前往 -> 前往文件夹 -> /Users/username/.subversion -> 编辑文件 servers 在文件的 [ ...
随机推荐
- 13 Roman to Integer(罗马数字转int Easy)
题目意思:罗马数字转int 思路:字符串从最后一位开始读,IV:+5-1 class Solution { public: int romanToInt(string s) { map<char ...
- Tinkphp定时发布文章的教程
第一步:在文章表中加一个字段,用来保存定时发布的时间 假定我把这个字段设为 push_time 默认为 0 第二步:写一个方法来检查文章列表,把文章列表到时间的文章改为发布状态 //定时发布文章 pu ...
- 让 zend studio 识别 Phalcon语法并且进行语法提示
让 zend studio 识别 Phalcon语法并且进行语法提示 https://github.com/phalcon/phalcon-devtools/tree/master/ide 下载解压 ...
- Phalcon处理404页面的 Ruter 方法
/** * Initializes the router * * @param array $options */ protected function initRouter($options = a ...
- Net Core 项目实战之权限管理系统(0)
0 前言 Net Core 项目实战之权限管理系统(0) 无中生有 0 http://www.cnblogs.com/fonour/p/5848933.html 学习的最好方法就是动手去做,这里以 ...
- 生成MyEclipse6.5&7.5&8.0注册码的java源码
//运行后即可得到注册码 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamRe ...
- Matrix(线段树版)
poj2155:http://poj.org/problem?id=2155 题意;同上一遍随笔. 题解:这里用二维线段树打了一发.第一次学习别人的代码.才学的.这种树套树的程序,确实很费脑子,一不小 ...
- COM实践经验
1. COM不能单独建立,必须有一个Delphi工程的实体,EXE或者DLL都行 2. 自动生成Project1_TLB.pas文件 3. 自动生成Unit2.pas文件,其中最重要的包含内容有: i ...
- COJ 0578 4019二分图判定
4019二分图判定 难度级别: B: 编程语言:不限:运行时间限制:1000ms: 运行空间限制:51200KB: 代码长度限制:2000000B 试题描述 给定一个具有n个顶点(顶点编号为0,1,… ...
- Java---练习(面试题) :字符串截取(2-最终版)
在java中,字符串"abcd"与字符串"ab你好"的长度是一样,都是四个字符. 但对应的字节数不同,一个汉字占两个字节. 定义一个方法,按照指定的字节数来取子 ...