1. 程式人生 > >C#的遠端呼叫與Java的RMI借鑑學習

C#的遠端呼叫與Java的RMI借鑑學習

這兩天一直在溫習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三個關鍵字中的任何一個將其隱藏起來,以避免意想不到的結果!