Microsoft .NET リモート処理: 技術的な概要

 

ピート・オーバーマイヤーとジョナサン・ホーキンス
Microsoft Corporation

概要: この記事では、Microsoft .NET リモート処理フレームワークの技術的な概要について説明します。 これには、TCP チャネルまたは HTTP チャネルを使用する例が含まれます。 (15ページ印刷)

メモ この記事には、更新されたベータ 2 コードが含まれています。

内容

概要 リモート オブジェクト プロキシ オブジェクト チャネル ライセンス認証 オブジェクトの有効期間とリース の結論 付録 A: TCP チャネルを使用したリモート処理のサンプル

はじめに

Microsoft® .NET リモート処理は、オブジェクトがアプリケーション ドメイン間で相互に対話できるようにするフレームワークを提供します。 このフレームワークには、アクティブ化と有効期間のサポート、リモート アプリケーションとの間でのメッセージの転送を担当する通信チャネルなど、さまざまなサービスが用意されています。 フォーマッタは、チャネルによって転送される前にメッセージをエンコードおよびデコードするために使用されます。 アプリケーションでは、パフォーマンスが重要なバイナリ エンコード、または他のリモート処理フレームワークとの相互運用性が不可欠な XML エンコードを使用できます。 すべての XML エンコードでは、SOAP プロトコルを使用して、あるアプリケーション ドメインから他方のアプリケーション ドメインにメッセージを転送します。 リモート処理はセキュリティを念頭に置いて設計されており、ストリームがチャネル経由で転送される前に、チャネル シンクがメッセージとシリアル化されたストリームにアクセスできるようにするフックが多数用意されています。

基になるフレームワークからのサポートなしでリモート オブジェクトの有効期間を管理することは、多くの場合、面倒です。 .NET リモート処理には、選択できるアクティブ化モデルが多数用意されています。 これらのモデルは、次の 2 つのカテゴリに分類されます。

  • クライアントでアクティブ化されたオブジェクト
  • サーバーでアクティブ化されたオブジェクト

クライアントでアクティブ化されたオブジェクトは、リースベースの有効期間マネージャーの制御下にあり、リースの有効期限が切れたときにオブジェクトがガベージ コレクションされるようにします。 サーバーでアクティブ化されたオブジェクトの場合、開発者は "単一呼び出し" または "シングルトン" モデルのいずれかを選択できます。 シングルトンの有効期間は、リースベースの有効期間によっても制御されます。

リモート オブジェクト

リモート処理フレームワークのメイン目的の 1 つは、リモート オブジェクトでメソッドを呼び出して結果を返す複雑さを隠す必要なインフラストラクチャを提供することです。 呼び出し元のアプリケーション ドメイン外のオブジェクトは、オブジェクトが同じコンピューター上で実行されている場合でも、リモートと見なす必要があります。 アプリケーション ドメイン内では、すべてのオブジェクトが参照渡しされ、プリミティブ データ型は値渡しされます。 ローカル オブジェクト参照は、作成されるアプリケーション ドメイン内でのみ有効であるため、その形式のリモート メソッド呼び出しに渡したり、リモート メソッド呼び出しから返したりすることはできません。 アプリケーション ドメインの境界を越える必要があるすべてのローカル オブジェクトは、値渡しする必要があり 、[serializable] カスタム属性でマークする必要があります。または、 ISerializable インターフェイスを実装する必要があります。 オブジェクトがパラメーターとして渡されると、フレームワークはオブジェクトをシリアル化し、オブジェクトが再構築される宛先アプリケーション ドメインに転送します。 シリアル化できないローカル オブジェクトは、別のアプリケーション ドメインに渡すことができないため、削除できません。

