正在使用 Microsoft? .NET Framework 构建一个需要使用分布式对象的应用程序。您的要求包括能够按值或按引用来传递对象,无论这些对象驻留在同一台计算机上,还是驻留在同一个局域网 (LAN) 中的不同计算机上,或者是驻留在广域网 (WAN) 中的不同计算机上。应用程序不需要您显式控制远程对象的生存期。
关于 .NET Remoting 的背景信息
远 程处理使用对象引用来进行客户端和服务器之间的通信。在服务器激活情况下,客户端使用远程处理基础结构 (Activator.GetObject) 来检索对现有服务器对象的引用。有了对象的引用后,就可以调用对象的方法,就好像该对象在您的进程中,而没有运行在不同的计算机上。以下基础机制用于实现 该功能:
- 客户端检索远程类型的实例。
- 远程处理基础结构创建一个充当远程类型的代理对象。
- 客户端调用该代理的方法。远程处理系统收到调用、将其路由到服务器进程、调用服务器对象,然后将结果返回给客户端代理,客户端代理再将结果传递给客户端对象。
调 用本身必须以某种方式在客户端和服务器之间进行发送。远程处理基础结构将该机制称为传输通道。通道在应用程序之间跨越远程处理边界传输消息,无论这种边界 是应用程序域之间、进程之间还是计算机之间的边界。通道可以在端点上侦听入站消息;将出站消息发送到另一个端点,或者同时执行这两个任务。这样,您就可以 插入各种协议,即使公共语言运行库不在通道的另一端。
虽然服务器进程知道关于每个唯一对象的一切信息,但客户端只知道它需要引用另一个应用程序域中的某个对象(可能在另一个计算机上)。从服务器应用程序域之外的范围来看,该对象是通过 URL 定位的。
服务器激活
正如分布式系统群集的介绍中描述的那样,.NET Framework 支持两种激活模型:服务器激活和客户端激活。服务器激活对象是其生存期直接由服务器控制的对象。只有当客户端调用对象的方法时,而不是在客户端调用 new 或 Activator.GetObject() 时,服务器应用程序域才会创建这些对象;这样可以减少只是为了创建实例而发生的网络往返通信。当客户端请求一个服务器激活类型的实例时,只会在客户端应用 程序域中创建一个代理。不过,这还意味着服务器激活类型只允许有默认的构造函数。如果要发布的类型的实例将以需要接受参数的特定构造函数来创建,那么,可 以使用客户端激活。
为了创建服务器激活类型的实例,客户端通常使用 Activator.GetObject().
选择协议和序列化机制
您 选择的协议类型会影响应用程序执行的方式。有关为应用程序选择正确的通道类型的某些标准,请参阅《.NET Framework Developer's Guide》中的“Choosing Communication Options in .NET”主题,您可以访问 MSDN? 开发人员程序网站:http://msdn.microsoft.com/library/ 来了解有关内容。
在此模式中,您将看到 HttpChannel/SOAP 和 TcpChannel/Binary 这两个示例。
实现策略
该模式提供了服务器激活对象的两个示例,以及 .NET Remoting 基础结构的灵活性。第一个示例使用 HttpChannel 及其默认的序列化机制 SOAP。第二个示例使用 TcpChannel 及其默认的二进制序列化机制。在讨论应用程序本身之前,我们首先要了解必须在整个网络中分布的类。
服务器对象
RecordingsManager 类有一个名为 GetRecordings 的方法,它从数据库中检索一列记录,然后在 DataSet 中返回结果。注意,在确定要通过远程连接传输的最佳数据类型时,会涉及到一系列考虑因素。该示例使用 DataSet,因为它的示例代码很简短,并且显示了复杂数据类型是如何传送的。有关本主题的深入探讨,请参阅 MSDN 文章“Designing Data Tier Components and Passing Data Through Tiers”:
http://msdn.microsoft.com/library/en-us/dnbda/html/BOAGag.asp
RecordingsManager.cs
以下示例显示了 RecordingsManager 类:
using System;
using System.Data;
using System.Data.SqlClient;
public class RecordingsManager
{
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;
}
}
必须以远程方式访问该类。首先,RecordingsManager 类必须从名为 MarshallByRefObject 的远程处理基础结构中的一个类继承而来。MarshalByRefObject 是那些通过使用代理交换消息来跨越应用程序域边界进行通信的对象的基类。不是从 MarshalByRefObject 继承的对象会以隐式方式按值封送。当远程应用程序引用一个按值封送的对象时,将跨越远程处理边界传递该对象的副本。因为您希望使用代理方法而不是副本方法进行通信,因此需要继承 MarshallByRefObject。其次,您需要从该类提取一个接口。接口对于减少客户端和服务器之间的依赖性是必不可少的,另外,也可以更好地部署应用程序。有关详细信息,请参阅该模式后面的“部署考虑事项”。
IRecordingsManager.cs
以下是提取的 IRecordingsManager 接口的代码:
using System;
using System.Data;
public interface IRecordingsManager
{
DataSet GetRecordings();
}
RecordingsManager.cs (启用远程支持)
更改 RecordingsManager 后得到以下代码:
public class RecordingsManager : MarshalByRefObject, IRecordingsManager
{ /* */ }
HttpChannel:SOAP 序列化
选择此通道和序列化机制的主要动机是安全性和互操作性。通过以 Microsoft Internet 信息服务 (IIS) 为宿主的 HttpChannel,可以利用内置在 IIS 和 ASP.NET 中的安全功能。如果您选择任何其他通道,或选择 HttpChannel 不驻留在 IIS 中,则必须提供自己的安全功能。另外,为了实现不同操作系统之间的互操作,必须使用 HttpChannel 和 SOAP 序列化。不过,由于使用了 XML 序列化以及在 IIS 和 ASP.NET 内使用 HTTP 协议而需要额外的系统开销,选择 HttpChannel 并不能获得最高性能。有关详细信息,请参阅此模式后面的“操作考虑事项”。
以下解决方案将 HttpChannel 以及 SOAP 序列化用于前面描述的 RecordingsManager 类(请参阅图 1)。
图 1 HttpChannel 实现
HttpServer.cs
HttpServer 是一个控制台应用程序,该程序创建了 HttpChannel 对象并分配端口 8100。然后,代码将名称“RecordingsManager.soap”与一个 RecordingsManager 实例相关联。
服务器激活对象有两种激活模式:Singleton 和 SingleCall。
Singleton 类型在任何时刻只能有一个实例。如果实例已存在,则所有客户端请求都由该实例来处理。如果不存在实例,服务器将创建一个实例,并且所有随后的客户端请求都会由该实例来处理。
SingleCall 类型对于每个客户端请求始终有一个实例。下一个方法调用将由其他服务器实例来处理,即使前一个实例尚未被系统回收。
RecordingsManager 使用 Singleton 激活模式,因此只有一个 RecordingsManager 实例运行在服务器上。这个过程很有效,因为对象只有一个方法来检索一组预定义数据。最后一行确保用户按下 Enter 键之后代码才退出。请注意,也许这不是确保程序不退出的最佳方式。如果程序退出,客户端将无法访问服务器对象。
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(RecordingsManager),
"RecordingsManager.soap",
WellKnownObjectMode.Singleton);
Console.ReadLine();
}
}
HttpClient.cs
客户端程序调用远程处理框架函数 Activator.GetObject(),从而指定对象所在的 URL 以及应该返回的类型。在本示例中的情况下,IRecordingsManager 对象应该在 http://localhost:8100/RecordingsManager.soap。有了实例之后,可以调用实例的方法,就好像它在同一个应用程序域中。
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);
IRecordingsManager mgr = (IRecordingsManager)
Activator.GetObject(typeof(IRecordingsManager),
"http://localhost:8100/RecordingsManager.soap");
Console.WriteLine("Client.main(): Reference acquired");
DataSet ds = mgr.GetRecordings();
Console.WriteLine("Recordings Count: {0}",
ds.Tables["recording"].Rows.Count);
}
}
TcpChannel:二进制序列化
选择此通道和序列化机制的主要动机是性能。实际上,使用二进制序列化就能显著提高性能。(请参阅“操作考虑事项”。)如果没有任何安全问题(例如,您正在构建一个完全运行在防火墙内的小应用程序),应该使用 TcpChannel 和二进制序列化,因为这样会获得最佳性能。
以下解决方案将 TcpChannel 以及二进制序列化用于前面描述的 RecordingsManager 类(请参阅图 2)。
图 2 TcpChannel/ 二进制序列化实现
TcpServer.cs
TcpServer 是一个控制台应用程序,它创建 TcpChannel 对象并分配端口 8100。然后,代码将名称“GetRecordingsManager”与一个 RecordingsManager 实例相关联。RecordingsManager 的激活模式是 Singleton,因此只会有一个 RecordingsManager 实例运行在服务器上。最后一行确保用户按下 Enter 键之后代码才退出。请注意,也许这不是确保程序不退出的最佳方式。如果程序退出,客户端将无法访问服务器对象。
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
public class TcpServer
{
static void Main(string[] args)
{
TcpChannel channel = new TcpChannel(8100);
ChannelServices.RegisterChannel(channel);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(RecordingsManager),
"GetRecordingsManager",
WellKnownObjectMode.Singleton);
Console.ReadLine();
}
}
TcpClient.cs
客户端程序通过调用远程处理框架方法 Activator.GetObject(),以便在服务器上检索 RecordingsManager 对象的代理。该方法指定对象所在的 URL 以及应该返回的类型。在本示例的情况下,IRecordingsManager 对象应该位于:http://localhost:8100/GetRecordingsManager。有了实例后,可以调用该实例的方法,就好像它在同一个应用程序域中。
using System;
using System.Data;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
class TcpClient
{
[STAThread]
static void Main(string[] args)
{
TcpChannel channel = new TcpChannel();
ChannelServices.RegisterChannel(channel);
IRecordingsManager mgr = (IRecordingsManager)
Activator.GetObject(typeof(IRecordingsManager),
"tcp://localhost:8100/GetRecordingsManager");
Console.WriteLine("Client.main(): Reference acquired");
DataSet ds = mgr.GetRecordings();
Console.WriteLine("Recordings Count: {0}",
ds.Tables["recording"].Rows.Count);
}
}
部署考虑事项
使用 .NET Remoting 时,必须在将应用程序部署到不同的程序集中时多加小心。主要目标是要确保服务器上的代码不会传送到客户端。图 3 是 HttpChannel/SOAP 示例的 UML 部署图。
图 3 HttpChannel/SOAP 示例的结构
该示例使用一个名为 IrecordingsManager 的程序集,该程序集由客户端和服务器共享。该程序集包含 IRecordingsManager 接口,它定义了客户端和服务器正在共享的远程对象的接口。在该示例中,IRecordingsManager 程序集被下载到客户端。
测试
用 Nunit 为服务器编写测试相对比较简单。可以从服务器检索对象,然后将这些对象当作本地对象调用它们的方法。下面的类测试 HttpServer 类:
HttpServerFixture.cs
using System;
using System.Data;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using NUnit.Framework;
[TestFixture]
public class HttpServerFixture
{
private IRecordingsManager mgr;
private HttpChannel channel;
private DataSet dataSet;
[SetUp]
public void LoadDataSet()
{
channel = new HttpChannel();
ChannelServices.RegisterChannel(channel);
mgr = (IRecordingsManager)
Activator.GetObject(typeof(IRecordingsManager),
"http://localhost:8100/RecordingsManager.soap");
dataSet = mgr.GetRecordings();
}
[Test]
public void RetrieveDataSet()
{
DataTable recording = dataSet.Tables["recording"];
Assertion.AssertEquals(4,recording.Rows.Count);
DataRow row = recording.Rows[0];
string title = (string)row["title"];
Assertion.AssertEquals("Up", title.Trim());
}
[TearDown]
public void Release()
{
ChannelServices.UnregisterChannel(channel);
}
}
结果上下文
使用服务器激活对象通过 .NET Remoting 来实现 Broker 具有下列优缺点。
优点
.NET Remoting 为全功能的分布式对象模型提供了运行在客户端和服务器上的全公共语言运行库语义。客户端和服务器之间所传递的数据的保真度不会受任何影响。该示例显示了如 何在客户端和服务器之间传递复杂类型 System.Data.DataSet。如果连接的两端没有公共语言运行库,就不可能实现这样的传递。
缺点
有些 Broker 优点会受到以下潜在缺点的影响:
- 远程对象。 您必须记住,这些对象是远程对象。即使它们看上去像是本地对象,但从服务器来回封送数据仍然需要开销。记住,远程调用比公共语言运行库中的本地调用至少慢 1000 倍。因此,您应当只在需要时才进行这样的调用。由于需要最大限度地减少往返操作,这可能导致您在处理接口时不会使用最细的粒度。
- 部署的复杂性。使用示例中描述的服务器激活对象时,在客户端请求对象之前,必须已经注册了该对象。这会使部署变得更加复杂。
- 有限的互操作性。 您可以使用 .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 的情况下驻留您的通道,则需要提供您自己的身份验证、授权和隐私机制。