从实例谈Remoting的激活模式及相关技术[未完]
前言:我在《Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 》中已经谈到了Remoting技术中的三种激活模式,同时在《Microsoft .Net Remoting系列专题之二:Marshal、Disconnect与生命周期以及跟踪服务》中,对激活方式作了一些补充。令我高兴的是,这两篇小文获得了很多朋友的捧场,阅读量也在不断上升。评论者有之,疑惑者有之,提问者有之,鼓励者有之,似乎感觉自己的博客就要靠Remoting来撑起半边天了。惭愧的是,自己在这方面的造诣实在有限,很多朋友的问题还无法给出满意的答复,只有自己不断努力了。
说到Remoting的激活模式,总有些意犹未尽的感觉。最近在看《Advanced .NET Remoting》一书,在其第三章中几乎将Remoting激活模式的有关技术穷尽了,而且讲解得非常清楚。当时更是感叹自己为何没有早日看到此书,否则也不会为了这些相关技术弄得我精疲力尽了。本来已经有了翻译此书的计划,无奈因为种种原因,只好作罢。放弃吧,还是心有不甘。想到或许有许多初学Remoting者仍然会产生我以前的疑惑,因此不揣冒昧,打算将该书所讲的一些内容,再结合自己的一些心得,作为Remtong的系列专题二,写出来,与大家共同分享。
目录:
一、Remoting激活模式概述
二、分布式开发中远程对象实现策略
三、从实例谈服务端激活模式
四、从实例谈客户端激活模式
五、通过SingleTon模式模拟客户端激活模式
很多人无法正确分辨Remoting的几种激活模式之间的区别,这直接影响了他们实施Remoting技术的抉择与判断。这就好比我们来到百货商场,面对琳琅满目、丰富多彩的商品,却因为自己的囊中羞涩,入宝山却空手而归,这种感觉似乎不仅是遗憾吧。不用担心,我相信当你读完本文后,这片不满的阴霾自然会烟消云散,反而会感谢Remoting为我们的分布式开发提供的体贴服务。
一、Remoting激活模式概述
相信我们都已明白,Microsoft .Net Remoting的实现应是在两个不同的应用程序域中互相传递信息。我们可以简单地将这两个应用程序域看作是服务器端和客户端,而要传递的信息即“远程对象”,它是传递在Remoting的通道中。在.Net Framework中,对象通常有两种表现形式,即:值对象与引用对象。Remoting对这两种类型对象各有不同的传递方式。值对象采用序列化方式,或者实现ISerializable接口,或者为其整个类对象加上[Serializable]Attribute。而引用对象则直接派生于MarshalByRefObject。这个引用对象按照分布式处理的原则,通常应部署在服务器端。客户端对其的调用方式就是本文要描述的Remoting激活模式。
MarshalByRefObject作为远程对象而言,总是运行在服务器端,但它可以响应客户端对它的方法调用。那么服务器端和客户端怎样实现这个远程对象数据的传递呢?很多人会认为,既然MarshalByRefObject属于引用对象,或许通道中传递的是指向这个对象内存地址的指针吧。事实并不尽然,如果我们要将这个传递的信息称为指针的话,那么这个指针是很特殊的,它并不实际指向对象在栈里的内存地址,而是指向了服务器的IP地址以及这个对象的唯一标识。在Remoting中,这个类似于指针的对象被称为ObjRef对象。这个ObjRef对象是通过Marshal-By-Reference的方式所获得的,它创建了一个指向远程对象的代理,便于通道进行传输。
所谓“激活”,既是将远程对象以Marshal-By-Reference的方式Marshal为ObjRef代理对象,并以不同的模式进行注册,传送到Remoting通道中。根据注册模式的不同,Remoting将激活方式分为:服务器端激活模式(Server-Activated Objects,简称SAO)和客户端激活模式(Client-Activated Objects,简称CAO)。以下是有关两种激活模式在特性上的区别:
1、服务器端激活模式又称为知名对象(WellKnown Objects)激活模式,因为在激活远程对象时,需要一个统一的URI作为唯一的标识。
2、服务器端激活模式根据激活方式的不同,又分为SingleTon激活模式和SingleCall激活模式。SingleTon模式激活的对象始终只有一个,而不管客户端的多少。(你可以参考一下设计模式的SingleTon模式。)而SingleCall模式则故名思义。它的对象创建是根据调用方法来决定的,每调用一次方法,都会创建该对象;而一旦调用下次方法时,则上次创建的对象会被销毁,并重新创建新对象。
3、服务器端激活是无状态态,它的特性更类似于Web Service。而客户端激活是有状态的,它的特性更像是你在本地实例化的一个对象。
4、服务器端激活模式中,SingleCall激活的对象是无法管理其生命周期的;而SingleTon激活模式以及客户端激活模式则可以管理生命周期。
5、服务器端激活模式对于其MarshalByRefObject对象本身只能使用默认的构造函数,而客户端激活模式则可以使用自定义的构造函数。
写到这里,我们对Remoting的激活模式已经有了粗略的了解,然而以上的描述未免有些空洞。不用担心,我们希望能通过实例来阐述具体的激活模式。
二、分布式开发中远程对象实现策略
在阐述对象的激活方式之前,我想先说说在分布式开发中通常采取的远程对象实现策略。一般而言,分布式开发要求接口与实现分离,这样既可以保证客户端程序不至于“臃肿”,更重要的是可以保证元数据的安全。为了实现这种分离机制,远程对象程序集应该定义接口,并暴露所有客户端需要使用的接口方法。同时将远程对象的接口和实现分离到两个不同的程序集中。例如:
接口程序集:GeneralInterface.dll
using System;
namespace GeneralInterface
{
//远程对象接口;
public interface IRemotingObject
{
void SetAge(int age);
int GetAge();
void Print(string message);
string Name
{
get;
set;
}
}
}
远程对象程序集(应添加接口程序集引用):
using System;
using GeneralInterface; //申明接口程序集命名空间
namespace RemotingObject
{
public class RemoteObject:MarshalByRefObject,IRemotingObject
{
public RemoteObject()
{
Console.WriteLine("Default Constructor is invoked.");
}
public RemoteObject(int age)
{
Console.WriteLine("User-Defined Constructor is invoked.");
userAge = age;
}
private string userName;
private int userAge;
private void Write(string message)
{
Console.WriteLine("Invoking the private method Write():");
Console.WriteLine("Message is {0}",message);
}
#region IRemotingObject 成员
public int GetAge()
{
Console.WriteLine("Invoking the GetAge() method:");
Console.WriteLine("The user age is {0}",userAge);
Console.WriteLine();
return userAge;
}
public void SetAge(int age)
{
Console.WriteLine("Invoking the SetAge() method:");
Console.WriteLine("The user age is set to {0}",age);
userAge = age;
Console.WriteLine("The user age is {0}",userAge);
Console.WriteLine();
}
public string Name
{
get
{
Console.WriteLine("Name Property's Get Accessor.");
Console.WriteLine();
return userName;
}
set
{
Console.WriteLine("Name Property's Set Accessor.");
Console.WriteLine();
userName = value;
}
}
public void Print(string message)
{
Console.WriteLine("Invoking the Public method Print():");
Write(message);
Console.WriteLine();
}
#endregion
}
}
通过这种分离机制,使我们非常方便对远程对象按照分布式处理的方式进行部署。对于服务器端,需要部署这两个程序集;而客户端,则只需要部署接口程序集就可以了。需要注意的是,如果我们要在客户端要调用远程对象的方法,则必须保证该方法已经被接口定义了。
自然这种实现方式是不能用抽象类来代替接口的。因为我们的远程对象能够被通道传递,必须继承MarshalByRefObject,如果其自身又需要继承抽象基类,这与C#只支持单继承相矛盾了。
前面我们说了传值对象和引用对象Marshal的区别。对于传值对象而言,只能通过序列化的方式经由通道传递。因此传值对象的实现也应放在接口程序集中,作为该程序集的一部分,分别部署在服务端和客户端。