任意のオブジェクトを MarshalByRefObject から派生させることで、リモート オブジェクトに変更できます。 クライアントは、リモート オブジェクトをアクティブにすると、リモート オブジェクトへのプロキシを受け取ります。 リモート処理インフラストラクチャが呼び出しを適切にインターセプトして転送できるようにするために、このプロキシに対するすべての操作は適切に間接処理されます。 この間接参照はパフォーマンスに影響を与えますが、JIT コンパイラと実行エンジン (EE) は、プロキシとリモート オブジェクトが同じアプリケーション ドメインに存在する場合の不要なパフォーマンスの低下を防ぐために最適化されています。 プロキシ オブジェクトとリモート オブジェクトが異なるアプリケーション ドメインにある場合、スタック上のすべてのメソッド呼び出しパラメーターがメッセージに変換され、リモート アプリケーション ドメインに転送されます。この場合、メッセージはスタック フレームに戻され、メソッド呼び出しが呼び出されます。 メソッド呼び出しから結果を返す場合にも、同じプロシージャが使用されます。

プロキシ オブジェクト

プロキシ オブジェクトは、クライアントがリモート オブジェクトをアクティブ化するときに作成されます。 プロキシ オブジェクトはリモート オブジェクトの代表として機能し、プロキシで行われたすべての呼び出しが正しいリモート オブジェクト インスタンスに確実に転送されるようにします。 プロキシ オブジェクトのしくみを正確に理解するには、それらを詳細に調べる必要があります。 クライアントがリモート オブジェクトをアクティブ化すると、フレームワークは、すべてのクラスの一覧とリモート オブジェクトのインターフェイス メソッドを含む TransparentProxy クラスのローカル インスタンスを作成します。 TransparentProxy クラスは作成時に CLR に登録されるため、プロキシ上のすべてのメソッド呼び出しはランタイムによってインターセプトされます。 ここでは、呼び出しがリモート オブジェクトの有効なメソッドであるかどうかを調べ、リモート オブジェクトのインスタンスがプロキシと同じアプリケーション ドメインに存在するかどうかを判断します。 これが true の場合、単純なメソッド呼び出しは実際のオブジェクトにルーティングされます。 オブジェクトが別のアプリケーション ドメインにある場合、スタック上の呼び出しパラメーターは IMessage オブジェクトにパッケージ化され、Invoke メソッドを呼び出すことによって RealProxy クラスに転送されます。 このクラス (または内部実装) は、メッセージをリモート オブジェクトに転送します。 TransparentProxy クラスと RealProxy クラスはどちらも、リモート オブジェクトがアクティブ化されるときに内部で作成されますが、TransparentProxy のみがクライアントに返されます。

これらのプロキシ オブジェクトについて理解を深めるためには、迂回して ObjRef を簡単にメンションする必要があります。 ObjRef の詳細な説明については、「アクティブ化」セクションを参照してください。 次のシナリオでは、 ObjRef と 2 つのプロキシ クラスの関連について簡単に説明します。 これはプロセスの非常に広範な説明であることに注意することが重要です。オブジェクトがクライアントとサーバーのどちらでアクティブ化されているか、および単一呼び出しオブジェクトであるかに応じて、いくつかのバリエーションが存在します。

  • リモート オブジェクトは、リモート コンピューター上のアプリケーション ドメインに登録されます。 オブジェクトはマーシャリングされ、 ObjRef が生成されます。 ObjRef には、ネットワーク上の任意の場所からリモート オブジェクトを見つけてアクセスするために必要なすべての情報が含まれています。 この情報には、クラスの厳密な名前、クラスの階層 (その親)、クラスが実装するすべてのインターフェイスの名前、オブジェクト URI、登録されているすべての使用可能なチャネルの詳細が含まれます。 リモート処理フレームワークは、オブジェクト URI を使用して、そのオブジェクトの要求を受信したときにリモート オブジェクト用に作成された ObjRef インスタンスを取得します。
  • クライアントは、CreateInstance などの新しいまたはいずれかの Activator 関数を呼び出して、リモート オブジェクトをアクティブ化します。 サーバーでアクティブ化されたオブジェクトの場合、リモート オブジェクトの TransparentProxy はクライアント アプリケーション ドメインで生成され、クライアントに返されます。リモート呼び出しはまったく行われません。 リモート オブジェクトは、クライアントがリモート オブジェクトのメソッドを呼び出すときにのみアクティブ化されます。 このシナリオは、クライアントがオブジェクトをアクティブ化するように求められたときにフレームワークがオブジェクトをアクティブ化することを想定しているため、クライアントによってアクティブ化されたオブジェクトでは明らかに機能しません。 クライアントがいずれかのアクティブ化メソッドを呼び出すと、クライアントにアクティブ化プロキシが作成され、URL とオブジェクト URI をエンドポイントとして使用してサーバー上のリモート アクティベーターに対してリモート呼び出しが開始されます。 リモート アクティベーターはオブジェクトをアクティブ化し、 ObjRef がクライアントにストリーミングされ、クライアントに返される TransparentProxy を生成するためにマーシャリングされません。
  • マーシャリング中に 、ObjRef が解析されてリモート オブジェクトのメソッド情報が抽出され、 TransparentProxy オブジェクトと RealProxy オブジェクトの両方が作成されます。 解析された ObjRef の内容は、CLR に登録される前に TransparentProxy の内部テーブルに追加されます。

