.Net Remoting 实践

 

.Net Remoting 实践

对于C/S架构的软件来说,Client端界面主要负责数据采集和呈现。而Server端则负责业务逻辑实现和数据处理。ClientServer的通信工作是一个联系两端的桥梁。

通常,我们的C/S应用环境都是类似如下图所示的情况:

 

1)一个数据库服务器(DB Server

2)一个应用服务器(APP Server

3)多个客户端(Client。可能是PCNoteBookPDA…

 

ClientAPP Server上,是两个独立运行的程序。在.Net的环境下,存在“App Domain”的概念,也就是每个运行的程序有自己独立的应用程序域,不能直接从A应用程序域访问B应用程序域中的对象。这就是我门今天要说的如何建立这个桥梁的问题。

在处理通信的问题上,我们有SocketMQRemotingWebService等技术可以选择。至于选择哪一种,要具体问题具体分析。在项目中,我门采用了Remoting技术。

查看微软的资料和网上同仁的Blog,可以很容易地明白以下几方面的基础知识:

1.      Remoting的两种通道:TCP ChannelHTTP Channel

2.      远程对象的激活方式:服务端激活(WellKnownSingleton)和客户端激活

还有就是要注意:要远程调用的对象都必须继承自MarshalByRefObject

实现服务端的步骤一般如下:(这些都是从Microsoft .Net Remoting系列专题之一:.Net Remoting基础篇 张逸:晴窗笔记摘录的

1、注册通道

要跨越应用程序域进行通信,必须实现通道。如前所述,Remoting提供了IChannel接口,分别包含TcpChannelHttpChannel两种类型的通道。这两种类型除了性能和序列化数据的格式不同外,实现的方式完全一致,因此下面我们就以TcpChannel为例。

注册TcpChannel,首先要在项目中添加引用“System.Runtime.Remoting”,然后using名字空间:System.Runtime.Remoting.Channel.Tcp。代码如下:
            TcpChannel channel = new TcpChannel(8080);
            ChannelServices.RegisterChannel(channel);

在实例化通道对象时,将端口号作为参数传递。然后再调用静态方法RegisterChannel()来注册该通道对象即可。

2、注册远程对象

注册了通道后,要能激活远程对象,必须在通道中注册该对象。根据激活模式的不同,注册对象的方法也不同。

(1) SingleTon模式

对于WellKnown对象,可以通过静态方法RemotingConfiguration.RegisterWellKnownServiceType()来实现:RemotingConfiguration.RegisterWellKnownServiceType(
                typeof(ServerRemoteObject.ServerObject),
                "ServiceMessage",WellKnownObjectMode.SingleTon);

(2)SingleCall模式

注册对象的方法基本上和SingleTon模式相同,只需要将枚举参数WellKnownObjectMode改为SingleCall就可以了。RemotingConfiguration.RegisterWellKnownServiceType(
                typeof(ServerRemoteObject.ServerObject),
                "ServiceMessage",WellKnownObjectMode.SingleCall);

(3)客户端激活模式

对于客户端激活模式,使用的方法又有不同,但区别不大,看了代码就一目了然。
RemotingConfiguration.ApplicationName = "ServiceMessage";
RemotingConfiguration.RegisterActivatedServiceType(
                typeof(ServerRemoteObject.ServerObject));

为什么要在注册对象方法前设置ApplicationName属性呢?其实这个属性就是该对象的URI。对于WellKnown模式,URI是放在RegisterWellKnownServiceType()方法的参数中,当然也可以拿出来专门对ApplicationName属性赋值。而RegisterActivatedServiceType()方法的重载中,没有ApplicationName的参数,所以必须分开。

3、注销通道

如果要关闭Remoting的服务,则需要注销通道,也可以关闭对通道的监听。在Remoting中当我们注册通道的时候,就自动开启了通道的监听。而如果关闭了对通道的监听,则该通道就无法接受客户端的请求,但通道仍然存在,如果你想再一次注册该通道,会抛出异常。

           //获得当前已注册的通道;
            IChannel[] channels = ChannelServices.RegisteredChannels;

            //关闭指定名为MyTcp的通道;
            foreach (IChannel eachChannel in channels)
            {
                if (eachChannel.ChannelName == "MyTcp")
                {
                    TcpChannel tcpChannel = (TcpChannel)eachChannel;

                    //关闭监听;
                    tcpChannel.StopListening(null);

                    //注销通道;
                    ChannelServices.UnregisterChannel(tcpChannel);
                }
            }
代码中,RegisterdChannel属性获得的是当前已注册的通道。在Remoting中,是允许同时注册多个通道的,这一点会在后面说明。

四、客户端

客户端主要做两件事,一是注册通道。这一点从图一就可以看出,Remoting中服务器端和客户端都必须通过通道来传递消息,以获得远程对象。第二步则是获得该远程对象。

1、注册通道:
TcpChannel channel = new TcpChannel();
ChannelServices.RegisterChannel(channel);

注意在客户端实例化通道时,是调用的默认构造函数,即没有传递端口号。事实上,这个端口号是缺一不可的,只不过它的指定被放在后面作为了Uri的一部分。

2
、获得远程对象。

与服务器端相同,不同的激活模式决定了客户端的实现方式也将不同。不过这个区别仅仅是WellKnown激活模式和客户端激活模式之间的区别,而对于SingleTonSingleCall模式,客户端的实现完全相同。

(1) WellKnown激活模式

要获得服务器端的知名远程对象,可通过Activator进程的GetObject()方法来获得:
ServerRemoteObject.ServerObject serverObj = (ServerRemoteObject.ServerObject)Activator.GetObject(
              typeof(ServerRemoteObject.ServerObject), "tcp://localhost:8080/ServiceMessage");

首先以WellKnown模式激活,客户端获得对象的方法是使用GetObject()。其中参数第一个是远程对象的类型。第二个参数就是服务器端的uri。如果是http通道,自然是用http://localhost:8080/ServiceMessage了。因为我是用本地机,所以这里是localhost,你可以用具体的服务器IP地址来代替它。端口必须和服务器端的端口一致。后面则是服务器定义的远程对象服务名,即ApplicationName属性的内容。

(2)
客户端激活模式

如前所述,WellKnown模式在客户端创建对象时,只能调用默认的构造函数,上面的代码就说明了这一点,因为GetObject()方法不能传递构造函数的参数。而客户端激活模式则可以通过自定义的构造函数来创建远程对象。

客户端激活模式有两种方法:
1)
调用RemotingConfiguration的静态方法RegisterActivatedClientType()。这个方法返回值为Void,它只是将远程对象注册在客户端而已。具体的实例化还需要调用对象类的构造函数。
 RemotingConfiguration.RegisterActivatedClientType(               
                typeof(ServerRemoteObject.ServerObject),
                "tcp://localhost:8080/ServiceMessage");
 ServerRemoteObject.ServerObject serverObj = new ServerRemoteObject.ServerObject();

2) 调用进程ActivatorCreateInstance()方法。这个方法将创建方法参数指定类型的类对象。它与前面的GetObject()不同的是,它要在客户端调用构造函数,而GetObject()只是获得对象,而创建实例是在服务器端完成的。CreateInstance()方法有很多个重载,我着重说一下其中常用的两个。
a
 public static object CreateInstance(Type type, object[] args, object[] activationAttributes);

