正在 .NET 中构建一个需要使用分布式对象的应用程序,并且分布式对象的生存期由客户端控制。您的要求包括能够按值或按引用来传递对象,无论这些对象驻留在同一台计算 机上,还是驻留在同一个局域网 (LAN) 中的不同计算机上,或者是驻留在广域网 (WAN) 中的不同计算机上。
实现策略
这 种模式为在 .NET Remoting 中实现客户端激活对象提供了两种实现方式。客户端激活对象 (CAO) 和服务器激活对象 (SAO) 之间的主要区别在于,是什么控制着远程对象的生存期。在使用 CAO 的情况下,客户端控制着生存期;而在使用 SAO 的情况下,服务器控制着生存期。这里采用的示例在功能上类似于使用服务器激活对象在 .NET 中实现 Broker 中采用的示例。正如 .NET 文档和示例中描述的那样,第一种实现使用了客户端激活。这种实现展示了客户端激活对象的能力;不过,它们也有一些缺点。第二种实现(称为混合法)则解决了这些问题。
客户端激活对象实现
RecordingsManager 类有一个名为 GetRecordings 的方法,它从数据库中检索一列记录,然后在 DataSet 中返回结果。该类扩展了 MarshalByRefObject 类,以确保在远程处理情况下使用 Broker 对象,而不是将对象从服务器复制到客户端。这里描述的功能与"使用服务器激活对象在 .NET 中实现 Broker"中所描述的示例的功能完全相同。
RecordingsManager.cs
以下示例显示了 RecordingsManager类,该类负责从数据库中检索 DataSet:
using System;
using System.Data;
using System.Data.SqlClient;
public class RecordingsManager : MarshalByRefObject
{
public DataSet GetRecordings()
{
String selectCmd = "select * from Recording";
SqlConnection myConnection = new SqlConnection(
"server=(local);database=recordings;Trusted_Connection=yes");
SqlDataAdapter myCommand =
new SqlDataAdapter(selectCmd, myConnection);
DataSet ds = new DataSet();
myCommand.Fill(ds, "Recording");
return ds;
}
}
HttpServer.cs
以下代码将服务器配置为允许使用 new 运算符创建客户端激活对象。该代码利用应用程序名以及要创建的对象的类型来配置服务器,而不是实际地注册一个示例(如 SAO 示例所示)。远程对象的 URL 是 http://localhost:8100/RecordingsServer。SAO 由本地主机上的框架在后台自动创建。该 SAO 负责接受来自客户端的请求,并在客户端请求对象时创建这些对象。
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
public class HttpServer
{
static void Main(string[] args)
{
HttpChannel channel = new HttpChannel(8100);
ChannelServices.RegisterChannel(channel);
RemotingConfiguration.ApplicationName = "RecordingsServer";
RemotingConfiguration.RegisterActivatedServiceType(
typeof(RecordingsManager));
Console.WriteLine("Recordings Server Started");
Console.ReadLine();
}
}
HttpClient.cs
为了能够使用 new 运算符,并且使远程处理框架创建一个远程对象(与本地对象相反),必须首先将远程对象的类型与服务器设置 ApplicationName 属性时所指定的 URL 相关联。该示例将 ApplicationName 定义为 RecordingsServer,并且使用本地主机上的端口 8100。
using System;
using System.Data;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
class HttpClient
{
[STAThread]
static void Main(string[] args)
{
HttpChannel channel = new HttpChannel();
ChannelServices.RegisterChannel(channel);
RemotingConfiguration.RegisterActivatedClientType(
typeof(RecordingsManager),
"http://localhost:8100/RecordingsServer");
RecordingsManager mgr = new RecordingsManager();
Console.WriteLine("Client.main(): Reference acquired");
DataSet ds = mgr.GetRecordings();
Console.WriteLine("Recordings Count: {0}",
ds.Tables["recording"].Rows.Count);
}
}
注册远程对象会将该对象的类型与 URL 相关联。之后,通过调用 new 在服务器上创建一个远程对象。该对象看起来与代码中的任何其他对象一样。
这种实现允许在客户端的控制下直接创建远程对象。另外,这种实现还表明,在配置客户端之后,创建对象的操作与使用 new 运算符在本地创建对象完全相同。不过,它有一个较大的缺点。这就是,您不能使用 SAO 模式中所描述的共享接口方式。这意味着,必须将编译好的对象传递给客户端。有关使用 SoapSuds 的其他替代方式,请参阅 Advanced .NET Remoting [Ingo02]。
注意:传送经过编译的服务器对象违反了分布式对象的一般原则。另外,由于部署和版本控制问题,也不应该这样做。
为了解决一部分这样的问题,下面的实现描述了混合法如何使用 SAO 创建对象。这种方式使客户端能够控制对象的生存期,而不必将服务器代码传送到客户端。
混合法
混合法需要使用 RecordingsFactory SAO,它提供了创建 RecordingsManager CAO 的方法。(如果您不熟悉 SAO 示例,请参阅"使用服务器激活对象通过 .NET Remoting 实现 Broker"。)下面的类图表描述了总体解决方案。
图 1: 混合法的结构
这种实现使用了 SAO 示例中所描述的共享接口法。IRecordingsManager和 IRecordingsFactory 这两个接口位于客户端和服务器所共享的程序集中。IRecordingsFactory 有一个 Create 方法,它可以返回一个对象来实现 IRecordingsManager 接口。这是 AbstractFactory [Gamma95] 模式的一个例子。因为客户端只依靠接口,所以无需传送服务器代码。当客户端需要 IRecordingsManager 对象时,它调用 IRecordingsFactory 实例的 Create 方法。这样,客户端就可以控制 IRecordingsManager 对象的生存期,而无需实现该对象。共享程序集中的两个接口是:
IRecordingsManager.cs
以下示例显示了 IRecordingsManager 接口:
using System;
using System.Data;
public interface IRecordingsManager
{
DataSet GetRecordings();
}
IRecordingsFactory.cs
以下示例显示了 IRecordingsFactory 接口:
using System;
public interface IRecordingsFactory
{
IRecordingsManager Create();
}
这些对象的服务器实现(RecordingsFactory 和 RecordingsManager)非常简单,并且包含在它们自己的、名为 Server 的程序集中。
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 DataSet GetRecordings()
{
Console.WriteLine("Assembly: {0} - filling a request",
Assembly.GetEntryAssembly().GetName().Name);
String selectCmd = "select * from Recording";
SqlConnection myConnection = new SqlConnection(
"server=(local);database=recordings;Trusted_Connection=yes");
SqlDataAdapter myCommand =
new SqlDataAdapter(selectCmd, myConnection);
DataSet ds = new DataSet();
myCommand.Fill(ds, "Recording");
return ds;
}
}
HttpServer.cs
混合法中的服务器初始化代码用于为服务器激活的 RecordingsFactory 对象配置远程处理框架。激活方式与所使用的通道和协议无关,因此与以前一样(端口 8100 上的 HTTP 协议)。
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
public class HttpServer
{
static void Main(string[] args)
{
HttpChannel channel = new HttpChannel(8100);
ChannelServices.RegisterChannel(channel);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(RecordingsFactory),
"RecordingsFactory.soap",
WellKnownObjectMode.Singleton);
Console.ReadLine();
}
}
在该代码中,RecordingsFactory 类型与 URL http://localhost:8100/RecordingsFactory.soap 相关联。
HttpClient.cs
客户端代码显示了这种方式的混合性质。首先使用 Activator.GetObject 方法从服务器检索 IRecordingsFactory 对象。然后,使用这个服务器激活对象来调用 Create 方法,以便实例化一个 IRecordingsManager 对象。这个新实例化的对象是在服务器上创建的,但它是一个远程对象。
using System;
using System.Data;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
public class HttpClient
{
[STAThread]
static void Main(string[] args)
{
HttpChannel channel = new HttpChannel();
ChannelServices.RegisterChannel(channel);
IRecordingsFactory factory = (IRecordingsFactory)
Activator.GetObject(typeof(IRecordingsFactory),
"http://localhost:8100/RecordingsFactory.soap");
Console.WriteLine("Client.main(): Factory acquired");
IRecordingsManager mgr = factory.Create();
DataSet ds = mgr.GetRecordings();
Console.WriteLine("Recordings Count: {0}",
ds.Tables["recording"].Rows.Count);
}
}
使用客户端激活对象通过 .NET Remoting 来实现 Broker 具有以下优缺点:
优点
- 分布式对象模型。 .NET remoting 为全功能的分布式对象模型提供了运行在客户端和服务器上的完整公共语言运行库语义。客户端和服务器之间所传递的数据的保真度不会受任何影响。该示例显示了如何在客户端和服务器之间传递复杂类型 System.Data.DataSet。如果连接的两端没有公共语言运行库,就不可能实现这样的传递。
- 构造参数。客户端激活实现和混合实现中的对象都考虑到了在创建对象时传递构造函数的参数。
缺点
- 远程对象。 您必须记住,这些对象是远程对象。即使它们看上去像是本地对象,但从服务器来回封送数据仍然需要开销。记住,远程调用比公共语言运行库中的本地调用至少慢 1000 倍。因此,您应当只在需要时才进行这样的调用。由于需要最大限度地减少往返操作,这可能导致您在处理接口时不会使用最细的粒度。
- 无共享程序集。在 CAO 方式中,不能使用共享程序集方式来处理接口。相反,必须将实现传送到客户端,或使用 SoapSuds 提取元数据。
- 部署的复杂性。使用混合法中所描述的服务器激活对象时,在客户端请求对象之前必须已经注册了该对象。这会使部署变得更加复杂。
- 有限的互操作性。 您可以使用 .NET Remoting 来构建 Web Service。不过,必须将端点减少到最简单的数据类型。例如,如果希望与其他 Web Service 工具包实现互操作,必须将参数限制为内置的简单类型和您自己的数据类型(不使用 .NET Framework 类型,例如 DataSet),并且使用服务器激活对象。
- 更加复杂。与 Web Service 相比,.NET Remoting 更难学习、实现和调试。
安全考虑事项
要 使用 Microsoft Internet 信息服务 (IIS) 所提供的安全功能(例如,标准 HTTP 身份验证方案,包括基本验证、摘要式验证、数字证书,甚至 Microsoft .NET Passport),您必须使用一个基于 HTTP 的应用程序,而且该应用程序应当驻留在具有 ASP.NET 环境的 IIS 中。如果要使用其他任何传输协议,或使用 IIS 之外的 HttpChannel,都需要您提供安全机制。
操作考虑事项
以 下是 MSDN文章"Performance Comparison: .NET Remoting vs. ASP.NET Web Services"(.NET Remoting 与. ASP.NET Web Service 的性能比较)[Dhawan02] 中的性能比较的概述。该文的结论是,通过使用 TCP 通道和二进制序列化以及 Windows 服务主机,您可以实现最高性能。这种配置通过原始 TCP 套接字传输二进制数据,这比 HTTP 更有效。与 HttpChannel(它使用驻留在具有 ASP.NET 的 IIS 中的 SOAP 序列化)这种最慢的方法相比,其性能快 60%。
驻留在 IIS 中会导致性能下降,因为它涉及从 IIS (Inetinfo.exe) 到 Aspnet_wp.exe 的额外进程跳跃。不过,如果选择在没有 IIS 和 ASP.NET 的情况下驻留您的通道,则需要提供您自己的身份验证、授权和隐私机制。