TransparentProxy は、置き換えたり拡張したりできない内部クラスです。 一方、 RealProxy クラスと ObjRef クラスはパブリックであり、必要に応じて拡張およびカスタマイズできます。 RealProxy クラスは、たとえば、リモート オブジェクトのすべての関数呼び出しを処理するため、負荷分散を実行するための理想的な候補です。 Invoke が呼び出されると、RealProxy から派生したクラスは、ネットワーク上のサーバーに関する読み込み情報を取得し、呼び出しを適切なサーバーにルーティングできます。 チャネルから必要な ObjectURI の MessageSink を要求し、SyncProcessMessage または AsyncProcessMessage を呼び出して、呼び出しを必要なリモート オブジェクトに転送するだけです。 呼び出しが返されると、 RealProxy によって戻り値パラメーターが自動的に処理されます。

派生 RealProxy クラスの使用方法を示すコード スニペットを次に示します。

   MyRealProxy proxy = new MyRealProxy(typeof(Foo));
   Foo obj = (Foo)proxy.GetTransparentProxy();
   int result = obj.CallSomeMethod();

上記で取得した TransparentProxy は、別のアプリケーション ドメインに転送できます。 2 番目のクライアントがプロキシでメソッドを呼び出そうとすると、リモート処理フレームワークは MyRealProxy のインスタンスを作成しようとします。アセンブリが使用可能な場合は、すべての呼び出しがこのインスタンスを介してルーティングされます。 アセンブリが使用できない場合、呼び出しは既定のリモート処理 RealProxy 経由でルーティングされます。

ObjRef は、既定の ObjRef プロパティ TypeInfoEnvoyInfoChannelInfo に置き換えることで簡単にカスタマイズできます。 次のコードは、これを行う方法を示しています。

public class ObjRef {
  public virtual IRemotingTypeInfo TypeInfo 
  {
    get { return typeInfo;}
    set { typeInfo = value;}
  }

  public virtual IEnvoyInfo EnvoyInfo
  {
    get { return envoyInfo;}
    set { envoyInfo = value;}
  }

  public virtual IChannelInfo ChannelInfo 
  {
    get { return channelInfo;}
    set { channelInfo = value;}
  }
}

チャンネル