参数说明:
type
:要创建的对象的类型。
args
:与要调用构造函数的参数数量、顺序和类型匹配的参数数组。如果 args 为空数组或空引用(Visual Basic 中为 Nothing),则调用不带任何参数的构造函数(默认构造函数)。
activationAttributes
:包含一个或多个可以参与激活的属性的数组。

这里的参数args是一个object[]数组类型。它可以传递要创建对象的构造函数中的参数。从这里其实可以得到一个结论:WellKnown激活模式所传递的远程对象类,只能使用默认的构造函数;而Activated模式则可以用户自定义构造函数。activationAttributes参数在这个方法中通常用来传递服务器的url
假设我们的远程对象类ServerObject有个构造函数:
            ServerObject(string pName,string pSex,int pAge)
            {
                name = pName;
                sex = pSex;
                age = pAge;
            }

那么实现的代码是:
            object[] attrs = {new UrlAttribute("tcp://localhost:8080/ServiceMessage")};
            object[] objs = new object[3];
            objs[0] = "wayfarer";
            objs[1] = "male";
            objs[2] = 28;
            ServerRemoteObject.ServerObject = Activator.CreateInstance(
                typeof(ServerRemoteObject.ServerObject),objs,attrs);
可以看到,objs[]数组传递的就是构造函数的参数。

bpublic static ObjectHandle CreateInstance(string assemblyName, string typeName, object[] activationAttribute);

参数说明:
assemblyName
:将在其中查找名为 typeName 的类型的程序集的名称。如果 assemblyName 为空引用(Visual Basic 中为 Nothing),则搜索正在执行的程序集。
typeName
:首选类型的名称。
activationAttributes
:包含一个或多个可以参与激活的属性的数组。

