- 注册多个通道
在Remoting中,允许同时创建多个通道,即根据不同的端口创建不同的通道。但Remoting要求通道的名字必须不同,因为它要用来作为通道的唯一标识符。虽然IChannel有ChannelName属性,但这个属性是只读的。因此一个标准的创建Remoting 程序的方法无法实现同时注册多个通道的要求。
这个时候,我们必段要用到System.Collection中的IDictionary接口。
注册Tcp通道:
IDictionary tcpProp = new Hashtable();
tcpProp["name"] = "tcp9090";
tcpProp["port"] = 9090;
IChannel channel = new TcpChannel(tcpProp,new BinaryClientFormatterSinkProvider(),new BinaryServerFormatterSinkProvider());
ChannelServices.RegisterChannel(channel);
IDictionary httpProp = new Hashtable();
httpProp["name"] = "http8080";
httpProp["port"] = 8080;
IChannel channel = new HttpChannel(httpProp,new SoapClientFormatterSinkProvider(),new SoapServerFormatterSinkProvider());
ChannelServices.RegisterChannel(channel);
在name属性中,定义不同的通道名称就可以了。
- 远程对象元数据相关性
由于服务器和客户端都要用到远程对象,通常的方式是生成两份完全相同的对象Dll,分别添加引用。不过为了代码的完全性,且降低客户端对远程对象元数据的相关性,我们有必要对这种方式进行改动。即在服务器端实现远程对象,而在客户端则删除这些实现的元数据。
由于激活模式的不同,在客户端创建对象的方法也不同,所以要分离元数据的相关性,也应分为两种情况。
- WellKnown激活模式:
通过接口来实现,在服务器端,提供接口和具体类的实现,而在客户端仅提供接口:
注意:两边生成该对象程序集的名字必须相同,严格地说,是命名空间的名字必须相同。public interface IServerObject { Person GetPersonInfo(String name,String sex,Int32 age); } public class ServerObject:MarshalByRefObject,IServerObject {......}
- 客户端激活模式:
如前所述,对于客户端激活模式,不管是使用静态方法,还是使用CreateInstance()方法,都必须在客户端调用构造函数实例化对象。所以,在客户端我们提供程对象,就不能只提供接口,而没有类的实现。实际上,要做到与远程对象元数据的分离,可以由两种方法供选择:
a、利用WellKnown激活模式模拟客户端激活模式:
方法是利用设计模式中的“抽象工厂”,下面的类图表描述了总体解决方案:
我们在服务器端的远程对象中加上抽象工厂的接口和实现类:
public interface IServerObject { Person GetPersonInfo(String name,String sex Int32 age); } public interface IServerObjFactory { IServerObject CreateInstance(); } public class ServerObject:MarshalByRefObject,IServerObject { public Person <span style="font-family: Arial, Helvetica, sans-serif;">GetPersonInfo</span>(String name,String sex,Int32 age) { Person person = new Person(); person.Name = name; person.Sex = sex; person.Age = age; return person; }<span style="font-family: Arial, Helvetica, sans-serif;"> </span><span style="font-family: Arial, Helvetica, sans-serif;">} public class ServerObjFactory:MarshalByRefObject,IServerObjFactory { public IServerObject CreateInstance() { return new ServerObject(); } }</span>
然后再客户端的远程对象中只提供工厂接口和原来的对象接口:
public interface IServerObject
{
Person GetPersonInfo(String name,String sex,Int32 age);
}
public interface IServerObjFactory
{
IServerObject CreateInstance();
}
我们用WellKnown激活模式注册远程对象,在服务器端:
为什么说这是一种客户端激活模式的模拟呢?从激活的方法来看,我们是使用了SingleCall模式来激活对象,但此时激活的并非我们要传递的远程对象,而是工厂对象。如果客户端要创建远程对象,还应该通过工厂对象的CreateInstance()方法来获得。而这个方法正是客户端调用的。因此它的实现方式就等同于客户端激活模式。
b、利用替代类来取代远程对象的元数据
//传递对象:
RemotingConfiguration.RegisterWellKnownServiceType(typeof(ServerRemoteObject.ServerObjFactory),"ServiceMessage",WellKnownObjectMode.SingleCall);
注意这里注册的不是ServerObject类对象,而是ServerObjFactory类对象。
客户端:
ServerRemoteObject.IServerObjFactory serverFactory = (<span style="font-family: Arial, Helvetica, sans-serif;">ServerRemoteObject.IServerObjFactory</span><span style="font-family: Arial, Helvetica, sans-serif;">)Activator.GetObject(typeof(</span><span style="font-family: Arial, Helvetica, sans-serif;">ServerRemoteObject.IServerObjFactory</span><span style="font-family: Arial, Helvetica, sans-serif;">),"tcp://localhost:8080/ServiceMessage");
ServerRemoteObject.IServerObject serverObj = serverFactory.CreateInstance();</span>
b、利用替代类来取代远程对象的元数据
实际上,我们可以用一个trick,来欺骗Remoting。这里所说的替代类就是这个trick了。既然是提供服务,Remoting传递的远程对象其实现的细节当然是放在服务器端。而要在客户端放对象的副本,不过是因为客户端必须调用构造函数,而采取的无奈之取。既然具体的实现是在服务器端,又为了能在客户端实例化,那么在客户端就实现这些好了。至于实现的细节,就不用管了。
如果远程对象有方法,服务器端则提供方法实现,而客户端就提供了这个方法就OK了,至于里面的实现,你可以是抛出一个异常,或者return一个null值;如果方法返回void,那么里机可以是空。关键是这个客户端类对象要有这个方法。这个方法的实现,其实和方法声明差不多,所以我说是一个trick。方法如果,构造函数也如此。
还是用代码来说明这种“阴谋”,更直观:
服务器端
public class ServerObject:MarshalByRefObject
{
public ServerObject()
{
}
public Person GetPersonInfo(string name,string sex,int age)
{
Person person = new Person();
person.Name = name;
person.Sex = sex;
person.Age = age;
return person;
}
}
客户端
public class ServerObject:MarshalByRefObject
{
public ServerObj()
{
throw new System.NotImplementedException();
}
public Person GetPersonInfo(string name,string sex,int age)
{
throw new System.NotImplementedException();
}
}
比较客户端和服务器端,客户端的方法GetPersonInfo(),没有具体的实现细节,只是抛出一个异常。或者直接写上语句return null,照样OK。我们称客户端的这个类为远程对象的替代类。
- 启动/关闭指定远程对象
Remoting中没有提供类似UnregisterWellKnownServiceType()的方法,也即是说,一旦通过注册了远程对象,如果没有关闭通道的话,该对象一直存在于通道中。只要客户端激活该对象,就会创建对象实例。如果Remoting传递的只有一个远程对象,这不存在问题,关闭通道就可以了。如果传送多个远程对象呢?要关闭指定的远程对象应该怎么做?关闭之后又需要启动又该如何?
我们注意到在Remoting中提供了Marshal()和DIsconnect()方法,答案就在这里。Marshal()方法是将MarshalByRefObject类对象转化为ObjRef类对象,这个对象是存储生成代理以与远程对象通讯所需的所有相关信息。这样就可以将该实例序列化以便在应程序域之间以及通过网络进行传输,客户端就可以调用了。而Disconnect()方法将具体的实例对象从通道中断开。
方法如下:
首先注册通道:
TcpChannel channel = new TcpChannel(8080);
ChannelServices.RegisterChannel(channel);
接着启动服务:
先在服务器端实例化远程对象。
ServerObject obj = new ServerObject();
然后,注册该对象。注意这里不用 RemotingConfiguration.RegisterWellKnownServiceType(),
而是使用RemotingServices.Marshal():
ObjRef objrefWellKnown = RemotingServices.Marshal(obj, "ServiceMessage");
如果要注销对象,则:
RemotingServices.Disconnect(obj);
要注意,这里Disconnect 的类对象必须是前面实例化的对象。正因为此,我们可以根据
需要创建指定的远程对象,而关闭时,则Disconnect 之前实例化的对象。
至 于 客 户端的调用, 和前面WellKnown 模式的方法相同, 仍然是通过
Activator.GetObject()来获得。但从实现代码来看,我们会注意到一个问题,由于服务器端是
显式的实例化了远程对象,因此不管客户端有多少,是否相同,它们调用的都是同一个远程
对象。因此我们将这个方法称为模拟的SingleTon 模式。
客户端激活模式
我们也可以通过 Marshal()和Disconnect()来模拟客户端激活模式。首先我们来回顾“远
程对象元数据相关性”一节,在这一节中,我说到采用设计模式的“抽象工厂”来创建对象
实例,以此用SingleCall 模式来模拟客户端激活模式。在仔细想想前面的模拟的SingleTon
模式。是不是答案就将呼之欲出呢?
在“模拟的SingleTon”模式中,我们是将具体的远程对象实例进行Marshal,以此让客
户端获得该对象的引用信息。那么我们换一种思路,当我们用抽象工厂提供接口,工厂类实
现创建远程对象的方法。然后我们在服务器端创建工厂类实例。再将这个工厂类实例进行
Marshal。而客户端获取对象时,不是获取具体的远程对象,而是获取具体的工厂类对象。
然后再调用CreateInstance()方法来创建具体的远程对象实例。此时,对于多个客户端而言,
调用的是同一个工厂类对象;然而远程对象是在各个客户端自己创建的,因此对于远程对象
而言,则是由客户端激活,创建的是不同对象了。
当我们要启动/关闭指定对象时,只需要用Disconnet()方法来注销工厂类对象就可以了。