チャネルは、リモート オブジェクトとの間でメッセージを転送するために使用されます。 クライアントがリモート オブジェクトでメソッドを呼び出すと、パラメーターと呼び出しに関連するその他の詳細が、チャネルを介してリモート オブジェクトに転送されます。 呼び出しの結果はすべて、同じ方法でクライアントに返されます。 クライアントは、"サーバー" に登録されているチャネルのいずれかを選択してリモート オブジェクトと通信できるため、開発者はニーズに最も適したチャネルを自由に選択できます。 また、既存のチャネルをカスタマイズしたり、異なる通信プロトコルを使用する新しいチャネルを構築したりすることもできます。 チャネル選択は、次の規則に従います。

  • リモート オブジェクトを呼び出すには、少なくとも 1 つのチャネルをリモート処理フレームワークに登録する必要があります。 チャネルは、オブジェクトが登録される前に登録されている必要があります。
  • チャネルは、アプリケーション ドメインごとに登録されます。 1 つのプロセスで複数のアプリケーション ドメインを使用できます。 プロセスが終了すると、登録されているすべてのチャネルが自動的に破棄されます。
  • 同じポートでリッスンする同じチャネルを複数回登録することは無効です。 チャネルはアプリケーション ドメインごとに登録されますが、同じコンピューター上の異なるアプリケーション ドメインで、同じポートでリッスンしている同じチャネルを登録することはできません。 同じチャネルを 2 つの異なるポートでリッスンして登録できます。
  • クライアントは、登録されている任意のチャネルを使用して、リモート オブジェクトと通信できます。 リモート処理フレームワークを使用すると、クライアントがリモート オブジェクトに接続しようとしたときに、リモート オブジェクトが適切なチャネルに確実に接続されます。 クライアントは、リモート オブジェクトとの通信を試みる前に、ChannelService クラスで RegisterChannel を呼び出す必要があります。

すべてのチャネル は IChannel から派生し、チャネルの目的に応じて IChannelReceiver または IchannelSender を実装します。 ほとんどのチャネルでは、受信側インターフェイスと送信側インターフェイスの両方を実装して、どちらの方向でも通信できるようにします。 クライアントがプロキシでメソッドを呼び出すと、リモート処理フレームワークによって呼び出しがインターセプトされ、 RealProxy クラス (または RealProxy を実装するクラスのインスタンス) に転送されるメッセージに変更されます。 RealProxy は、処理のためにメッセージをチャネル シンク チェーンに転送します。

チェーン内のこの最初のシンクは、通常、メッセージをバイト ストリームにシリアル化するフォーマッタ シンクです。 その後、メッセージは、チェーンの末尾にあるトランスポート シンクに到達するまで、あるチャネル シンクから次のチャネル シンクに渡されます。 トランスポート シンクは、サーバー側でトランスポート シンクとの接続を確立し、バイト ストリームをサーバーに送信する役割を担います。 サーバー上のトランスポート シンクは、フォーマッタ シンクに到達するまで、サーバー側のシンク チェーンを介してバイト ストリームを転送します。その時点で、メッセージはディスパッチポイントからリモート オブジェクト自体に逆シリアル化されます。

リモート処理フレームワークの混乱を招く側面の 1 つは、リモート オブジェクトとチャネルの関係です。 たとえば、 SingleCall リモート オブジェクトは、呼び出しが到着したときにのみオブジェクトがアクティブ化された場合に、クライアントの接続をリッスンするためにどのように管理されますか?

マジックの一部は、リモート オブジェクトがチャネルを共有するという事実に依存しています。 リモート オブジェクトはチャネルを所有していません。 リモート オブジェクトをホストするサーバー アプリケーションでは、必要なチャネルと、リモート処理フレームワークで公開するオブジェクトを登録する必要があります。 チャネルは、登録されると、指定されたポートで自動的にクライアント要求の待機を開始します。 リモート オブジェクトが登録されると、オブジェクトに対して ObjRef が作成され、テーブルに格納されます。 要求がチャネルで受信されると、リモート処理フレームワークはメッセージを調べてターゲット オブジェクトを特定し、オブジェクト参照のテーブルをチェックして、テーブル内の参照を見つけます。 オブジェクト参照が見つかった場合、フレームワークターゲットオブジェクトはテーブルから取得されるか、必要に応じてアクティブ化され、フレームワークは呼び出しをオブジェクトに転送します。 同期呼び出しの場合、クライアントからの接続はメッセージ呼び出しの存続期間にわたって維持されます。 各クライアント接続はそれ自身のスレッドで処理されるため、1 つのチャネルで複数のクライアントを同時に処理できます。

