65.9K
CodeProject 正在变化。 阅读更多。
Home

.NET Remoting – 基础操作

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.46/5 (44投票s)

2003年10月7日

13分钟阅读

viewsIcon

195445

downloadIcon

9932

.NET Remoting 教程

目录

  1. 引言
  2. 目标。
  3. 先决条件。
  4. 软件包。
  5. 运行示例。
  6. 配置文件。
  7. 来自客户端程序集的程序集引用。
  8. 客户端源文件 CRemoteObjClient.cs 中的代码块
  9. 客户端源文件 CRemoteObjServer.cs 中的代码块
  10. 演练。
  11. 参考。

引言

.NET Remoting 是一个用于开发分布式应用程序的框架。它是 DEC/RPC/DCOM 的后继者。简单地说,Remoting 允许一个应用程序(Remoting Client)在远程服务器(Remoting server)上实例化一个类型(Remotable class)。客户端和服务器对象(Remoting Server 托管的 Remotable 类的实例)之间的通信通过“代理”(Proxy)进行——这是客户端侧的服务器对象的表示。

Remoting Server 可以是一个简单的控制台应用程序、一个 Windows 服务或托管在 IIS 中。它负责托管服务器对象并将其发布到外部世界。Remoting client 是任何使用已发布服务器对象的应用程序。

关于这个广泛的主题有很多教程。不幸的是,大多数教程在涵盖开发人员需要了解的基本操作方面,或多或少都有所欠缺。本文旨在补充 MSDN,并在一个简短的文章中提供对基本 Remoting 任务的完整介绍。

教程目标

本教程将涵盖以下主题。

  • 基本 Remoting,程序化配置(通道/类型),配置文件,LeaseTime,newActivator.GetObject(服务器激活对象,或 SAO)和Activator.CreateInstance(客户端激活对象,或 CAO)
  • 如何为远程类实现接口,并通过接口与远程对象交互。
  • 在远程对象和客户端之间传递自定义/用户定义对象。
  • 对远程对象进行异步方法调用。
  • 事件和 Remoting:客户端如何订阅由远程对象生成的事件。

本教程不涵盖以下主题。

  • 从 IIS 托管服务器 [参考 2, 7]
  • 延迟加载通道
  • 跟踪服务
  • ClientSponsor 和赞助机制 [参考 5]
  • 自定义格式化程序、接收器、通道——这些才是真正复杂的地方。[参考 1]
  • SoapSuds

必备组件

  • .NET 委托和事件。
  • 异步编程。IAsyncResult, BeginInvoke, EndInvoke, WaitHandle... 等。

  • dnetremoting.zip

整体架构

  • 自述文件:dnetremoting.doc(即本文档。)
  • 项目文件夹、类和整体结构

[编辑说明 - 在目录中使用连字符/空格以防止滚动]

模块

文件夹/目录

主要类

命名空间

备注

接口

Dll

/CRemote-ObjInterface

源文件:IRemoteObj.cs

CRemote-ObjInterface

命名空间:CRemote-ObjInterface

用于 CRemoteObj 的接口(IRemoteObj)。三个接口方法

  • Authenticate-Loser
  • SetupUser
  • UpdateProfile
CProfile

演示如何将自定义对象传递给远程对象(CRemoteObj)和从其传递出来。它被标记为 Serializable,并且是 CremoteObj. UpdateProfile 的输入/返回参数。

CStatusEventSink

这是要在客户端进程中实例化的事件接收器类。它继承自 MarshalByRefObject。该类有一个公共方法 StatusHandler。此方法处理 CRemoteObj. AuthenticateLoser 引发的事件。

StatusEventArgs

Serializable 事件参数。请参阅 CremoteObjevStatus。委托到 evStatus 事件可以在 CRemoteObj 类中找到。签名如下:

public delegate void StatusEvent( object sender, StatusEventArgs e);

远程对象 dll

/CRemoteObj

源文件:CRemoteObj.cs

CRemoteObj

命名空间:CRemoteObj

这就是您的远程对象。此类实现 IRemoteObj 接口并公开

方法

  • Authenticate-Loser – 演示客户端如何响应远程对象生成的事件。
  • SetupUser – 演示如何执行异步调用和方法参数输入输出。
  • UpdateProfile – 演示如何将用户定义类的实例传递进出远程对象。

属性

  • objID - 远程对象的生命周期取决于对象的激活模式
  1. 服务器激活 - SingleCall
  2. 服务器激活 - Singleton
  3. 客户端激活

