通过上一篇的介绍,我想大家对.net remoting的作用也有一个基本的认识了。这一篇,我们主要来讨论两个问题:
1. MarshalByRefObject的介绍
2. .Net Remoting中的两种远程对象激活方式,他们的应用以及区别
先来看看官方的解释吧:
应用程序域是一个操作系统进程中一个或多个应用程序所驻留的分区。同一应用程序域中的对象直接通信。不同应用程序域中的对象的通信方式有两种:一种是跨应用程序域边界传输对象副本,一种是使用代理交换消息。
MarshalByRefObject 是通过使用代理交换消息来跨应用程序域边界进行通信的对象的基类。不是从 MarshalByRefObject 继承的对象根据值隐式封送。当远程应用程序引用根据值封送的对象时,将跨应用程序域边界传递该对象的副本。
MarshalByRefObject 对象在本地应用程序域的边界内可直接访问。远程应用程序域中的应用程序首次访问 MarshalByRefObject 时,会向该远程应用程序传递代理。对该代理后面的调用将封送回驻留在本地应用程序域中的对象。
当跨应用程序域边界使用类型时,类型必须是从 MarshalByRefObject 继承的,而且由于对象的成员在创建它们的应用程序域之外无法使用,所以不得复制对象的状态。
也就是说继承了MarshalByRefObject的类能够以引用的方式传递对象。这里要值得注意的是如果一个类标记为serializable那么这个对象将被按对象副本也就说按值传送。我们来举一个例子说明他们他们的区别:
我们在Common中新加一个Student类,注意这个被标记为Serializable:
[Serializable] public class Student { private string studentName; public string StudentName { get { return studentName; } set { studentName = value; } } public Student(string name) { studentName = name; } }
然后在Person类中新增一个方法,将student类实例作为参数:
public void ProduceStudent(Student student) { Console.WriteLine("产生了一个学生对象:"+student.StudentName); }
现在我们来客户端远程调用这个方法,看看结果:
可以发现对象被成功调用了。如果Student没有标志为可序列化或者使用MarshalByRefObject的话,客户端调用的时候将抛出异常信息。从这里我们也可以看出,MarshalByRefObject远程调用的时候只是像浏览器请求服务器一样,请求了一个页面(方法)。具体的请求的参数信息必须为可以传输的,这样服务端才能接收到参数信息。
另外当谈论到MarshalByRefObject时候,透明代理和真实代理也常常被拿出来做比较。简单的说,透明代理是想达到这样一种目标,用户操作远程对象就像在本地操作对象一样的方便。而真实代理则通常位于透明代理之后,处理透明代理传递过来的消息,将这个消息序列化为可传输的格式和传递该消息给远程服务端。在真实代理中,我们通常可以自定义一些操作来实现对象的拦截。这种方式通常被使用在AOP拦截机制中。
更多“MarshalByRefObject”知识:http://www.cnblogs.com/dudu/archive/2004/03/04/2182.html
更多“代理”知识:http://blog.csdn.net/anghlq/archive/2007/07/17/1695456.aspx
有的人听到这个词可能有点疑惑,到底是个什么意思啊?其实,这个激活方式我们在上一篇中已经有用到了,只不过我们用的是其中一种激活方式而已。上一篇不想给大家太多的包袱,这一篇我们就来好好的看看什么是激活方式,不同的激活方式到底有什么区别?!
首先来说什么叫激活吧。还记得我们在服务端注册通道后的那一步操作么?
//将服务端的某个对象注册为已知类型,方便客户端调用 RemotingConfiguration.RegisterWellKnownServiceType ( typeof(Person), "SayHello", WellKnownObjectMode.Singleton );
这里我们就使用了其中一种激活方式:服务器端激活,又叫做WellKnow方式。剩下的另外一种叫做客户端激活方式。也就是说,服务端将“对象发布”的过程叫做对象的激活。
服务端激活
服务端激活又分为两种方式。Singleton和SingleCall。学过设计模式的对Singleton这个词肯定不陌生,因为在设计模式里面就有一个singleton模式。同样的,在这里的意思就是所有客户端发出的调用请求服务端只会只用同一个实例。而SingleCall则是对每个客户端的请求,服务端都会返回对应的多个实例来响应请求。我们来看个计数的例子就明白了。
我们将Person类改一下,这样每次调用Count方法,按照前面介绍的区别运行的结果应该也不同:
public class Person : MarshalByRefObject { private int counter = 0; public Person() { } public int GetPersonInfo(int clientId) { Console.WriteLine(clientId + "号客户端调用服务端方法,服务器端返回值为:"+DateTime.Now.Second); return DateTime.Now.Second; } public void Count() { counter++; Console.WriteLine("服务端请求数:" + counter); } }
采用Singleton方式记录结果:
采用SingleCall方式记录结果:
可以看到使用singleton方式,服务器每次都是使用的同一个实例来执行客户端请求,而SingleCall方式则每次都会新生成一个实例。
客户端激活
客户端激活在实例的创建上和服务端的SingleCall方式差不多,但又不完全一样。主要体现在一下几个方面:
1.对象实例化时间不同。SingleCall方式只有在调用服务端方法的时候,服务端的对象才会被初始化。而客户端激活只要发出调用请求就会被服务端对象就会被实例化。
2.对象激活和在客户端获得对象引用的方式不一样。
3.客户端激活允许使用带有参数的构造函数来创建实例,而服务端激活只能使用默认的构造函数来创建实例。
现在我们就来看看客户端激活如何使用。
服务端:
static void Main(string[] args) { //注册tcp通道进行消息对象的传输 TcpChannel tcp = new TcpChannel(8085); //注册通道 ChannelServices.RegisterChannel(tcp, true); //将服务端的某个对象注册为已知类型,方便客户端调用 //RemotingConfiguration.RegisterWellKnownServiceType // ( // typeof(Person), // "SayHello", // WellKnownObjectMode.Singleton // ); //因为客户端激活方法中不可以直接赋值应用程序名称,所以我们 //必须单独拿出来赋值,而且客户端激活也没有Singleton和SingleCall //之分,某人都是一个客户端就创建一个实例 RemotingConfiguration.ApplicationName = "SayHello"; RemotingConfiguration.RegisterActivatedServiceType(typeof(Person)); System.Console.WriteLine("服务器正在等待信息,按任意键退出!"); System.Console.ReadLine(); } }
客户端:
class Program { static void Main(string[] args) { TcpChannel tcp = new TcpChannel(); ChannelServices.RegisterChannel(tcp, true); //Person obj1 = (Person)Activator.GetObject( //typeof(Person), //"tcp://localhost:8085/SayHello"); //将访问的URL作为参数传进去,null的部分为构造函数参数,如果使用 //默认构造函数则置为NULL object[] attrs = { new UrlAttribute("tcp://localhost:8085/SayHello") }; Person obj1 = (Person)Activator.CreateInstance(typeof(Person), null, attrs); if (obj1 == null) { System.Console.WriteLine( "连接TCP服务器失败"); } //obj1.Count(); Console.Read(); } }