セキュリティはビジネス アプリケーションを構築する際に重要な考慮事項であり、開発者は、ビジネス要件を満たすために、承認や暗号化などのセキュリティ機能をリモート メソッド呼び出しに追加できる必要があります。 このニーズに対応するために、チャネルをカスタマイズして、開発者がリモート オブジェクトとの間でメッセージの実際のトランスポート メカニズムを制御できるようにします。

HTTP チャネル

HTTP チャネルは、SOAP プロトコルを使用してリモート オブジェクトとの間でメッセージを転送します。 すべてのメッセージは SOAP フォーマッタを介して渡されます。この場合、メッセージは XML に変更されてシリアル化され、必要な SOAP ヘッダーがストリームに追加されます。 バイナリ フォーマッタを使用するように HTTP チャネルを構成することもできます。 結果のデータ ストリームは、HTTP プロトコルを使用してターゲット URI に転送されます。

TCP チャネル

TCP チャネルは、バイナリ フォーマッタを使用して、すべてのメッセージをバイナリ ストリームにシリアル化し、TCP プロトコルを使用してストリームをターゲット URI に転送します。 SOAP フォーマッタに TCP チャネルを構成することもできます。

アクティブ化

リモート処理フレームワークでは、リモート オブジェクトのサーバーとクライアントのアクティブ化がサポートされています。 サーバーのアクティブ化は、通常、メソッド呼び出し間の状態を維持するためにリモート オブジェクトが必要ない場合に使用されます。 また、複数のクライアントが同じオブジェクト インスタンスでメソッドを呼び出し、オブジェクトが関数呼び出し間で状態を維持する場合にも使用されます。 一方、クライアントでアクティブ化されたオブジェクトはクライアントからインスタンス化され、クライアントは、その目的のために提供されるリース ベースのシステムを使用して、リモート オブジェクトの有効期間を管理します。

クライアントがリモート 処理フレームワークにアクセスするには、すべてのリモート オブジェクトをリモート処理フレームワークに登録する必要があります。 オブジェクトの登録は通常、起動し、1 つ以上のチャネルを ChannelServices に登録し、1 つ以上のリモート オブジェクト を RemotingConfiguration に登録し、終了するまで待機するホスティング アプリケーションによって行われます。 登録されたチャネルとオブジェクトは、登録されたプロセスが有効な間にのみ使用できます。 プロセスが終了すると、このプロセスによって登録されたすべてのチャネルとオブジェクトが、登録されたリモート処理サービスから自動的に削除されます。 リモート オブジェクトをフレームワークに登録する場合は、次の 4 つの情報が必要です。

  1. クラスが格納されているアセンブリ名。
  2. リモート オブジェクトの型名。
  3. クライアントがオブジェクトの検索に使用するオブジェクト URI。
  4. サーバーのアクティブ化に必要なオブジェクト モード。 SingleCall または Singleton指定できます。

リモート オブジェクトを登録するには、 RegisterWellKnownServiceType を呼び出し、上記の情報をパラメーターとして渡すか、上記の情報を構成ファイルに格納してから Configure を呼び出して、構成ファイルの名前をパラメーターとして渡します。 これら 2 つの関数のいずれかを使用して、リモート オブジェクトがまったく同じ関数を実行するように登録できます。 後者は、ホスト アプリケーションを再コンパイルせずに構成ファイルの内容を変更できるため、使用する方が便利です。 次のコード スニペットは、 HelloService クラスを SingleCall リモート オブジェクトとして登録する方法を示しています。

RemotingConfiguration.RegisterWellKnownServiceType(
  Type.GetType("RemotingSamples.HelloServer,object"), 
  "SayHello", 
  WellKnownObjectMode.SingleCall);

