正在 .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 具有以下优缺点:
优点
缺点
安全考虑事项
要 使用 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 的情况下驻留您的通道,则需要提供您自己的身份验证、授权和隐私机制。