程汤培 管建和
摘要 通过实现一个远程主机信息获取的程序来展示.NET Remoting技术的使用方法与技巧。 关键词 .NET Remoting,远程处理,RPC 在.NET Remoting 之前,DCOM、Java RMI以及CORBA等分布式应用技术已经在企业级应用中发挥了重要作用。相比同样出自Microsoft公司的DCOM的来说,Remoting改善了很多功能并极好的融合到.NET平台下。它提供了一种允许对象通过应用程序域与另一对象进行交互的新的框架,被誉为管理应用程序域之间的 RPC 的首选技术。 一、.NET Remoting技术概述 .NET Remoting 是一种在不同应用程序域之间通信的技术。使用.NET Remoting在不同应用程序域之间通信可以无论是在同一个进程中还是一个系统的进程之间或不同系统的进程之间进行。.NET Remoting主要由下面几个重要元素所组成: 1.远程对象 远程对象是运行在服务器上的对象。客户机不能直接来调用远程对象的方法,而需要使用代理。为了定义远程对象,因此首先必须实现可远程处理的类型。要使其他应用程序域中的对象能够使用可远程处理的类的实例,该类必须继承MarshalByRefObject类型。 定义一个从 MarshalByRefObject 类派生的类,就像对待非可远程处理的类型一样,实现该类的方法和属性。而为了要使其他应用程序域中的对象能够在远程创建该对象的实例,必须生成宿主或侦听器应用程序,以完成以下两项任务: 首先是选择并注册一个信道,该信道是处理网络协议和序列化格式的对象;其次是要将类型注册到.NET远程处理系统,使它可以使用信道来侦听对类型的请求。 2.信道 信道用于客户机与服务器之间的通信之间的通信。.NET Framework 2.0提供了三个默认的信道:HttpChannel(它使用 SOAP 格式化)、TcpChannel(它使用二进制格式化)以及IPCChannel。大多数的Web服务所使用的是 HttpChannel信道,它使用HTTP协议进行通信。因为在某些方案中可以通过防火墙(防火墙通常使80端口处于打开状态)使用该信道而不必打开端口,而且该信道支持标准的安全和身份验证协议。在Internet上同样也可以使用TCP信道,但是必须配置防火墙,使客户机能够访问TCP信道所使用的指定端口。与HTTP信道相比,在内部局域网使用TCP信道能够进行更加有效的通信。而IPC信道最适合与在单个系统上进行跨进程的通信,它使用Windows跨进程通信机制,所以效率是最高的。 3.消息 消息是为了客户机与服务器之间的通信而创建的,它被发送到信道中。消息包括了远程对象的信息、被调用方法的名称以及所有的参数。 4.格式标志符与格式标志符提供程序 格式标志符定义了消息是如何传输到信道中的。.NET Framework 2.0提供了两个默认的格式标志符:SOAP格式标志符以及二进制格式标志符。使用SOAP格式标志符可以有更大的灵活性,可以与非.NET Framework的Web服务进行通信。而使用二进制格式标志符的数据传输速度更快,可以更有效地使用于企业内部网络环境中。 格式标志符提供程序用于把格式标志符与信道联系起来。在创建信道时,可以指定要使用的格式标志符提供程序,格式标志符提供程序则定义把数据传输到信道中所使用的格式标志符。 5.代理对象 客户机所调用的实际上是代理对象上的方法,而非远程对象上的方法。代理对象分为两种:透明代理对象以及真实代理对象。从客户端的角度来看,透明代理对象的方法调用就像远程对象的方法调用一样。首先,在透明代理对象上,客户机可以调用远程对象执行的方法。然后,透明代理对象再调用真实代理对象上的Invoke()方法。最后,Invoke()方法使用消息接收器把消息传递个信道。 6.消息接收器 在服务器端和客户端用于来截取消息的对象。它与信道相联系,真实代理对象使用消息接收器把消息传递到信道中。 7.激活器 客户端使用激活器在服务器上创建远程的对象,或者获取一个被服务器激活的对象的代理。 8.RemotingConfiguration类 RemotingConfiguration类是用于配置远程服务器和客户端的一个类。它可以用于读取配置文件或动态地配置远程对象。 9.ChannelServices类 ChannelServices类可用于注册信道并把消息分派到信道中。 二、生成基本的.NET Remoting 应用程序框架 生成一个基本的.NET Remoting应用程序框架主要是要实现包括:可远程处理的类型,用于侦听的宿主(服务器端)应用程序域,用于调用的客户端应用程序域以及配置每个应用程序域中的远程处理系统以便将远程激活用于可远程处理的类型。 图1中显示了服务器端消息的传送。首先是信道接收来自客户端的已格式化消息,并且使用格式标志符解析消息中的SOAP或者二进制数据,然后信道调用服务器环境接收器。服务器环境接收器是一个接收器链,链中的最后一个接收器继续调用对象环境接收器链。最后由对象环境接收器链的最后一个调用远程对象上的方法。
图1 服务器端消息的传送 宿主(服务器端)任务包括了以下几点: 1. 设计相应的服务,它包括:选择服务器端应用程序域;选择远程对象激活的模型;选择数据传输的信道以及端口;决定客户端获取服务的元数据的方式。 2. 实现服务器端应用程序域。 远程处理宿主可以是 Windows 服务、控制台应用程序、Windows 窗体应用程序、Internet 信息服务 (IIS) 进程或 ASP.NET 应用程序。如果以编程方式配置该系统,则无需使用配置文件。如果使用配置文件,必须通过调用RemotingConfiguration.Configure 将该文件加载到系统中,而需要更改相应设置时无须重新编译可执行文件,提供了更大的灵活性。 在服务器端,创建适当的信道并通过调用 ChannelServices.RegisterChannel 将其注册到系统。如果使用配置文件,则必须通过调用 RemotingConfiguration.Configure 将该文件加载到系统中。 下面的代码简单的演示了服务器端的设计 创建远程类的配置文件Listener.exe.config。以XML形式将信道和对象配置写入到文件中。宿主应用程序必须能够找到用来加载远程类的配置的配置文件,因此,在编程时应当注意到该配置文件必须与宿主应用程序保存在同一目录下,否则,将找不到该配置文件并引发一些异常。下面的代码显示了宿主应用程序域的 Listener.exe.config 配置文件里的信息。 <configuration> <system.runtime.remoting> <application> <service> <wellknown mode="Singleton" type="RemotableType, RemotableType" objectUri="RemotableType.rem" /> </service> <channels> <channel ref="http" port="10001"/> </channels> </application> </system.runtime.remoting> </configuration> 导入 System.Runtime.Remoting 命名空间。 using System; using System.Runtime.Remoting; 配置远程类的配置文件。 public class Listener { public static void Main() { RemotingConfiguration.Configure("Listener.exe.config"); } } 将该类另存为 Listener.cs并将该类编译成宿主可执行文件。记住需要将该文件保存在与RemotableType.dll 相同的目录中。编译需要键入以下命令: csc /noconfig /r:RemotableType.dll Listener.cs 举例: // Listener.cs using System; using System.Runtime.Remoting; public class Listener { public static void Main() { RemotingConfiguration.Configure("Listener.exe.config"); Console.WriteLine("Listening for requests. Press Enter to exit..."); Console.ReadLine(); } } 创建完服务器端的宿主应用程序后需要创建相应的类型以便客户端远程调用该类型的对象。下面的代码演示了可远程类型RemotableType的创建。 // RemotableType.cs using System; public class RemotableType : MarshalByRefObject { private string StringValue = "This is the RemotableType."; public string StringMethod() { return StringVale; } }
最后是客户端的设计。图2中显示了客户机调用远程对象方法的消息传输的情况。客户端调用透明代理对象的方法就像对远程对象的方法调用一样,它可以执行远程真实对象的公用方法。透明代理对象使用了反射机制,从程序集中读取元数据,获得真实对象的公共方法的信息。然后透明代理对象再调用真实代理对象。真实代理对象负责把消息传送到信道当中。真实代理对象是“可插入的”,它可以使用定制的执行方法去取代。真实代理对象把消息传递给特使接收器链。特使接收器可以截取和改变消息并由最后一个特使接收器把消息发送到信道当中。
图2 客户端消息传递 客户端的任务包括了以下几点: 1. 设计客户端。 客户端程序服务包括:选择客户端应用程序域;确定激活模式以及客户端激活 URL 或远程类型的已知对象 URL;考虑是否需要注册信道和端口;获取远程类型的元数据。 2. 实现客户端应用程序域。 远程处理宿主可以是 Windows 服务、控制台应用程序、Windows 窗体应用程序、Internet 信息服务 (IIS) 进程或 ASP.NET 应用程序。 3. 用激活模式和其他类型信息(如应用程序名和对象统一资源标识符 (URI))配置客户端远程处理系统。如果要以编程方式配置该系统,则无需使用配置文件。如果使用配置文件,则必须通过调用 RemotingConfiguration.Configure 将该文件加载到系统中。 4. 创建适当的信道并通过调用 ChannelServices.RegisterChannel 将该信道注册到系统。如果使用配置文件,则必须通过调用 RemotingConfiguration.Configure 将该文件加载到系统中。 在之前我们已经定义了一个远程类型,并创建了一个宿主应用程序,最后要做的就是生成该远程类型的客户端,并且其要由宿主应用程序来承载。为此,应用程序必须将其自身注册为该远程对象的客户端,然后就像该远程对象位于客户端的应用程序域中一样调用它。.NET 远程处理系统将截获客户端调用,将其转发到远程对象,并将结果返回到客户端。以下代码过程介绍如何生成简单的远程处理客户端。 导入 System.Runtime.Remoting 命名空间 创建一个客户端配置文件,以便客户端应用程序可以找到远程对象,并将该文件保存到客户端应用程序所在的那个文件夹中。 例如,以下配置文件使远程处理系统知道可以在 RemotableType 程序集中找到 RemotableType 远程对象的类型信息,而且此客户端应该创建并使用位于 http://localhost:10001/RemotableType.rem 的 RemotableType 对象。 <configuration> <system.runtime.remoting> <application> <client> <wellknown type="RemotableType, RemotableType" url=http://localhost:10001/RemotableType.rem /> </client> </application> </system.runtime.remoting> </configuration> 注:如果要在网络上运行该应用程序,必须用远程计算机的名称或者IP地址替换客户端配置中的“localhost”。 客户端应用程序另存为 Client.cs并将该文件保存在与RemotableType.dll 副本相同的目录中。注意:客户端应用程序不应保存在服务端应用程序所在的那个目录中。如果保存到同一目录,将无法确定是否在接收和利用远程引用,因为当应用程序位于同一个目录时,可能会进行程序集和类型解析。 编译客户端应用程序。例如,要编译客户端应用程序 Client.cs,需键入以下命令: csc /noconfig /r:RemotableType.dll Client.cs // Client.cs using System; using System.Runtime.Remoting; public class Client { public static void Main() { RemotingConfiguration.Configure("Client.exe.config"); RemotableType remoteObject = new RemotableType(); Console.WriteLine(remoteObject.StringMethod()); } }
三、远程对象的激活 .NET Remoting中远程对象有两种激活方式,即服务器端激活(WellKnown模式)与客户端激活。对于服务器端激活来说,又分为两种不同的方式——SingCall模式和SingleTon模式。 对于服务器端激活,客户端根据注册在客户端的远程对象的元数据创建透明代理和真实代理。在创建代理的时候,不曾有任何来自客户端访问请求发送到服务器端。因此,此时在服务器端也不可能有相应的远程对象被创建。而真正第一次向服务器端发送消息发生客户端在第一次通过透明代理调用远程对象的某个方法时。当这个方法请求从某个注册在服务器中的相应信道抵达服务器端的时候,服务器端的远程处理框架将提取请求消息中远程对象的ObjRef,与内部维护的、用于存储已注册类型的表当中相关条目进行比较,获得所需的元数据信息,并通过反射机制创建远程对象。因此,在服务器端激活方式下,远程对象是在透明代理第一次调用时激活,而不是在之前的客户端创建透明代理和真实代理的时候激活。对于服务器激活方式的远程对象来说,它的创建之也就只能通过默认的无参构造函数来创建。 SingleTon模式属于一种状态模式,Remoting将为所有客户端建立同一个对象实例。当对象处于活动状态时,SingleTon实例会处理所有后来的客户端访问请求,而不管它们是同一个客户端,还是其他客户端。SingleTon实例将在方法调用中一直维持其状态。SingleTon模式与ASP.NET状态管理中的Application状态类似。在服务器端,SingleTon模式可以使用静态方法RemotingConfiguration.RegisterWellKnownServiceType()来实现: RemotingConfiguration.RegisterWellKnownServiceType( typeof(ServerRemoteObject.ServerObject), "ServiceMessage",WellKnownObjectMode.SingleTon); SingleCall模式属于一种无状态模式,当客户端调用远程对象的方法时,Remoting会为每一个客户端分别建立一个远程对象实例,至于对象实例的销毁则是由垃圾回收器(GC)自动管理的。SingleCall模式与ASP.NET状态管理中的Session状态很相似。同样,SingleCall模式也可以使用静态方法RemotingConfiguration.RegisterWellKnownServiceType()来实现: RemotingConfiguration.RegisterWellKnownServiceType( typeof(ServerRemoteObject.ServerObject), "ServiceMessage",WellKnownObjectMode.SingleCall); 而在客户端,WellKnown对象(无论是SingleTon还是SingleCall)的激活可以使用System.Activator类的静态方法GetObject()或者通过先在客户机上使用RemotingConfiguration.RegisterWellKnownClientType()注册远程对象然后再用new运算符取激活远程对象来实现。 对于客户端激活,它采用了不同的激活方式。客户端在创建透明代理和真实代理的同时创建远程对象。当客户端通过使用New或者Activator.CreateInstance在客户端创建代理对象实际上是经历了以下一个过程:首先在客户端创建一个激活器代理对象,借助这个激活器代理对象,Remoting 框架发送一个激活的请求到服务器端,服务器端的Remoting框架将激活请求中的元数据信息和已经注册的远程对象类型做比较,提取所需的元数据信息并通过反射机制创建远程对象。同时创建这个远程对象的ObjRef通过相应的信道被发送到客户端,随之生成透明代理和真实代理。最后透明代理被客户端所调用。Remoting在激活每个对象实例的时候,会给每个客户端激活的类型指派一个URI。客户端激活模式一旦获得客户端的请求,将为每一个客户端都建立一个实例引用,这点很像SingleCall模式。但是客户端激活模式与SingleCall模式却是有区别的:第一,远程对象创建的时间是不一样的。客户端激活方式几乎是在客户端创建透明代理和真实代理的同时在服务器端创建远程对象;而SingleCall则是要等到客户端的透明代理调用对象方法时才会创建。第二,SingleCall模式激活的对象是无状态的,对象生命期的管理是由GC管理的,而客户端激活的对象则是有状态的,可以自定义其生命周期。第三,两种激活模式在服务器端和客户端实现的方法不一样。尤其是在客户端,SingleCall模式是由GetObject()来激活,它只允许调用远程对象的默认构造函数。而客户端激活模式,则通过CreateInstance()来激活,它可以传递参数,所以可以调用自定义的构造函数来创建实例。 在服务器端和客户端的应用程序实现都可以通过RemotingConfiguration.RegisterActivatedServiceType()方法来实现客户端激活模式。其中ApplicationName属性指的是该对象的URI RemotingConfiguration.ApplicationName = "ServiceMessage"; RemotingConfiguration.RegisterActivatedServiceType( typeof(ServerRemoteObject.ServerObject)); 而客户端的应用程序实现可以也调用进程Activator的CreateInstance()方法调用进程Activator的CreateInstance()方法有多个重载,感兴趣的读者可参见MSDN获得相关的详细信息。 四、程序关键代码及分析 创建类库项目SysInfoServer,其中设计了可远程类型SysInfo类,它继承了MarshalByRefObject类型,其方法主要完成对远程主机的信息读取。其信息包括了机器名称,版本号,用户名,用户所在域,系统目录等。实现的代码如下: using System; using System.Collections; namespace SysInfoServer { /// <summary> /// 摘要说明。 /// </summary> public class SysInfo:MarshalByRefObject { public string GetCurrentTime() { return DateTime.Now.ToLongTimeString(); } public string GetWorkingSet() { return Environment.WorkingSet.ToString(); } public string GetVersion() { return Environment.Version.ToString(); } public string GetUserName() { return Environment.UserName; } public string GetUserDomainName() { return Environment.UserDomainName; } public string GetSystemDirectory() { return Environment.CurrentDirectory; } public string GetMachineName() { return Environment.MachineName; } public string GetCurrentDirectory() { return Environment.CurrentDirectory; } public string GetOSVersion() { return Environment.OSVersion.ToString(); } } } 编译类库项目,生成SysInfoServer.dll库文件。 创建基于控制台的服务器端项目RemotingServer。在引用里添加System.Runtime.Remoting和刚编译好的SysInfoServer.dll库文件。编写相应代码如下: using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; using SysInfoServer;
class MyApp { static void Main() { //创建TCP信道 TcpServerChannel channel=new TcpServerChannel(10001); //注册信道 ChannelServices.RegisterChannel(channel,false); //注册远程对象类型 RemotingConfiguration.RegisterWellKnownServiceType(typeof(SysInfo), "SysInfo", WellKnownObjectMode.SingleCall); Console.WriteLine("远程服务器正在运行中,按Enter键退出"); Console.ReadLine (); } } 编译项目,生成RemotingServer.exe可执行文件。 创建基于控制台或Windows窗体的客户端端项目RemotingClient。在引用里添加System.Runtime.Remoting和刚编译好的SysInfoServer.dll库文件。编写相应代码如下: using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; using SysInfoServer; namespace TimeClient { class MyApp { static void Main(string[] args) { //创建TCP信道 TcpClientChannel channel=new TcpClientChannel(); //注册信道 ChannelServices.RegisterChannel(channel, false); try { //获取远程对象 RemotingConfiguration.RegisterWellKnownClientType(typeof(SysInfo), "tcp://localhost:10001/SysInfo"); SysInfo info = new SysInfo(); Console.WriteLine("远程系统信息列表"); Console.WriteLine("机器名称:" + info.GetMachineName()); Console.WriteLine("版本号:" + info.GetVersion()); Console.WriteLine("用户名:" + info.GetUserName()); Console.WriteLine("用户所在域:" + info.GetUserDomainName()); Console.WriteLine("系统目录:" + info.GetSystemDirectory()); Console.WriteLine("当前目录:" + info.GetCurrentDirectory()); Console.WriteLine("系统版本:" + info.GetOSVersion()); Console.WriteLine("当前时间:" + info.GetCurrentTime()); Console.ReadLine(); } catch { Console.WriteLine("发生错误了!"); } } } } 编译项目,生成RemotingClient.exe执行文件。 五、程序运行效果 运行生成RemotingServer.exe可执行文件,服务器端执行效果如图3所示:
图3 服务器端执行效果 运行生成RemotingClient.exe可执行文件,客户器端获取了远程主机的信息(本例中采用的是本机,可更改localhost为远程主机IP,程序正常运行)执行效果如图4所示:
图4 客户器端执行效果 六、结语 本文通过远程主机信息获取的程序的实现基本介绍了.NET Remoting程序设计的基本思路与框架。当前很多企业级服务都是基于 .NET 远程处理基础结构构建的,它不仅可以提供 COM+ 分布式对象模型所具备的所有丰富性和灵活性,而且包括支持大范围分布式事务。笔者希望在此能抛砖引玉,使读者通过了解本程序框架的基础上并做一些必要修改(比如可远程类型设计的修改)开发出更加功能强大的应用。
|