一步一步学Remoting之二:激活模式
远程对象的激活模式分服务端激活和客户端激活两种,(也就是对象分服务端激活对象或者说是知名对象和客户端激活对象两种)先看看msdn怎么描述服务端激活的:
服务器激活的对象是其生存期由服务器直接控制的对象。服务器应用程序域只有在客户端在对象上进行方法调用时才创建这些对象,而不会在客户端调用 new 或 Activator.GetObject 时创建这些对象;这节省了仅为创建实例而进行的一次网络往返过程。客户端请求服务器激活的类型实例时,只在客户端应用程序域中创建一个代理。然而,这也意味着当您使用默认实现时,只允许对服务器激活的类型使用默认构造函数。若要发布其实例将使用带参数的特定构造函数创建的类型,可以使用客户端激活或者动态地发布您的特定实例。
服务器激活的对象有两种激活模式(或 WellKnownObjectMode 值):Singleton 和 SingleCall。
Singleton 类型任何时候都不会同时具有多个实例。如果存在实例,所有客户端请求都由该实例提供服务。如果不存在实例,服务器将创建一个实例,而所有后继的客户端请求都将由该实例来提供服务。由于 Singleton 类型具有关联的默认生存期,即使任何时候都不会有一个以上的可用实例,客户端也不会总接收到对可远程处理的类的同一实例的引用。
SingleCall 远程服务器类型总是为每个客户端请求设置一个实例。下一个方法调用将改由其他实例进行服务。从设计角度看,SingleCall 类型提供的功能非常简单。这种机制不提供状态管理,如果您需要状态管理,这将是一个不利之处;如果您不需要,这种机制将非常理想。也许您只关心负载平衡和可伸缩性而不关心状态,那么在这种情况下,这种模式将是您理想的选择,因为对于每个请求都只有一个实例。如果愿意,开发人员可以向 SingleCall 对象提供自己的状态管理,但这种状态数据不会驻留在对象中,因为每次调用新的方法时都将实例化一个新的对象标识。
首先对于服务端激活的两种模式来做一个试验,我们把远程对象做如下的修改:
namespace RemoteObject
{
public class MyObject:MarshalByRefObject
{
private int i=0;
public int Add(int a,int b)
{
return a+b;
}
public int Count()
{
return ++i;
}
}
}
对客户端做以下修改:
Console.WriteLine(app.Count());
Console.ReadLine();
第一次打开客户端的时候显示1,第二次打开的时候显示2,类推……由此验证了Singleton 类型任何时候都不会同时具有多个实例。如果存在实例,所有客户端请求都由该实例提供服务。如果不存在实例,服务器将创建一个实例,而所有后继的客户端请求都将由该实例来提供服务。
把服务器端的config修改一下:
mode="SingleCall" />
(这里注意大小写,大写的C)
再重新运行服务端和客户端,打开多个客户端发现始终显示1。由此验证了SingleCall 类型对于每个客户端请求都会重新创建实例。下一个方法调用将由另一个服务器实例提供服务。
下面再说一下客户端的激活模式,msdn中这么写:
客户端激活的对象是其生存期由调用应用程序域控制的对象,正如对象对于客户端是本地对象时对象的生存期由调用应用程序域控制一样。对于客户端激活,当客户端试图创建服务器对象的实例时发生一个到服务器的往返过程,而客户端代理是使用对象引用 (ObjRef) 创建的,该对象引用是从在服务器上创建远程对象返回时获取的。每当客户端创建客户端激活的类型的实例时,该实例都将只服务于该特定客户端中的特定引用,直到其租约到期并回收其内存为止。如果调用应用程序域创建两个远程类型的新实例,每个客户端引用都将只调用从其中返回引用的服务器应用程序域中的特定实例。
理解一下,可以归纳出
1、客户端激活的时间是在客户端请求的时候,而服务端激活远程对象的时间是在调用对象方法的时候
远程对象修改如下:
namespace RemoteObject
{
public class MyObject:MarshalByRefObject
{
private int i=0;
public MyObject()
{
Console.WriteLine("激活");
}
public int Add(int a,int b)
{
return a+b;
}
public int Count()
{
return ++i;
}
}
}
服务端配置文件:
<system.runtime.remoting>
<application name="RemoteServer">
<service>
<activated type="RemoteObject.MyObject,RemoteObject"/>
</service>
<channels>
<channel ref="tcp" port="9999"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
客户端程序:
namespace RemoteClient
{
class MyClient
{
[STAThread]
static void Main(string[] args)
{
//RemoteObject.MyObject app = (RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
RemoteObject.MyObject app=(RemoteObject.MyObject)Activator.CreateInstance(typeof(RemoteObject.MyObject),null,new object[]{new System.Runtime.Remoting.Activation.UrlAttribute(System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"])});
//Console.WriteLine(app.Count());
Console.ReadLine();
}
}
}
客户端配置文件:
<appSettings>
<add key="ServiceURL" value="tcp://localhost:9999/RemoteServer"/>
</appSettings>
</configuration>
(这里的uri按照服务端配置文件中application元素定义的RemoteServer来写)
运行程序可以看到,在客户端启动的时候服务端就输出了“激活”,我们再转回知名模式进行测试发现只有运行了方法才会在服务端输出“激活”。
2、客户端激活可以调用自定义的构造方法,而不像服务端激活只能使用默认的构造方法
把客户端代码修改如下:
Console.WriteLine(app.Count());
这里看到我们在CreateInstance方法的第二个参数中提供了10作为构造方法的参数。在服务端激活模式我们不能这么做。
远程对象构造方法修改如下:
{
this.i=k;
Console.WriteLine("激活");
}
毫无疑问,我们运行客户端发现输出的是11而不是1了。
3、通过上面的例子,我们运行多个客户端发现出现的永远是11,因此,客户端激活模式一旦获得客户端的请求,将为每一个客户端都建立一个实例引用。
总结:
1、Remoting支持两种远程对象:知名的和客户激活的。知名的远程对象使用了uri作为标识,客户程序使用这个uri来访问那些远程对象,也正式为什么称作知名的原因。对知名的对象来说2种使用模式:SingleCall和Singleton,对于前者每次调用都会新建对象,因此对象是无状态的。对于后者,对象只被创建一次,所有客户共享对象状态,因此对象是有状态的。另外一种客户端激活对象使用类的类型来激活,uri再后台被动态创建,并且返回给客户程序。客户激活对象是有状态的。
2、对于Singleton对象来说需要考虑伸缩性,Singleton对象不能在多个服务器上被部署,如果要跨服务器就不能使用Singleton了。
备注:个人习惯原因,在我的例子中服务端的配置都是用config文件的,客户端的配置都是基本用程序方式的
使用配置文件的优点:无需重新编译就可以配置通道和远程对象,编写的代码量比较少
使用程序定制的优点:可以获得运行期间的信息,对程序调试有利。