.NET Remoting与MSMQ不同,它不支持离线可得,另外只适合.NET平台的程序进行通信。它提供了一种允许对象通过应用程序域与另一个对象进行交互的框架。.NET 应用程序都在一个主应用程序域中执行的,在一个应用程序域中的代码不能访问另一个应用程序域的数据,然而在某些情况下,我们需要跨应用程序域,与另外的应用程序域进行通信,这时候就可以采用.NET Remoting技术来实现与另一个程序域中的对象进行交互。
.NET Remoting技术是通过通道来实现两个应用程序之间对象的通信的。
首先,客户端通过Remoting技术的访问通道来获得服务器端对象,再通过代理解析为客户端对象,也称作透明代理,此时获得客户端对象只是服务器对象的一个引用。这既保证了客户端和服务端有关对象的松散耦合,同时优化了通信的性能。在这个过程中,当客户端通过透明代理来调用远程对象的方法时,此时会将调用封装到一个消息对象中,该消息对象包括远程对象信息,被调用的方法名和参数,然后透明代理会将调用委托给真实代理(RealProxy对象)的Invoke方法来生成一个IMethodCallMessage,
接着通过序列化把这个消息对象序列化成数据流发送到通道,通道会把数据流传送到服务器端。当服务器接收到经过格式化的数据之后,首先从中通过反序列化来还原消息对象,之后在服务器端来激活远程对象,并调用对应的方法,而方法的返回结果过程则是按照之前的方法反向重复一遍。具体的实现原理图如下所示:
是运行在服务器端的对象,客户端不能直接调用,由于.NET Remoting传递的对象是以引用的方式,因此所传递的远程对象必须继承MarshalByRefObject类,这个类可以使远程对象在.NET Remoting应用通信中使用,支持对象的跨域边界访问。
在访问服务器端的一个对象实例之前,必须通过一个名为Activation的进程创建它并进行初始化。这种客户端通过通道来创建远程对象的方式称为对象的激活。在.NET Remoting中,远程对象的激活分为两大类:服务器端激活和客户端激活。
为什么称为知名对象激活模式呢?是因为服务应用程序在激活对象实例之前会在一个众所周知的统一资源标示符(URI)上发布这个类型,然后该服务器进行会为此类型配置一个WellKnow对象,并根据指定的端口或地址来发布对象。
.NET Remoting把服务器端激活又分为SingleTon模式和SingleCall模式两种。
与Wellknow模式不同,。NET Remoting在激活每个对象实例的时候,会给每个客户端激活的类型指派一个URI。客户端激活模式一旦获得客户端的请求,将为每一个客户端都建立一个实例引用。SingleCall模式与客户端激活模式的区别有:
首先,对象实例创建的时间不同。客户端激活方式是客户一旦发出调用请求就实例化,而SingleCall则要等到调用对象方法时再创建。
其次,SingleCall模式激活的对象是无状态的,对象声明周期由GC管理,而客户端激活的对象是有状态的,其生命周期可自定义。
第三,两种激活模式在服务器端和客户端实现的方法不一样,尤其是在客户端,SingleCall模式由GetObject()来激活,它调用对象默认的构造函数,而客户端激活模式,则通过CreateInstance()来激活,它可以传递参数,所以可以调用自定义的构造函数来创建实例。
在.NET Remoting中时通过通道来实现两个应用程序域之间对象的通信。.NET Remoting中包括4中通道类型:
//定义接口类ITax //编译生成ITaxTemoting.dll //服务器端和客户端都要添加该类dll的引用 public interface ITax { double GetTax(int salary); }
创建远程对象,该对象必须继承MarshalByRefObject对象。远程对象类Tax继承了基类MarshalByRefObject和接口ITax。
//定义远程对象,必须继承自MarshalByRefObject //编译生成TaxRemoting.dll,服务器端必须添加该dll的引用 public class Tax : MarshalByRefObject, ITax { private int _callOCunt = 0; public Tax() { Console.WriteLine("Remoting object Tax 已激活"); } //根据税 public double GetTax(int salary) { _callOCunt++; return (double)tax; } }
需要添加System.Runtime.Remoting.dll引用
定义通道并监听,注册远程对象。信道用于.NET客户端和服务器端的通信。
在.NET Remoting中,是允许同时创建多个通道的,但是.NET Remoting要求通道的名字必须不同,因为名字是用来标识通道的唯一标识符。
TcpChannel channel = new TcpChannel(8085);//定义通道,还有HttpChannel\IPCChannel等 ChannelServices.RegisterChannel(channel, false);//注册通道 RemotingConfiguration.RegisterWellKnownServiceType(typeof(Tax), "Tax1", WellKnownObjectMode.SingleCall); //在服务端注册一个知名的远程对象Tax,ObjectURI为Tax,知名对象模式为单次调用方式。
注册信道,根据URL获取远程对象代理,使用代理调用服务器端的远程对象。
void Main() { TcpChannel channel = new TcpChannel(); ChannelServices.RegisterChannel(channel, false); ITax obj = (ITax)Activator.GetObject(typeof(ITax), "tcp://localhost:8085/Tax1");//根据URL获取远程对象代理,使用此代理调用服务端的远程对象。 if (obj == null) { Console.WriteLine("Could not locate TCP server"); } Console.WriteLine(obj.GetTax(1).ToString());//调用远程对象的方法获取结果,此时调用服务端的类的默认构造函数实例化。 Console.WriteLine(obj.GetCallCount().ToString()); Console.WriteLine(obj.GetTax(1).ToString()); Console.WriteLine(obj.GetCallCount().ToString()); } //也可以先为客户端注册类型,再实例化对象(不推荐) RemotingConfiguration.RegisterWellKnownClientType(typeof(ITax), "tcp://localhost:8085/Tax1"); Tax obj = new Tax(); obj.GetTax();
可以调用服务端非默认的带参数的构造函数,服务端为每个客户端保存不同的状态。
public class Tax : MarshalByRefObject, ITax { //代码同服务端SAO }
TcpChannel channel = new TcpChannel(8085); ChannelServices.RegisterChannel(channel, false); RemotingConfiguration.ApplicationName = "Tax1"; RemotingConfiguration.RegisterActivatedServiceType(typeof(Tax));
可以使用Soapsuds.exe分离Tax程序集共客户端调用(即不包含具体的实现内容)
E:>soapsuds -url:http://127.0.0.1:8502/TaxTax1?wsdl -oa:ClientProxy.dll
这将为我们在E盘的根目录下生成ClientProxy.dll文件,这个文件将用于客户端成生代理。
void Main() { TcpChannel channel = new TcpChannel(); ChannelServices.RegisterChannel(channel, false); Tax obj = (Tax)Activator.CreateInstance(typeof(ITax), null, new[] {new UrlAttribute ("tcp://localhost:8085/Tax1")});//根据URL获取远程对象代理,使用此代理调用服务端的远程对象。 Console.WriteLine(obj.GetTax(1).ToString()); Console.WriteLine(obj.GetCallCount().ToString()); Console.WriteLine(obj.GetTax(1).ToString()); Console.WriteLine(obj.GetCallCount().ToString()); } //也可以先为客户端注册类型,再实例化对象(不推荐) RemotingConfiguration.RegisterActivatedClientType(typeof(ITax), "tcp://localhost:8085/Tax1");//Activator.GetObject Tax obj = new Tax(); obj.GetTax();
channel.StartListening(null); ChannelServices.UnregisterChannel(channel);
RemotingConfiguration.Configure("RemotingServerHostByConfig.exe.config", false);
服务端的配置文件app.config的内容为
<configuration> <system.runtime.remoting> <application> <service> <!--服务端,如果是客户端改成Client –> <wellknown type="Tax" objectUri="Tax1" mode="SingleCall" /> <!--客户端激活改成activator—> </service> <channels> <channel ref="tcp" port="8085" /> </channels> </application> </configuration>
RemotingConfiguration.Configure("RemotingClientByConfig.exe.config", false);
客户端配置文件的内容为:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.runtime.remoting> <application> <client> <wellknown type="Tax" url="tcp://localhost:8085/Tax1" /> </client> <channels> <channel ref="tcp" port="8085"></channel> </channels> </application> </system.runtime.remoting> </configuration>