RemotingSamples が名前空間である場合、HelloServer はクラスの名前であり、Object.dllはアセンブリの名前です。 SayHello は、サービスが公開されるオブジェクト URI です。 オブジェクト URI には直接ホスト用の任意のテキスト文字列を指定できますが、サービスが IIS でホストされる場合は、.rem または .soap 拡張子が必要です。 したがって、これらの拡張機能は、すべてのリモート処理エンドポイント (URI) に対して推奨されます。

オブジェクトが登録されると、フレームワークはこのリモート オブジェクトのオブジェクト参照を作成し、アセンブリからオブジェクトに関する必要なメタデータを抽出します。 この情報は、URI とアセンブリ名と共に、登録されたリモート オブジェクトの追跡に使用されるリモート処理フレームワーク テーブルに提出されるオブジェクト参照に格納されます。 リモート オブジェクト自体は登録プロセスによってインスタンス化されないことに注意してください。 これは、クライアントが オブジェクトのメソッドを呼び出そうとしたとき、またはクライアント側からオブジェクトをアクティブ化した場合にのみ発生します。

このオブジェクトの URI を認識しているクライアントは、 ChannelServices に優先するチャネルを登録し、 newGetObject、または CreateInstance を呼び出してオブジェクトをアクティブ化することで、このオブジェクトのプロキシを取得できるようになりました。 この方法の例を次のコード スニペットに示します。

""      ChannelServices.RegisterChannel(new TcpChannel());
      HelloServer obj =  (HelloServer)Activator.GetObject(
        typeof(RemotingSamples.HelloServer), 
        "tcp://localhost:8085/SayHello");

ここでは "tcp://localhost:8085/SayHello" 、ポート 8085 の TCP を使用して SayHello エンドポイントのリモート オブジェクトに接続することを指定します。 このクライアント コードがコンパイルされるときに、コンパイラには 明らかに HelloServer クラスに関する型情報が必要です。 この情報は、次のいずれかの方法で提供できます。

  • HelloService クラスが格納されているアセンブリへの参照を指定します。
  • リモート オブジェクトを実装およびインターフェイス クラスに分割し、クライアントのコンパイル時にインターフェイスを参照として使用します。
  • SOAPSUDS ツールを使用して、必要なメタデータをエンドポイントから直接抽出します。 このツールは、指定されたエンドポイントに接続し、メタデータを抽出し、クライアントのコンパイルに使用できるアセンブリまたはソース コードを生成します。

GetObject または new は、サーバーのアクティブ化に使用できます。 これらの呼び出しのいずれかが行われると、リモート オブジェクトがインスタンス化されないことに注意することが重要です。 実際のところ、ネットワーク呼び出しはまったく生成されません。 フレームワークは、リモート オブジェクトにまったく接続せずにプロキシを作成するのに十分な情報をメタデータから取得します。 ネットワーク接続は、クライアントがプロキシで メソッドを呼び出すときにのみ確立されます。 呼び出しがサーバーに到着すると、フレームワークはメッセージから URI を抽出し、リモート処理フレームワーク テーブルを調べて URI に一致するオブジェクトの参照を見つけ、必要に応じて オブジェクトをインスタンス化し、メソッド呼び出しを オブジェクトに転送します。 オブジェクトが SingleCall として登録されている場合は、メソッド呼び出しが完了した後に破棄されます。 呼び出されるメソッドごとに、 オブジェクトの新しいインスタンスが作成されます。 GetObjectnew の唯一の違いは、前者では URL をパラメーターとして指定できる点です。ここで、後者は構成から URL を取得します。

CreateInstance または new は、クライアントでアクティブ化されたオブジェクトに使用できます。 どちらも、パラメーターを持つコンストラクターを使用してオブジェクトをインスタンス化できます。 クライアントがクライアントでアクティブ化されたオブジェクトをアクティブ化しようとすると、アクティブ化要求がサーバーに送信されます。 クライアントでアクティブ化されたオブジェクトの有効期間は、リモート処理フレームワークによって提供されるリース サービスによって制御されます。 オブジェクト リースについては、次のセクションで説明します。

リースを使用したオブジェクトの有効期間