对于服务器激活的 SingleCall 对象,每次客户端通过代理调用方法时,都会创建一个新的远程对象实例。对于“服务器激活的 Singleton”和“客户端激活”对象,远程对象的生命周期取决于服务器配置(配置文件中的 <lifetime> 元素或通过 LifeTimeServices 进行编程设置),以及客户端何时调用远程对象上的方法。默认的 LeaseTime* 是 300 秒。

对象 ID 用于通过为每个对象分配一个随机生成的 ID 来演示远程对象的生命周期。调用远程对象上的方法时,请注意服务器控制台的输出。

服务器

exe

/CRemote-ObjServer

源文件:CRemote-ObjServer.cs

可执行文件

\web_vdir\bin\ CRemoteObj-Server.exe

(C# 控制台应用程序)

CRemote-ObjServer

命名空间:CRemote-ObjServer

CRemoteObjServer 托管/发布远程对象 CRemoteObj

配置文件

  • cremoteobjserv (client). config – 用于客户端激活。
  • cremoteobjserv (wellknown) .config – 用于服务器激活;WellKnown ObjectMode = SingleCall。如果您想使用 Singleton 模式发布远程对象,则需要修改配置文件。

选项 2 按如下方式加载配置文件

RemotingConfiguration.Configure( "cremoteobjserv. config");

请注意,除非文件名符合以下约定,否则配置文件中的安全设置将被忽略:

AssemblyName. exe.config

在这种情况下:

CRemoteObjServer. exe.config

在本教程中,请将配置文件重命名为:cremoteobjserv.config 并将其放在与 exe 文件相同的文件夹(\web_vdir\bin)中。

客户端

exe

/CRemote-ObjClient

source

CRemoteObj-Client.cs

可执行文件

\ bin\Debug \CRemoteObj-Client.exe

(C# 控制台应用程序)

CRemote-ObjClient

命名空间:CRemote-ObjClient

注意

如果不是因为我们在 <BLOCK 2-A> 中使用了“new”关键字来实例化远程对象

CRemoteObj obj = new CRemoteObj();

那么我们可以省略对 CRemoteObj 程序集的引用——只需引用接口 CRemoteObjInterface 即可。

配置文件(文件夹:/config file)

  • cremoteobjclient (client).config – 用于客户端激活。
  • cremoteobjclient (wellknown). config – 用于服务器激活;

在 <BLOCK 2-A> 中,我们调用 Configure 来加载配置文件

RemotingConfiguration. Configure( "cremoteobjclient. config");

请注意,除非文件名符合以下约定,否则配置文件中的安全设置将被忽略:

AssemblyName .exe.config

在这种情况下:

CRemoteObjClient .exe.config

在本教程中,请将配置文件重命名为:cremoteobjclient.config 并将其放在与 exe 文件相同的文件夹(\web_vdir\bin)中。


运行示例

  1. 运行服务器可执行文件。
  2. 运行客户端可执行文件。

服务器和客户端都是 C# 控制台应用程序。请注意控制台输出。

配置文件

在 Remoting 客户端和 Remoting 服务器开始通信之前,无论是在服务器端还是客户端,都必须首先正确配置两项信息:

通信通道

  • 通信通道:端口号、协议、URI/URL。配置文件中的 <channel> 标签。(服务器和客户端两端)

注意:您不需要为客户端指定端口号。

要发布或使用的类型(Remotable class)

  • 要发布和激活模式的资源(Remoting server 的配置文件)配置文件中的 <service> 标签(Remoting server 端)。
  • 要使用的资源(Remoting client 的配置文件)配置文件中的 <client> 标签(Remoting client 端)。

两种配置选项

  • 使用 XML 配置文件
    • RemotingConfiguration.Configure("cremoteobjserv.config");
  • 以编程方式配置设置
    //Configure channel:
    ChannelServices.RegisterChannel(httpchannel);
    //Register TYPE for server-activated objects (SAO):
    RemotingConfiguration.RegisterWellKnownServiceType(
      typeof(nsCRemoteObj.CRemoteObj), //Type
      "CRemoteObjURI", //"object URI" (NOT URL to remoting server!)
      WellKnownObjectMode.SingleCall //Activation mode: SingleCall or Singleton
    );
    //Register TYPE for client-activated objects (CAO):
    RemotingConfiguration.RegisterActivatedType(
      Typeof(nsCRemoteObj.CRemoteObj) );

注意:有两种类型的服务器对象:SAO(服务器激活对象)和 CAO(客户端激活对象)。SAO 可进一步分为两种类型:singlecallsingleton。以下是简要说明。

  • 服务器激活对象(SAO)= 知名对象。
  • 客户端激活对象(CAO)。

SAO

SingleCall - 每次客户端通过代理调用方法时,都会创建一个新的可远程类实例。

生命周期:实例在方法调用期间保留在内存中。

这是无状态的。

Singleton - Singleton 实例在首次方法调用时创建。

生命周期:此实例保留在内存中,直到“租约”过期。

这是有状态的。在 Remoting 服务器上只实例化一个 Singleton-SAO 实例。多个 Remoting 客户端由同一个 Singleton-SAO 提供服务。多个 Remoting 客户端可以共享一个 Singleton SAO 实例进行通信。

CAO

  • 一旦 Remoting 客户端通过 Activator.CreateInstancenew 关键字请求创建实例,它就会在远程服务器上实例化。这与 SAO(服务器对象在首次方法调用时创建)不同。
  • 生命周期:与 Singleton-SAO 相同,对象保留在内存中,直到“租约”过期。
  • 有状态。每个 CAO 实例仅服务于负责创建它的 Remoting 客户端。因此,如果您有多个 Remoting 客户端,每个客户端都会获得自己的 CAO 实例。

Single-call SAO 是三种选项中最具可伸缩性的,并且最适合负载均衡的环境。Singleton-SAO 和 CAO 提供了更大的灵活性,因为

  • Remoting 客户端对服务器对象的生命周期拥有完全控制权。
  • 服务器对象是有状态的。CAO 在多个请求/方法调用中保持状态。Singleton-SAO 在多个 Remoting 客户端之间保持状态。

示例配置文件

服务器端

服务器激活。文件名:cremoteobjserv(wellknown).config

客户端

客户端激活。文件名:cremoteobjclient(wellknown).config

除了类型和通道信息外,配置文件还可以包含对象生命周期、安全等设置。例如:Singleton SAO 和 CAO 远程对象的生命周期可以在配置文件中的 <lifetime> 标签中配置。

远程对象在其 CurrentLeaseTime>0 的时间内有效。当对象首次实例化时,CurrentLeaseTime = leaseTime。从这一点开始,CurrentLeaseTime 开始递减,直到下一个方法调用,或者“赞助商”延长了“租约”。在下一次方法调用时(如果租约尚未过期),CurrentLeaseTime 将重置为 renewOnCallTime。另一方面,如果 CurrentLeaseTime 在下一次方法调用到来之前递减到零,则对象将被标记为垃圾回收。如果发生这种情况,下一次方法调用将由远程类的新实例提供服务。简而言之,“服务器激活的 Singleton”和“客户端激活”的远程对象的生命周期为 CurrentLeaseTime>0。生命周期设置也可以通过编程方式配置。

using System.Runtime.Remoting.LifetimeServices;
LifetimeServices.LeaseTime = TimeSpan.FromMinutes(3);
LifetimeServices.RenewOnCallTime = TimeSpan.FromMinutes(3);

有关租约机制和赞助机制的更多信息,请参阅 MSDN 中的“Lifetime Leases”[参考 5] 和“Remoting Example: Lifetimes”[参考 8] 主题。

来自“客户端”程序集的程序集引用

  • 引用接口程序集(即,“项目”菜单>“添加引用”>选择接口 CRemoteObjInterface.dll
现在,由于我们在 BLOCK 2-A 中使用了“new”关键字,我们在接口程序集(CRemoteObjInterface.dll)的引用之外,还添加了对远程对象程序集(CRemoteObj.dll)的引用。但一般来说,我们可以通过 Activator.GetObjectActivator.CreateInstance 检索代理,从而无需引用远程对象程序集(即,客户端仅引用接口程序集)。

客户端源文件 CRemoteObjClient.cs 中的代码块

基本上,客户端源文件分为两个互斥的块——在客户端的源代码 CRemoteObjClient.cs 中通过以下标签进行标记:

<BLOCK X-Y>

... code block ...

<BLOCK X-Y END>

当您想运行 BLOCK 2 中的代码时,应该注释掉 BLOCK 1,反之亦然。

  • BLOCK 1:远程对象被发布为服务器激活对象。
  • BLOCK 2:远程对象被发布为客户端激活对象。

因此,根据 CRemoteObjServer 中的设置,您可能希望在编译和执行之前注释掉其中一个块。此外,<BLOCK 2-A> 和 <BLOCK 2-B> 是互斥的。

  • BLOCK 2-A 通过加载配置文件来配置通道/类型信息,然后使用“new”关键字实例化远程对象。
  • BLOCK 2-B 通过编程方式配置通道/类型信息。通过调用 Activator.CreateInstance(..) 检索代理。

因此,再次强调,在编译和执行之前注释掉其中一个。

namespace nsCRemoteObjClient

{ //nsCRemoteObjClient

class CRemoteObjClient

{ //CRemoteObjClient
  1. 委托到 CRemoteObj.SetupUser()
  2. 签名public string SetupUser(string sname, string spasswd, out string sTime)
  3. 参考 <BLOCK 1-C>
public delegate string SetupUserDelegate(...);
[STAThread]
static void Main(string[] args)
{ //Main()
//Scenario 1: "server-activated" remote object. 

<BLOCK 1>

try

{ //try-A

<BLOCK 1-A>

检索接口/代理
  • 演示如何通过 Activator.GetObject(...) 检索代理。
  • 将代理强制转换为 IRemoteObj:进一步分离提供者和消费者。

<BLOCK 1-A END>

<BLOCK 1-B>

  • LeaseTime
  • 通过接口在远程对象上调用方法。

<BLOCK 1-B END>

<BLOCK 1-C> 异步编程/Remoting

  • 演示对远程对象的异步调用以及如何使用 BeginInvokeEndInvoke 将参数传递进出远程对象公开的方法。
  • 演示如何将用户定义对象传递给/从远程对象。

<BLOCK 1-C END>

} //try-A
catch(Exception err)
{
}

<BLOCK 1 END>

  • 场景 2:“客户端激活”远程对象。

<BLOCK 2>

try

{ //try-B

<BLOCK 2-A>

  • 演示如何使用配置文件配置通道信息。
  • 演示如何使用“new”关键字实例化远程对象。

<BLOCK 2-A END>

<BLOCK 2-B>

演示如何通过以下方式检索到远程对象的代理:

Activator.CreateInstance(...)

<BLOCK 2-B END>

<BLOCK 2-C>

  • 演示如何通过代理调用方法。
  • 演示如何订阅由远程对象生成的事件。

<BLOCK 2-C END>

} //try-B
catch(Exception err)
{
}

<BLOCK 2 END>

    } //Main()
  } //CRemoteObjClient
} //nsCRemoteObjClient

服务器源文件中的代码块

  • 块 1:选项 1:通过以下方式进行通道和类型信息的编程注册:
ChannelServices.RegisterChannel
RemotingConfiguration.RegisterWellKnownServiceType
  • 块 2:选项 2:通过加载 xml 配置文件进行配置
RemotingConfiguration.Configure("cremoteobjserv.config");

同样,如果您使用选项 2,请注释掉选项 1,反之亦然。

演练

  • Remoting 基础,配置(通道/类型):编程方式,配置文件。LeaseTime,“new”,Activator.GetObject(用于服务器激活)和Activator.CreateInstance(用于客户端激活)
  • 配置文件:请参阅上一节。

LeaseTime

  • 更改配置文件,并在不同的激活模式下运行应用程序:服务器激活 SingleCall,服务器激活 Singleton,客户端激活。
  • 运行客户端。注释掉 <BLOCK 2> 和 <BLOCK 1-C>。请注意 obj.AuthenticateLoser 在 <BLOCK 1-B> 中被调用。
  • 监控服务器控制台和 objID。对于 Singleton,默认 LeaseTime 为 300 秒,如果方法调用之间的时间间隔大于 300 秒,当远程对象在以下模式下运行时,每次调用都会创建一个新的远程对象实例:
    • 服务器激活 Singleton。
    • 客户端激活

租约配置可以在服务器配置文件中找到。

<lifetime leaseTime="100MS" sponsorshipTimeout="50MS" 
    renewOnCallTime="100MS" leaseManagerPollTime="10MS" />

在 <BLOCK 1-B> 中,连续方法调用之间的时间为 101 毫秒。略大于 100 秒。因此,每次方法调用都应由一个新实例提供服务——因此是不同的 objID。调整租约配置并观察。对于服务器激活的 SingleCall,每个方法调用都由一个新实例提供服务。

注意objID 是分配给远程对象 CRemoteObj 实例的随机生成的对象 ID——请查看构造函数。

new”关键字:而不是使用 Activator.GetObjectActivator.CreateInstance,我们可以使用“new”来实例化远程对象。但是,如果您选择通过配置文件配置通道和类型信息,您可以使用“new”关键字来实例化远程对象。但是,“new”意味着您将实例化 CRemoteObj——而不是接口 IRemoteObj。这进一步意味着您的客户端程序集必须引用 CRemoteObj 程序集。

Activator.GetObject:请查看 CRemoteObjClient.cs <BLOCK 1-A>。

IRemoteObj obj = (IRemoteObj) Activator.GetObject(
typeof(IRemoteObj),
"https://:8085/CRemoteObjURI"
);

Activator.CreateInstance:请查看 CRemoteObjClient.cs <BLOCK 2-B>。

//Note that it references CRemoteObjServer – not the remote object.
object[] attrs = { new UrlAttribute(
  "tcp://:8086/CRemoteObjServer") };
ObjectHandle handle = (ObjectHandle) Activator.CreateInstance(
  "CRemoteObj", 
  "nsCRemoteObj.CRemoteObj", 
  attrs);
CRemoteObj obj = (CRemoteObj) handle.Unwrap();

如何为远程类实现接口,并通过接口与远程对象交互。

接口IRemoteObjCRemoteObjInterface.cs

如果我们通过 Activator 的静态方法实例化远程对象,那么客户端程序集就不需要引用远程对象的程序集。这提供了远程对象和客户端之间的进一步分离。如果客户端设置是从配置文件加载的,则必须使用“new”关键字实例化远程对象。这暗示客户端必须引用远程对象的程序集。

查看源代码。注意架构和程序集引用。

在远程对象和客户端之间传递自定义/用户定义对象。

用户定义类CProfile(在 CRemoteObjInterface.cs 中定义)

//ATTENTION: Must be marked Serializable to pass 
//user-defined object in/out of remote object.
[Serializable] 
public class CProfile
{
  public string fname;
  public string lname;
  public string user;
  public string passwd;
  //... other stuff...
};

方法CRemoteObj.UpdateProfile(在 CRemoteObj.cs 中定义)

该方法接受 CProfile 对象作为参数,并返回一个 CProfile 对象。

public CProfile UpdateProfile(CProfile p)
{
//...other stuff...
return pout;
}

该方法也由接口 IRemoteObj 公开,尽管这不是必需的。对方法的调用很简单。现在,让我们看一下客户端源文件 CRemoteObjClient.cs 中的 <BLOCK 1-C>。

CProfile p = new CProfile("John", "Kennedy", "JFK", "ace");
CProfile p2 = obj.UpdateProfile(p);
Console.WriteLine("UpdateProfile completed. return p2 passwd: {0}", 
    p2.passwd);

对远程对象进行异步方法调用。

查看客户端源文件 CRemoteObjClient.cs 中的 <BLOCK 1-C>。

//ATTENTION:
//Delegate for CRemoteObj.SetupUser:
public delegate string SetupUserDelegate(
    string string1, string string2, out string string3);

//...other stuff...

Main(...)
{

<BLOCK 1-C>

SetupUser 包装在委托中,然后再调用 BeginInvoke

  • SetupUserDelegate d = new SetupUserDelegate(obj.SetupUser);

异步调用 CRemoteObj.SetupUser

string sTime;
IAsyncResult ar = d.BeginInvoke(
  "Dexter",
  "AceInOpen",
  out sTime,
  null,
  null
);
//Blocks the current thread until obj.AuthenticateLoser completes.
ar.AsyncWaitHandle.WaitOne(); 
if(ar.IsCompleted)
{
  //Retrieving output: 
  //(a) "output variable" sTime
  //(b) "return variable" value.
  string ret = d.EndInvoke(out sTime, ar);
  Console.WriteLine("obj.SetupUser return. sTime:{0}{1}" +
  "return value: {2}", sTime, Environment.NewLine, ret);
}

//...other stuff...

<BLOCK 1-C END>

//...other stuff...
}

事件和 Remoting:客户端如何订阅由远程对象生成的事件。

事件处理程序在接口模块 CRemoteObjInterface 中声明。

路由机制:当客户端调用 AuthenticateLoser 时,所发生的事件顺序如下:

就是这样。我希望本文档能全面介绍组件开发人员预期的大多数基本操作。请仔细阅读源代码并尝试不同的配置。您将能够构建分布式应用程序并充分利用 .NET Remoting 的强大功能。

参考文献

© . All rights reserved.