参数说明一目了然。注意这个方法返回值为ObjectHandle类型,因此代码与前不同:
            object[] attrs = {new UrlAttribute("tcp://localhost:8080/EchoMessage")};           
            ObjectHandle handle = Activator.CreateInstance("ServerRemoteObject",
                                   "ServerRemoteObject.ServerObject",attrs);
            ServerRemoteObject.ServerObject obj = (ServerRemoteObject.ServerObject)handle.Unwrap();

这个方法实际上是调用的默认构造函数。ObjectHandle.Unwrap()方法是返回被包装的对象。

说明:要使用UrlAttribute,还需要在命名空间中添加:using System.Runtime.Remoting.Activation;

 

 

 

在我们的实际应用中,使用的是Singleton +客户端激活的方式。首先创建了一个Singleton方式的远程对象ServiceFactory(继承MarshalByRefObject)。ServiceFactory是个工厂,有许多的方法CreateA(),CreateB(),CreateC()…,可以创建其他的远程业务对象。其次,在客户端通过Singleton的方式拿到这个Factory后,调用这些方法,再获取其他的远程业务对象。简单地说,就是我们要刀要枪要炮,不用自己造,先把兵工厂搞到手了,再让工厂造。

在服务端工厂对象的处理上,我们也是先注册通道,然后调用RemotingServices.Marshal方法,就实现了远程对象的注册。

……注册信道

ServiceFactory ServiceObj = new ServiceFactory();

ServiceRef = RemotingServices.Marshal(ServiceObj, serviceName, typeof(ServiceFactory));

 

而在远程对象的销毁上,直接调用RemotingServices.Disconnect方法,阻止ServiceObj接收任何消息。

RemotingServices.Disconnect(ServiceObj);

……注销信道

 

当然,这里面还有一些问题需要注意。例如远程对象的生命周期要设置为无限期,则需要重载如下方法,并返回null。

public override object InitializeLifetimeService()

        {

            return null;

        }

在客户端,要获取远程对象ServiceFacory的实例引用,需要使用Activator.GetObject方法,

IServiceFactory boFactory =

(IServiceFactory)Activator.GetObject( typeof(IServiceFactory), serviceUrl);

这里IServiceFacotry ServiceFacotry的接口。

获得IServiceFacotry类型 boFactory后,我们就可以调用boFactoryCreateXXX方法来造枪造炮了。

 

虽然远程对象能够提供服务,而且客户端也能获取远程对象,并使用该服务了,但是总觉得不够完整:服务什么时候启动?什么时候关闭?如何灵活管理该服务呢?要是能像SQL Server的服务管理器一样就好了。

首先将Remoting服务包装一下,做成Windows服务。这个比较简单,网上也有很多。大致是建立一个Windows 服务项目,在Service1的代码中重载两个方法:

protected override void OnStart(string[] args)

protected override void OnStop()

然后在Service1的属性窗口点击“添加安装程序”会添加一个“ProjectInstaller1”的文件。它包含两个组件,“serviceInstaller1”设置一些服务的基本信息,如名称,说明等,“serviceProcessInstaller1”的Accout属性比较有意思,选择的是运行服务的账户类型,这里选择的是“Local System”。

编译后生成WindowsService1.exe,然后用命令行执行:C:"WINDOWS"Microsoft.NET"Framework"v2.0.50727"InstallUtil.exe WindowsService1.exe

net start ServiceName

就可以安装并启动服务了。

顺便说一下,要卸载服务,使用

net stop ServiceName

C:"WINDOWS"Microsoft.NET"Framework"v2.0.50727"InstallUtil.exe -u WindowsService1.exe

 

   最后,我们还要做一个服务管理器,当然,这是一个很简单的界面。

 

 其中要注意的就是对Windows服务的管理(包括启动,停止等操作),我门用到了ServiceController(要引用System.ServiceProcess名空间)

ServiceController sc = new ServiceController(serviceName);//建立服务对象

                      //服务停止则启动服务

            if ((sc.Status.Equals(ServiceControllerStatus.Stopped)) || (sc.Status.Equals(ServiceControllerStatus.StopPending)))

            {

                sc.Start();

                sc.Refresh();

            }

         //服务运行则停止服务

            if (sc.Status.Equals(ServiceControllerStatus.Running))

            {

                sc.Stop();

                sc.Refresh();

            }

另外,我还遇到了一个小问题,在停止服务的时候,管理界面反应还挺快,但是在启动服务的时候,服务的状态不能立即刷新:(。我也没有办法了,只好做了个Timer来检查Service的状态。

好了,这个环节终于快马加鞭地回顾了一遍,嗯~

posted @ 2008-07-30 17:45  春风沉醉的晚上  阅读(793)  评论(1编辑  收藏  举报