各アプリケーション ドメインには、そのドメイン内のリースの管理を担当するリース マネージャーが含まれています。 すべてのリースは、リース時間の期限切れについて定期的に調べられます。 リースの有効期限が切れている場合は、リースのスポンサーの 1 つ以上が呼び出され、リースを更新する機会が与えられます。 スポンサーがリースの更新を決定しなかった場合、リース マネージャーはリースを削除し、オブジェクトはガベージ コレクションされます。 リース マネージャーは、リースを残りのリース時間で並べ替えたリース一覧を保持します。 残り時間が最も短いリースは、一覧の先頭に格納されます。

リースは ILease インターフェイスを実装し、更新するポリシーとメソッドを決定するプロパティのコレクションを格納します。 リースは通話時に更新できます。 リモート オブジェクトでメソッドが呼び出されるたびに、リース時間は現在の LeaseTimeRenewOnCallTime の最大値に設定されます。 LeaseTime が経過すると、スポンサーはリースの更新を求められます。 信頼性の低いネットワークは随時処理する必要があるため、リース スポンサーが利用できない状況が発生する可能性があり、サーバー上のゾンビ オブジェクトをサーバーに残さないようにするには、各リースに スポンサーシップタイムアウトがあります。 この値は、スポンサーがリースを終了するまでの応答を待機する時間を指定します。 SponsorshipTimeout が null の場合、CurrentLeaseTime を使用してリースの有効期限が切れるタイミングが決定されます。 CurrentLeaseTime の値が 0 の場合、リースは期限切れになりません。 構成または API を使用して、 InitialLeaseTimeSponsorshipTimeoutおよび RenewOnCallTime の既定値をオーバーライドできます。

リース マネージャーは、スポンサーシップ時間を短縮するために格納されているスポンサーの一覧 ( ISponsor インターフェイスを実装) を保持します。 リースの時間を更新するためにスポンサーが必要な場合は、リストの上部にある 1 人以上のスポンサーに時間の更新を求められます。 リストの上部は、以前に最大のリース更新時間を要求したスポンサーを表します。 スポンサーが SponsorTimeOut 期間に応答しない場合は、一覧から削除されます。 オブジェクトのリースを取得するには、 GetLifetimeService を呼び出し、リースが必要なオブジェクトをパラメーターとして渡します。 この呼び出しは、 RemotingServices クラスの静的メソッドです。 オブジェクトがアプリケーション ドメインに対してローカルである場合、この呼び出しのパラメーターは オブジェクトへのローカル参照であり、返されるリースはリースへのローカル参照です。 オブジェクトがリモートの場合、プロキシはパラメーターとして渡され、リースの透過的なプロキシが呼び出し元に返されます。

オブジェクトは独自のリースを提供し、それによって独自の有効期間を制御できます。 これを行うには、MarshalByRefObjectInitializeLifetimeService メソッドを次のようにオーバーライドします。

public class Foo : MarshalByRefObject {
  public override Object InitializeLifetimeService()
  {
    ILease lease = (ILease)base.InitializeLifetimeService();
    if (lease.CurrentState == LeaseState.Initial)  {
      lease.InitialLeaseTime = TimeSpan.FromMinutes(1);
      lease.SponsorshipTimeout = TimeSpan.FromMinutes(2);
      lease.RenewOnCallTime = TimeSpan.FromSeconds(2);
    }
    return lease;
  }
}

リース プロパティは、リースが初期状態の場合にのみ変更できます。 InitializeLifetimeService の実装では、通常、基本クラスの対応するメソッドを呼び出して、リモート オブジェクトの既存のリースを取得します。 オブジェクトがマーシャリングされたことがない場合、返されるリースは初期状態になり、リース プロパティを設定できます。 オブジェクトがマーシャリングされると、リースは最初からアクティブな状態に移動し、リース プロパティを初期化しようとすると無視されます (例外がスローされます)。 InitializeLifetimeService は、リモート オブジェクトがアクティブ化されるときに呼び出されます。 アクティベーション呼び出しでリースのスポンサー一覧が提供され、リースがアクティブである間にいつでもスポンサーを追加できます。

