一.前言
半年前,我刚刚接触Remoting,由于甲方单位的需求中明确要求我们使用这种技术,面对这种新技术来说,作为新手的我在做项目过程中遇到不少的困难,也走了不少弯路。当然网上有不少关于Remoting的文章,微软的MSDN上也有不少精彩示例,然而,都不是很全面。最近项目正处在验收阶段,有些空闲,写一点感想,同时,也是对前一阶段工作作一些总结。
二.Remoting基础
1.1 简介
Microsoft® .NET Remoting 提供了一种允许对象通过应用程序域与另一对象进行交互的框架。这种框架提供了多种服务,包括激活和生存期支持,以及负责与远程应用程序进行消息传输的通讯通道。简而言之,我们可以将其看作是一种分布式处理方式。这也正是我们使用Remoting的原因。
在Windows操作系统中,是将应用程序分离为单独的进程。这个进程形成了应用程序代码和数据周围的一道边界。如果不采用进程间通信(RPC)机制,则在一个进程中执行的代码就不能访问另一进程。这是一种操作系统对应用程序的保护机制。然而在某些情况下,我们需要跨过应用程序域,与另外的应用程序域进行通信,即穿越边界。
Remoting技术为客户端提供一个接口,使客户端可以像调用任何本地接口一样调用方法。但是,客户端接口内的方法会触发要对远程对象执行的服务。这对客户端是透明的,因为远程服务对象实现了相同的接口。该模式将启动远程服务调用的业务组件当作"客户端",而将响应远程服务调用的组件当作"服务器"。
通常,如果没有底层框架的支持,管理远程对象的生存期会非常麻烦。.NET Remoting 提供了许多可供选择的生存期模型,这些模型分为两个类别:
•客户端激活对象
•服务器激活对象
客户端激活对象受基于租用的生存期管理器的控制,这种管理器确保了租用期满时对象可被回收。而对于服务器激活对象,开发人员则可以选择“单一调用”模式或“单一元素”模式。
1.2 通道
通道用于在远程对象之间传输消息。当客户端调用某个远程对象上的方法时,与该调用相关的参数以及其他详细信息会通过通道传输到远程对象。调用的任何结果都会以同样的方式返回给客户端。客户端可以选择“服务器”中注册的任一通道,以实现与远程对象之间的通讯,因此开发人员可以自由选择最适合需要的通道。当然,也可以自定义任何现有的通道或创建使用其他通讯协议的新通道。通道选择遵循以下规则:
• 在能够调用远程对象之前,远程处理框架必须至少注册一个通道。通道注册必须在对象注册之前进行。
• 通道按应用程序域注册。一个进程中可以有多个应用程序域。当进程结束时,该进程注册的所有通道将被自动清除。
•多次注册侦听同一端口的通道是非法的。即使通道按应用程序域注册,同一计算机上的不同应用程序域也不能注册侦听同一端口的通道。
•客户端可以使用任何已注册的通道与远程对象通讯。当客户端试图连接至某个远程对象时,远程处理框架会确保该对象连接至正确的通道。客户端负责在尝试与远程对象通讯之前调用 ChannelService 类的 RegisterChannel。
Remoting的通道主要有两种:Tcp和Http。在.Net中,System.Runtime.Remoting.Channel中定义了IChannel接口。IChannel接口包括了TcpChannel通道类型和Http通道类型。它们分别对应Remoting通道的这两种类型。
TcpChannel类型放在名字空间System.Runtime.Remoting.Channel.Tcp中。Tcp通道提供了基于Socket的传输工具,使用Tcp协议来跨越Remoting边界传输序列化的消息流。TcpChannel类型默认使用二进制格式序列化消息对象,因此它具有更高的传输性能。HttpChannel类型放在名字空间System.Runtime.Remoting.Channel.Http中。它提供了一种使用Http协议,使其能在Internet上穿越防火墙传输序列化消息流。默认情况下,HttpChannel类型使用Soap格式序列化消息对象,因此它具有更好的互操作性。通常在局域网内,我们更多地使用TcpChannel;如果要穿越防火墙,则使用HttpChannel。
1.3 激活
在 .NET Remoting 中实现客户端激活对象提供了两种实现方式。客户端激活对象 (CAO) 和服务器激活对象 (SAO) 之间的主要区别在于,是什么控制着远程对象的生存期。在使用 CAO 的情况下,客户端控制着生存期;而在使用 SAO 的情况下,服务器控制着生存期。
远程处理框架支持远程对象的“服务器激活”和“客户端激活”。不需要远程对象在方法调用之间维护任何状态时,一般使用服务器激活。服务器激活也适用于多个客户端调用方法位于同一对象实例上、且对象在函数调用之间维持状态的情况。另一方面,客户端激活对象从客户端实例化,并且客户端通过使用基于租用的专用系统来管理远程对象的生存期。
在框架中注册远程对象时,需要以下四项信息:
•包含类的程序集名称。
•远程对象的类型名称。
•客户端定位对象时将使用的对象 URI。
•服务器激活所需的对象模式。该模式可以是 SingleCall,也可以是 Singleton。
在Remoting中,远程对象的激活分为两大类:服务器端激活和客户端激活。
1.3.1 服务器端激活
服务器端激活又叫做WellKnow方式,很多又翻译为知名对象。为什么称为知名对象激活模式呢?是因为服务器应用程序在激活对象实例之前会在一个众所周知的统一资源标识符(URI)上来发布这个类型。然后该服务器进程会为此类型配置一个WellKnown对象,并根据指定的端口或地址来发布对象。.Net Remoting把服务器端激活又分为SingleTon模式和SingleCall模式两种。
•SingleTon模式:
此为有状态模式。如果设置为SingleTon激活方式,则Remoting将为所有客户端建立同一个对象实例。当对象处于活动状态时,SingleTon实例会处理所有后来的客户端访问请求,而不管它们是同一个客户端,还是其他客户端。SingleTon实例将在方法调用中一直维持其状态。举例来说,如果一个远程对象有一个累加方法(i=0;++i),被多个客户端(例如两个)调用。如果设置为SingleTon方式,则第一个客户获得值为1,第二个客户获得值为2,因为他们获得的对象实例是相同的。如果熟悉Asp.Net的状态管理,我们可以认为它是一种Application状态。
•SingleCall模式:
SingleCall是一种无状态模式。一旦设置为SingleCall模式,则当客户端调用远程对象的方法时,Remoting会为每一个客户端建立一个远程对象实例,至于对象实例的销毁则是由GC自动管理的。同上一个例子而言,则访问远程对象的两个客户获得的都是1。我们仍然可以借鉴Asp.Net的状态管理,认为它是一种Session状态。
1.3.2 客户端激活
与WellKnown模式不同,Remoting在激活每个对象实例的时候,会给每个客户端激活的类型指派一个URI。客户端激活模式一旦获得客户端的请求,将为每一个客户端都建立一个实例引用。
SingleCall模式和客户端激活模式是有区别的:首先,对象实例创建的时间不一样。客户端激活方式是客户一旦发出调用的请求,就实例化;而SingleCall则是要等到调用对象方法时再创建。其次,SingleCall模式激活的对象是无状态的,对象生命期的管理是由GC管理的,而客户端激活的对象则有状态,其生命周期可自定义。其三,两种激活模式在服务器端和客户端实现的方法不一样。尤其是在客户端,SingleCall模式是由GetObject()来激活,它调用对象默认的构造函数。而客户端激活模式,则通过CreateInstance()(或者new)来激活,它可以传递参数,所以可以调用自定义的构造函数来创建实例。
三.远程对象的定义
2.1 服务端激活模式
客户端在获取服务器端对象时,并不是获得实际的服务端对象,而是获得它的引用。因此在Remoting中,对于远程对象有一些必须的定义规范要遵循。
• 一般模式
打开visual studio.NET开发平台,新建一个工程命名为RecordingManager (这个名字可以随意取,默认情况下此类的名称空间与工程名相同), 选择新建Class library, 写入如下代码,将该远程对象以类库的方式编译成Dll。这个Dll将分别放在服务器端和客户端,以添加引用。在Remoting中能够传递的远程对象可以是各种类型,包括复杂的DataSet对象,只要它能够被序列化。
Manager 类有一个名为 GetRecordings 的方法,它从数据库中检索一列记录,然后在 DataSet 中返回结果。注意,在确定要通过远程连接传输的最佳数据类型时,会涉及到一系列考虑因素。该示例使用 DataSet,因为它的示例代码很简短,并且显示了复杂数据类型是如何传送的。
RecordingManager.cs :
using System;
using System.Data;
using System.Data.OracleClient;
using System.Runtime.Remoting;
namespace RecordingManager
{
[Serializable]
public class Manager : MarshalByRefObject
{
public Manager()
{
System.Console.WriteLine("New Reference normally server activated !");
}
public DataSet GetRecordings()
{
DataSet ds = new DataSet();
try
{
String selectCmd = "select * from ME";
OracleConnection myConnection = new OracleConnection(
"Data Source = DB00; User Id = jakey; Password = jakey");
OracleDataAdapter myAdapter = new
OracleDataAdapter(selectCmd, myConnection);
myAdapter.Fill(ds, "ME");
}
catch(OracleException ex)
{
Console.WriteLine("Oracle error!" + ex.Message);
}
catch(RemotingException ex)
{
Console.WriteLine("Remoting error!" + ex.Message);
}
catch(Exception ex)
{
Console.WriteLine("Unknown error!" + ex.Message);
}
return ds;
}
}
}
必须以远程方式访问该类。首先,Manager 类必须从名为 MarshallByRefObject 的远程处理基础结构中的一个类继承而来。MarshalByRefObject 是那些通过使用代理交换消息来跨越应用程序域边界进行通信的对象的基类。不是从 MarshalByRefObject 继承的对象会以隐式方式按值封送。当远程应用程序引用一个按值封送的对象时,将跨越远程处理边界传递该对象的副本。因为您希望使用代理方法而不是副本方法进行通信,因此需要继承 MarshallByRefObject。
•使用接口
其次,还可以从该类提取一个接口。接口对于减少客户端和服务器之间的依赖性是必不可少的,另外,也可以更好地部署应用程序。打开visual studio.NET开发平台,新建一个工程命名为IRecordingManager (这个名字可以随意取,默认情况下此类的名称空间与工程名相同), 选择新建Class library, 写入如下代码,将该远程对象以类库的方式编译成Dll。再分别在客户端和服务端添加引用,具体如前。
IRecordingManager.cs
using System;
using System.Data;
public interface IRecordingManager
{
DataSet GetRecordings();
}
这时,就可以在服务端添加一个类,继承接口去实现方法,这样客户端只需调用接口来满足需要,从而隐藏了业务逻辑实现的细节,增加了代码安全性,而且减少了客户端和服务器间的依赖性。在服务器端新建类ServerRecordingManager。代码如下:
ServerRecordingManager.cs
using System;
using System.Data;
using System.Data.OracleClient;
using System.Runtime.Remoting;
namespace ServerRecordingManager
{
[Serializable]
public class ServerImplementManager : MarshalByRefObject , IRecordingManager
{
public ServerImplementManager ()
{
Console.WriteLine("New Reference added through a interface!");
}
public DataSet GetRecordings()
{
DataSet ds = new DataSet();
try
{
String selectCmd = "select * from ME";
OracleConnection myConnection = new OracleConnection(
"Data Source = DB00; User Id = jakey; Password = jakey");
OracleDataAdapter myAdapter = new
OracleDataAdapter(selectCmd, myConnection);
myAdapter.Fill(ds, "ME");
}
catch(OracleException ex)
{
Console.WriteLine("Oracle error!" + ex.Message);
}
catch(RemotingException ex)
{
Console.WriteLine("Remoting error!" + ex.Message);
}
catch(Exception ex)
{
Console.WriteLine("Unknown error!" + ex.Message);
}
return ds;
}
}
2.2 客户端激活模式
• 一般模式
与服务器端激活模式类似,新建Class library, 写入如下代码,将该远程对象以类库的方式编译成Dll。这个Dll将分别放在服务器端和客户端,以添加引用。RecordingManager 类有一个名为 GetRecordings 的方法,它从数据库中检索一列记录,然后在 DataSet 中返回结果。
RecordingManager.cs :
using System;
using System.Data;
using System.Data.OracleClient;
using System.Runtime.Remoting;
namespace RecordingManager
{
[Serializable]
public class Manager : MarshalByRefObject
{
public Manager()
{
System.Console.WriteLine("New Reference normally client activated !");
}
public DataSet GetRecordings()
{
DataSet ds = new DataSet();
try
{
String selectCmd = "select * from ME";
OracleConnection myConnection = new OracleConnection(
"Data Source = DB00; User Id = jakey; Password = jakey");
OracleDataAdapter myAdapter = new
OracleDataAdapter(selectCmd, myConnection);
myAdapter.Fill(ds, "ME");
}
catch(OracleException ex)
{
Console.WriteLine("Oracle error!" + ex.Message);
}
catch(RemotingException ex)
{
Console.WriteLine("Remoting error!" + ex.Message);
}
catch(Exception ex)
{
Console.WriteLine("Unknown error!" + ex.Message);
}
return ds;
}
}
}
•使用接口
使用一般模式下的客户端激活有一个较大的缺点。这就是,您不能使用 SAO 模式中所描述的共享接口方式。这意味着,必须将编译好的对象传递给客户端。注意:传送经过编译的服务器对象违反了分布式对象的一般原则。另外,由于部署和版本控制问题,也不应该这样做。
为了解决一部分这样的问题,下面的实现描述了“混合法”如何使用 SAO 创建对象。这种方式使客户端能够控制对象的生存期,而不必将服务器代码传送到客户端。
混合法的结构图
与服务器端激活模式类似,新建Class library, 建立两个类:IRecordingsManager和 IRecordingsFactory, 这两个接口位于客户端和服务器所共享的程序集中。IRecordingsFactory 有一个 Create 方法,它可以返回一个对象来实现 IRecordingsManager 接口。
这是 AbstractFactory [Gamma95] 模式的一个例子。因为客户端只依靠接口,所以无需传送服务器代码。当客户端需要 IRecordingsManager 对象时,它调用 IRecordingsFactory 实例的 Create 方法。
这样,客户端就可以控制 IRecordingsManager 对象的生存期,而无需实现该对象。写入如下代码,将该远程对象以类库的方式编译成Dll。这个Dll将分别放在服务器端和客户端,以添加引用。共享程序集中的两个接口是:
IRecordingsManager.cs, 以下示例显示了 IRecordingsManager 接口:
using System;
using System.Data;
public interface IRecordingsManager
{
DataSet GetRecordings();
}
IRecordingsFactory.cs, 以下示例显示了 IRecordingsFactory 接口:
using System;
public interface IRecordingsFactory
{
IRecordingsManager Create();
}
四.服务器端与客户端代码
根据激活模式的不同,通道类型的不同服务器端的实现方式也有所不同。大体上说,服务器端应分为三步:
3.1 注册通道
要跨越应用程序域进行通信,必须实现通道。如前所述,Remoting提供了IChannel接口,分别包含TcpChannel和HttpChannel两种类型的通道。这两种类型除了性能和序列化数据的格式不同外,实现的方式完全一致,因此下面我们就以TcpChannel为例。
注册TcpChannel,首先要在项目中添加引用“System.Runtime.Remoting”,然后using名字空间:System.Runtime.Remoting.Channel.Tcp。代码如下:
1) 服务端:
TcpChannel channel = new TcpChannel(8888);
ChannelServices.RegisterChannel(channel);
2) 客户端:
TcpChannel channel = new TcpChannel();
ChannelServices.RegisterChannel(channel);
在实例化通道对象时,将端口号作为参数传递。然后再调用静态方法RegisterChannel()来注册该通道对象即可。
3.2注册远程对象
注册了通道后,要能激活远程对象,必须在通道中注册该对象。根据激活模式的不同,注册对象的方法也不同。
3.2.1 一般模式下激活远程对象
3.2.1 .1 一般模式服务端激活模式
•代码法(一般模式):
a) Server:
对于WellKnown对象的注册,server端可以通过静态方法RemotingConfiguration. RegisterWellKnownServiceType() 来实现。注册SingleCall对象的方法基本上和SingleTon模式相同,只需要将枚举参数WellKnownObjectMode改为SingleCall就可以了。
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(RecordingManager.Manager),
"myServer",WellKnownObjectMode.SingleTon); // 若是SingleCall,则替换即可。
b) Client:
客户端程序调用远程处理框架函数Activator.GetObject(),以便在服务器上检索 Manager 对象的代理。该方法指定对象所在的 URL 以及应该返回的类型。
RecordingManager.Manager myobj = (RecordingManager.Manager)Activator.GetObject
(typeof(RecordingManager.Manager) // 第一个参数是远程对象类型。
,"tcp:// localhost:8888/myServer"); // 第二个参数是远程对象的URI。
•配置文件法(一般模式):
服务器端配置文件: <configuration> <system.runtime.remoting> <application> <service> <wellknown mode="SingleCall" type="RecordingManager.Manager, RecordingManager" objectUri="myServer" /> </service> <channels> <channel ref="tcp" port="8888"/> </channels> </application> </system.runtime.remoting> </configuration> |
把该配置文件放到服务器程序的应用程序文件夹中,命名为 Server.exe.config 。那么前面的服务器端程序直接用这条语句即可:
try
{
RemotingConfiguration.Configure("Server.exe.config");
}
catch (Exception ex)
{
Console.WriteLine("Read server configuration file error:" + ex.Message);
}
客户端配置文件: <configuration> <system.runtime.remoting> <application> <client> <wellknown type="RecordingManager.Manager, RecordingManager" url="tcp:// localhost:8888/myServer" /> </client> </application> </system.runtime.remoting> </configuration> |
把该配置文件放到服务器程序的应用程序文件夹中,命名为 Client.exe.config 。 客户端调用也是使用 RemotingConfiguration.Configure() 方法来调用存储在客户端的配置文件。
try
{
const String filename = "Client.exe.config";
RemotingConfiguration.Configure(filename);
}
catch (Exception ex)
{
MessageBox.Show("Read client configuration file error:" + ex.Message);
}
通过调用new在服务器上创建一个远程对象。该对象看起来与代码中的任何其他对象一样。
RecordingManager.Manager myobj = new RecordingManager.Manager();
3.2.1 .2 一般模式客户端激活模式
•代码法(一般模式):
a) Server:
对于客户端激活模式,使用的方法又有不同,但区别不大,看了代码就一目了然。以下代码将服务器配置为允许使用new运算符创建客户端激活对象。该代码利用应用程序名以及要创建的对象的类型来配置服务器,而不是实际地注册一个实例。远程对象的 URL 是 tcp://localhost:8888/myServer。SAO 由本地主机上的框架在后台自动创建。该 SAO 负责接受来自客户端的请求,并在客户端请求对象时创建这些对象。
RemotingConfiguration.ApplicationName = " myServer ";
RemotingConfiguration.RegisterActivatedServiceType(
typeof(RecordingManager.Manager));
为什么要在注册对象方法前设置ApplicationName属性呢?其实这个属性就是该对象的URI。对于WellKnown模式,URI是放在RegisterWellKnownServiceType()方法的参数中,当然也可以拿出来专门对ApplicationName属性赋值。而RegisterActivatedServiceType()方法的重载中,没有ApplicationName的参数,所以必须分开。
b) Client:
为了能够使用new运算符,并且使远程处理框架创建一个远程对象(与本地对象相反),必须首先将远程对象的类型与服务器设置 ApplicationName属性时所指定的 URL 相关联。该示例将 ApplicationName 定义为 mYServer,并且使用本地主机上的端口 8888。
TcpChannel channel = new TcpChannel();
ChannelServices.RegisterChannel(channel);
RemotingConfiguration.RegisterActivatedClientType(
typeof(RecordingManager.Manager),
"tcp://localhost:8888/myServer");
我们还可以调用进程Activator的CreateInstance()方法。这个方法将创建方法参数指定类型的类对象。它与前面的GetObject()不同的是,它要在客户端调用构造函数,而GetObject()只是获得对象,而创建实例是在服务器端完成的,这种情况下,我们就不用传统方法注册通道了,而用下面的办法。
object[] attrs = {new UrlAttribute("tcp:// localhost:8888/myServer")};
ObjectHandle handle = Activator.CreateInstance("RecordingManager",
" RecordingManager.Manager", attrs);
RecordingManager.Manager myobj = (RecordingManager.Manager)handle.Unwrap();
• 配置文件法(一般模式):
客户端激活模式与服务器激活模式的配置文件的不同,就是把wellknown改为activated,同时删除mode属性。
服务器端配置文件: <configuration> <system.runtime.remoting> <application> <service> <activated type=" RecordingManager.Manager, RecordingManager" objectUri="myServer" /> </service> <channels> <channel ref="tcp" port="8888"/> </channels> </application> </system.runtime.remoting> </configuration> |
把该配置文件放到服务器程序的应用程序文件夹中,命名为 ServerRemoting.exe.config 。那么前面的服务器端程序直接用这条语句即可:
try
{
RemotingConfiguration.Configure("ServerRemoting.exe.config");
}
catch (Exception ex)
{
Console.WriteLine("Read server configuration file error:" + ex.Message);
}
客户端配置文件: <configuration> <system.runtime.remoting> <application> <client url="tcp:// localhost:8888" > <activated type=" RecordingManager.Manager, RecordingManager"/> </client> </application> </system.runtime.remoting> </configuration> |
把该配置文件放到服务器程序的应用程序文件夹中,命名为 ClientRemoting.exe.config 。 客户端调用也是使用 RemotingConfiguration.Configure() 方法来调用存储在客户端的配置文件。
try
{
const String filename = "ClientRemoting.exe.config";
RemotingConfiguration.Configure(filename);
}
catch (Exception ex)
{
MessageBox.Show("Read client configuration file error:" + ex.Message);
}
通过调用new在服务器上创建一个远程对象。该对象看起来与代码中的任何其他对象一样。
RecordingManager.Manager myobj = new RecordingManager.Manager();
3.2.2 使用接口激活远程对象
3.2.2 .1 使用接口服务端激活模式
•代码法(使用接口):
a) Server:
对于WellKnown对象的注册,server端可以通过静态方法RemotingConfiguration. RegisterWellKnownServiceType() 来实现。注册SingleCall对象的方法基本上和SingleTon模式相同,只需要将枚举参数WellKnownObjectMode改为SingleCall就可以了。
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(ServerImplementManager), // 注意,这里是在服务端继承接口的类名。
"myServer",WellKnownObjectMode.SingleTon); // 若是SingleCall,则替换即可。
b) Client:
客户端程序调用远程处理框架函数Activator.GetObject(),以便在服务器上检索 Manager 对象的代理。该方法指定对象所在的 URL 以及应该返回的类型。
RecordingManager.IServerObject myobj =
(RecordingManager.IServerObject) Activator.GetObject(
typeof(RecordingManager.IServerObject) // 只有一个参数是远程接口名。
,"tcp:// localhost:8888/myServer"); // 第二个参数是远程对象的URI。
• 配置文件法(使用接口):
在远程对象定义为接口的前提下,再使用配置文件来完成remoting技术,似乎有些困难,在微软的MSDN上也没有提供此问题的解决办法。然而如果具体项目确实需要这样做的话,比如需要将客户端的连接远端信息通过读取配置文件来完成,这样可以增加程序的灵活性。
然而这也难不到,网上的大牛们,在网上有这样一个类可以帮助解决这个问题,不过及时这样也不能让服务器端也通过读取配置文件来注册通道和远程对象,服务器端只能hard code了,只有客户端则可以通过读取配置文件来实现所有功能,不过这样已经足够了。
先看一下这个类 RemotingHelper.cs:
using System;
using System.Collections;
using System.Runtime.Remoting;
class RemotingHelper
{
private static bool _isInit;
private static IDictionary _wellKnownTypes;
public static Object GetObject(Type type)
{
if (! _isInit) InitTypeCache();
WellKnownClientTypeEntry entr =
(WellKnownClientTypeEntry) _wellKnownTypes[type];
if (entr == null)
{
throw new RemotingException("Type not found!");
}
return Activator.GetObject(entr.ObjectType,entr.ObjectUrl);
}
public static void InitTypeCache()
{
_isInit = true;
_wellKnownTypes= new Hashtable();
foreach (WellKnownClientTypeEntry entr in
RemotingConfiguration.GetRegisteredWellKnownClientTypes())
{
if (entr.ObjectType == null)
{
throw new RemotingException("A configured type could not " +
"be found. Please check spelling");
}
_wellKnownTypes.Add (entr.ObjectType,entr);
}
}
}
客户端配置文件: <configuration> <system.runtime.remoting> <application> <client> <wellknown type="RecordingManager.IServerObject, RecordingManager" url="tcp:// localhost:8888/myServer" /> </client> </application> </system.runtime.remoting> </configuration> |
把该配置文件放到服务器程序的应用程序文件夹中,命名为 Client.exe.config 。 客户端调用也是使用 RemotingConfiguration.Configure() 方法来调用存储在客户端的配置文件。
try
{
const String filename = "Client.exe.config";
RemotingConfiguration.Configure(filename);
}
catch (Exception ex)
{
MessageBox.Show("Read client configuration file error:" + ex.Message);
}
不能再通过调用new在服务器上直接创建一个远程对象。这时用RemotingHelper这个类来帮忙。
RecordingManager.IServerObject myobj =
(RecordingManager.IServerObject) RemotingHelper.GetObject
(typeof(RecordingManager.IServerObject));
3.2.2 .2 使用接口客户端激活模式
对于接口(IRecordingsManager和 IRecordingsFactory)的服务器实现(RecordingsFactory 和 RecordingsManager)非常简单,在服务器端的TcpServer工程添加两个类,分别继承和实现接口。代码如下:
RecordingsFactory.cs
该类扩展了 MarshalByRefObject,并实现了 IRecordingsFactory 接口:
using System;
public class RecordingsFactory : MarshalByRefObject, IRecordingsFactory
{
public IRecordingsManager Create()
{
return new RecordingsManager();
}
}
RecordingsFactory 对象是服务器激活对象。该实现只是对 RecordingsManager 类型调用new。该 RecordingsManager 对象是在服务器上创建的,并且,不是作为 RecordingsManager 对象、而是作为 IRecordingsManager 接口返回的。利用这种机制,客户端就可以依赖于接口而不是实现。
RecordingsManager.cs
RecordingsManager 类所需要的唯一更改是,它现在实现的是 IRecordingsManager 接口。
using System;
using System.Reflection;
using System.Data;
using System.Data.SqlClient;
public class RecordingsManager : MarshalByRefObject, IRecordingsManager
{
public RecordingsManager()
{
System.Console.WriteLine("New Reference added through interface!");
}
public DataSet GetRecordings()
{
DataSet ds = new DataSet();
try
{
String selectCmd = "select * from ME";
OracleConnection myConnection = new OracleConnection(
"Data Source = DB00; User Id = jakey; Password = jakey");
OracleDataAdapter myAdapter = new
OracleDataAdapter(selectCmd, myConnection);
myAdapter.Fill(ds, "ME");
}
catch(OracleException ex)
{
Console.WriteLine("Oracle error!" + ex.Message);
}
catch(RemotingException ex)
{
Console.WriteLine("Remoting error!" + ex.Message);
}
catch(Exception ex)
{
Console.WriteLine("Unknown error!" + ex.Message);
}
return ds;
}
}
a) Server:
混合法中的服务器初始化代码用于为服务器激活的 RecordingsFactory 对象配置远程处理框架。激活方式与所使用的通道和协议无关,因此与以前一样(端口8888上的TCP 协议)。
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(RecordingsFactory),
" RecordingsFactory",
WellKnownObjectMode.SingleTon); // 若是SingleCall,则替换即可。
b) Client:
客户端代码显示了这种方式的混合性质。首先使用 Activator.GetObject 方法从服务器检索 IRecordingsFactory 对象。然后,使用这个服务器激活对象来调用 Create 方法,以便实例化一个 IRecordingsManager 对象。这个新实例化的对象是在服务器上创建的,但它是一个远程对象。
Recording.IRecordingsFactory factory = (Recording.IRecordingsFactory)
Activator.GetObject(typeof(Recording.IRecordingsFactory),
"tcp://localhost:8888/RecordingsFactory");
Recording.IRecordingsManager myobj = factory.Create();
3.3注销通道
如果要关闭Remoting的服务,则需要注销通道,也可以关闭对通道的监听。在Remoting中当我们注册通道的时候,就自动开启了通道的监听。而如果关闭了对通道的监听,则该通道就无法接受客户端的请求,但通道仍然存在,如果你想再一次注册该通道,会抛出异常。
private void NewUnRegisterChannels()
{
IChannel[] channels = ChannelServices.RegisteredChannels;
foreach (IChannel channel in channels)
{
if (channel.ChannelName.ToLower().IndexOf("http") >= 0) // http
{
HttpChannel httpChannel = (HttpChannel)channel;
//Close listening
httpChannel.StopListening(null);
//Unregister channel
ChannelServices.UnregisterChannel(httpChannel);
}
if (channel.ChannelName.ToLower().IndexOf("tcp") >= 0) // tcp
{
TcpChannel tcpChannel = (TcpChannel)channel;
//Close listening
tcpChannel.StopListening(null);
//Unregister channel
ChannelServices.UnregisterChannel(tcpChannel);
}
}
}
五.小结
按照一些博友(blog friend)的话说:“Microsoft.Net Remoting真可以说是博大精深”。小小一篇文章实在无法将内容都涵盖进来,很多地方也只是点到为止,未敢深究。看来要弄通一门新的技术,实在是一件不容易的事情。