C#的远程调用与Java的RMI借鉴学习

Posted 11月 3, 2010 by anzhixin in 


这两天一直在温习C#的远程调用。VS2008带的MSDN中的说明有点乱,示例代码也不全,好不容易终于在微软的MSDN上找到了远程调用的相关资料。不过示例代码里就只是几句有关配置的代码(RemotingConfiguration.Config),搞得我晕晕乎乎。再结合前面的TcpServerChannel类和TcpClientChannel类,我算是搞清了。原来TcpServerChannel类和TcpClientChannel负责通信,RemotingConfiguration负责配置。C#的远程配置有两种,一种是使用配置文件(app-name.exe.config)进行远程调用的配置,再有一种就是通过RemotingConfiguration类相关静太方法在程序里进行配置,通过配置,会将一个本地对象的调用转化为远程对象的调用。假设远程处理类是MyRmtSrv,那么在客户实例后一个MyRmtSrv对象后:

MyRmtSrv obj = new MyRmtSrv();
 obj.Foo();

obj的Foo方法调用将会被定向至一个服务器端的MyRmtSrv实例的调用。

通过对比,如果仔细观察,会发现java的远程调用的客户端里是不会出现直接实例化远程实现对象的,而是通过Naming类的lookup方法将远端执行对象转化为本地的一个接口的调用。比如RMI中定义的调用接口为IMyService,那么在客户端中实例化的时也许是这个样子的:

IMyService ms = (IMyService)Naming.lookup(“rmi://192.168.10.1:9999/hellowold”); ms.someMethod(…);

尽管C#和Java的远程调用在底层上是相似的,都是归结本地对象序列化后的一系列动作,但是Java的RMI设计方式更为优秀,遵循了“按接口进行设计”的开发原则。假设,在一个较大的分布式应用处理中,服务器端和客户端将由不同的小组开发,接照接口设计的原则,可以使得客户端开发小组不必了解服务器端直接的实现类型是什么从而专心按接口开发,另一方面也降低了出现bug的机率,为什么呢?假设我的服务实现类MyServiceImp中有一个公开方法specialMethod,这个方法并不在接口IMyService的定义中,并且约定这个方法只能由MyServiceImp的其他方法或者服务器宿主程序调用,这样如果我在客户端实例化了一个MyServiceImp对象并调用了specialMethod,就可能产生意想不到的结果。相反使用接口就不存在这个问题,因为它们的方法是约定好的,因此更安全。

MSDN中是没有介绍如何按接口实现远程调用的,通过百度再加上我的加工,下面的示例将演示一个按照接口设计的远程调用实例:

远程服务定义(MyRmtSrv.dll):

using System;
 namespace MyRmtSrv
 {
 public interface IRemotingService
 {
 int add(int a, int b);
 }
 }

远程宿主程序(server.exe,需把MyRmtSrv.dll复制到server.exe相同的目录下):

using System;
 using System.Text;
 using System.Runtime.Remoting;
 using System.Runtime.Remoting.Channels;
 using System.Runtime.Remoting.Channels.Tcp;
 using MyRmtSrv; namespace Server
 {
 class MyRmtSrvImp : MarshalByRefObject, IRemotingService
 {
 public int add(int a, int b)
 {
 Console.WriteLine(“Client Request: {0}+{1}=?”, a, b);
 return a + b;
 }
 } class Program
 {
 static void Main(string[] args)
 {
 TcpServerChannel chan = new TcpServerChannel(9999);
 ChannelServices.RegisterChannel(chan, false);
 Console.WriteLine(chan.ChannelName); RemotingConfiguration.RegisterWellKnownServiceType(typeof(MyRmtSrvImp), “add”, WellKnownObjectMode.SingleCall);
 Console.WriteLine(“Press <Enter> to Exit…”);
 Console.ReadLine();
 }
 }
 }

客户端程序(client.exe,需把MyRmtSrv.dll复制到client.exe相同的目录下):

using System;
 using System.Text;
 using System.Runtime.Remoting;
 using System.Runtime.Remoting.Channels;
 using System.Runtime.Remoting.Channels.Tcp;
 using MyRmtSrv; namespace Client
 {
 class Program
 {
 static void Main(string[] args)
 {
 TcpClientChannel chan = new TcpClientChannel();
 ChannelServices.RegisterChannel(chan, false); IRemotingService rs = (IRemotingService)Activator.GetObject(typeof(IRemotingService), “tcp://localhost:9999/add”, null);
 Console.WriteLine(“3+2={0}”, rs.add(3, 2));
 Console.WriteLine(“Press <enter> to exit…”);
 Console.ReadLine();
 }
 }
 }

在客户端中我们使用Activator类的静态方法GetObject获取实现IRemotingService接口的实例对象,而不是直接在客户端中实例化远程对象。至此,我们也可以像Java的RMI中那样按照接口进行设计了。

注意,对于客户端来说,他并不知道(也无需知道)服务器端执行处理的远程实例对象是什么类型,对于客户端来说,它被有效地隐藏了。

当然,如果非要按照MSDN中的方法开发远程调用,我也想到了一条原则:
如果远程实现对象的方法不应该在客户端被客户端程序直接调用,那么应该使用internal、protected和private三个关键字中的任何一个将其隐藏起来,以避免意想不到的结果!