リース時間は、次のように延長できます。

  • クライアントは、Lease クラスで Renew メソッドを呼び出すことができます。
  • リースはスポンサーに 更新 を要求できます。
  • クライアントが オブジェクトのメソッドを呼び出すと、 リースは RenewOnCall 値によって自動的に更新されます。

リースの有効期限が切れると、その内部状態が [アクティブ] から [有効期限切れ] に変わり、スポンサーに対するそれ以上の呼び出しは行われず、オブジェクトはガベージ コレクションされます。 スポンサーが Web 上またはファイアウォールの内側に展開されている場合、リモート オブジェクトがスポンサーに対してコールバックを実行することは多くの場合困難であるため、スポンサーはクライアントと同じ場所に配置する必要はありません。 リモート オブジェクトによって到達可能なネットワークの任意の部分に存在できます。

リースを使用してリモート オブジェクトの有効期間を管理することは、参照カウントの代替アプローチであり、信頼性の低いネットワーク接続よりも複雑で非効率的になる傾向があります。 リモート オブジェクトの有効期間は必要以上に長く延長されると主張できますが、参照カウントと ping クライアントに専念するネットワーク トラフィックの減少により、リースは非常に魅力的なソリューションになります。

まとめ

ビジネス アプリケーションの大部分のニーズを満たす完璧なリモート処理フレームワークを提供することは、不可能ではないにしても困難です。 必要に応じて拡張およびカスタマイズできるフレームワークを提供することで、Microsoft は正しい方向に重要な手順を実行しました。

付録 A: TCP チャネルを使用したリモート処理のサンプル

この付録では、単純な "Hello World" リモート アプリケーションを記述する方法を示します。 クライアントは、文字列に "Hi There" という単語を追加し、結果をクライアントに返す文字列をリモート オブジェクトに渡します。 TCP ではなく HTTP を使用するようにこのサンプルを変更するには、ソース ファイルの TCP を HTTP に置き換えます。

このコードを server.cs として保存します。

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;

namespace RemotingSamples {
  public class Sample {

    public static int Main(string [] args) {

      TcpChannel chan = new TcpChannel(8085);
      ChannelServices.RegisterChannel(chan);
      RemotingConfiguration.RegisterWellKnownServiceType
      (Type.GetType("RemotingSamples.HelloServer,object"), 
      "SayHello", WellKnownObjectMode.SingleCall);
      System.Console.WriteLine("Hit <enter> to exit...");
      System.Console.ReadLine();
      return 0;
    }
  }
}

このコードを client.cs として保存します。

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;

namespace RemotingSamples {
  public class Client
  {
    public static int Main(string [] args)
    {
      TcpChannel chan = new TcpChannel();
      ChannelServices.RegisterChannel(chan);
      HelloServer obj = 
   (HelloServer)Activator.GetObject(typeof(RemotingSamples.HelloServer)
   , "tcp://localhost:8085/SayHello");
      if (obj == null) 
      System.Console.WriteLine("Could not locate server");
      else Console.WriteLine(obj.HelloMethod("Caveman"));
      return 0;
    } 
  }
}

このコードを object.cs として保存します。

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;

namespace RemotingSamples {
  public class HelloServer : MarshalByRefObject {

    public HelloServer() {
      Console.WriteLine("HelloServer activated");
    }

    public String HelloMethod(String name) {
      Console.WriteLine("Hello.HelloMethod : {0}", name);
      return "Hi there " + name;
    }
  }
}

メイクファイルを次に示します。

all: object.dll server.exe client.exe

object.dll: share.cs
   csc /debug+ /target:library /out:object.dll object.cs

server.exe: server.cs
   csc /debug+ /r:object.dll /r:System.Runtime.Remoting.dll server.cs

client.exe: client.cs server.exe
   csc /debug+ /r:object.dll /r:server.exe 
   /r:System.Runtime.Remoting.dll client.cs