通过上面的描述,基本上已经完成了一个最简单的Remoting程序。这是一个标准的创建Remoting程序的方法,但在实际开发过程中,我们遇到的情况也许千奇百怪,如果只掌握一种所谓的“标准”,就妄想可以“一招鲜、吃遍天”,是不可能的。
1、注册多个通道
在Remoting中,允许同时创建多个通道,即根据不同的端口创建不同的通道。但是,Remoting要求通道的名字必须不同,因为它要用来作为通道的唯一标识符。虽然IChannel有ChannelName属性,但这个属性是只读的。因此前面所述的创建通道的方法无法实现同时注册多个通道的要求。
这个时候,我们必须用到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); |
注册Http通道:
IDictionary httpProp = new Hashtable(); httpProp["name"] = "http8080"; httpProp["port"] = 8080; IChannel channel = new HttpChannel(httpProp, new SoapClientFormatterSinkProvider(), new SoapServerFormatterSinkProvider()); ChannelServices.RegisterChannel(channel); |
在name属性中,定义不同的通道名称就可以了。
2、远程对象元数据相关性
由于服务器端和客户端都要用到远程对象,通常的方式是生成两份完全相同的对象Dll,分别添加引用。不过为了代码的安全性,且降低客户端对远程对象元数据的相关性,我们有必要对这种方式进行改动。即在服务器端实现远程对象,而在客户端则删除这些实现的元数据。
由于激活模式的不同,在客户端创建对象的方法也不同,所以要分离元数据的相关性,也应分为两种情况。
(1) WellKnown激活模式:
通过接口来实现。在服务器端,提供接口和具体类的实现,而在客户端仅提供接口:
public interface IServerObject { Person GetPersonInfo(string name,string sex,int age); } public class ServerObject:MarshalByRefObject,IServerObject { ......} |
注意:两边生成该对象程序集的名字必须相同,严格地说,是命名空间的名字必须相同。
(2) 客户端激活模式:
如前所述,对于客户端激活模式,不管是使用静态方法,还是使用CreateInstance()方法,都必须在客户端调用构造函数实例化对象。所以,在客户端我们提供的远程对象,就不能只提供接口,而没有类的实现。实际上,要做到与远程对象元数据的分离,可以由两种方法供选择:
a、利用WellKnown激活模式模拟客户端激活模式:
方法是利用设计模式中的“抽象工厂”,下面的类图表描述了总体解决方案:
我们在服务器端的远程对象中加上抽象工厂的接口和实现类:
public interface IServerObject { Person GetPersonInfo(string name,string sex,int age); } public interface IServerObjFactory { IServerObject CreateInstance(); } public class ServerObject:MarshalByRefObject,IServerObject { 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 ServerObjFactory:MarshalByRefObject,IServerObjFactory { public IServerObject CreateInstance() { return new ServerObject(); } } |
然后再客户端的远程对象中只提供工厂接口和原来的对象接口:
public interface IServerObject { Person GetPersonInfo(string name,string sex,int age); } public interface IServerObjFactory { IServerObject CreateInstance(); } |
我们用WellKnown激活模式注册远程对象,在服务器端:
//传递对象; RemotingConfiguration.RegisterWellKnownServiceType( typeof(ServerRemoteObject.ServerObjFactory), "ServiceMessage",WellKnownObjectMode.SingleCall); |
注意这里注册的不是ServerObject类对象,而是ServerObjFactory类对象。
客户端:
ServerRemoteObject.IServerObjFactory serverFactory = (ServerRemoteObject.IServerObjFactory) Activator.GetObject( typeof(ServerRemoteObject.IServerObjFactory), "tcp://localhost:8080/ServiceMessage"); ServerRemoteObject.IServerObject serverObj = serverFactory.CreateInstance(); |
为什么说这是一种客户端激活模式的模拟呢?从激活的方法来看,我们是使用了SingleCall模式来激活对象,但此时激活的并非我们要传递的远程对象,而是工厂对象。如果客户端要创建远程对象,还应该通过工厂对象的CreateInstance()方法来获得。而这个方法正是在客户端调用的。因此它的实现方式就等同于客户端激活模式。
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; } } |