はじめに
追跡サービス(Tracking Service)とは、その名前から想像されるように、オブジェクトのライフサイクルに関係するさまざまな活動を追跡するものです。このサービスは.NET Remotingフレームワークの一部なので、.NET Remotingの概念を復習しておきましょう。
.NET Remoting
Microsoftの.NETフレームワークは、アプリケーションドメイン内のクライアントから別のアプリケーションドメイン内のオブジェクトにアクセスする仕組みを提供しています。これを可能にするのが.NET Remotingです。
.NET Remotingは非常に柔軟性の高いテクノロジであり、クライアントとサーバーがどこにあっても相互に通信できるようにします。クライアントとサーバーの場所は、同じコンピュータ上でも、同じネットワーク上の別のコンピュータでも、海を隔てた別の国のコンピュータでもかまいません。.NET Remotingのサービスやインフラストラクチャコンポーネントを利用することにより、アプリケーション開発者は複雑な要素を意識せずに済みます。
.NET Remotingでは各種のプロトコルやシリアル化形式、設定情報を使用できます。また.NET Remotingパイプライン内に独自の「フック」を設ければ、リモートフレームワークの既定の動作をカスタマイズすることもできます(追跡サービスは、こうしたフックの一例です)。
では.NET Remotingの仕組みはどうなっているのでしょうか。.NET Remotingの概念の一部は、従来の分散テクノロジ(DCOM、CORBAなど)から借りてきたものです。まず明確にしておきたいのは、.NET Remotingでは、オブジェクトは何らかの「リスナプロセス」を通じて外部に公開されるという点です。このリスナプロセスはコンソールアプリケーションかもしれませんし、WinFormアプリケーションやWindowsサービス、あるいはIISということもあります。リスナプロセスは、プログラムまたは設定ファイルを通じて公開されるオブジェクトを指定します。オブジェクトが公開されている場合、クライアントアプリケーションはリスナプロセスに接続してオブジェクトの生成を要求し、リスナプロセスは生成したオブジェクトをクライアントに返します。クライアントとサーバーのアプリケーションドメイン間の通信は、エンコードされたデータストリームをチャネル上で送受信することによって行われます。
チャネル
チャネルの基本的な目的は、データをある場所から別の場所へ転送することです。.NETには既定で2つのチャネルが用意されています。1つはHTTPチャネル(System.Runtime.Remoting.Channels.Http)で、もう1つはTCPチャネル(System.Runtime.Remoting.Channels.Tcp)です。
TCPチャネルはHTTPチャネルよりも高速で、バイナリデータの転送に向いています。一方、HTTPチャネルはファイアウォールやインターネットを経由するときに適しています。使用できるのは、これらのチャネルに限りません。独自のチャネルを作成して.NET Remotingに組み込むこともできます。
フォーマッタ
フォーマッタとは、データやメッセージの体裁を送信用に整えるものです。フォーマットされたデータ/メッセージは、所定のチャネルを用いて他のアプリケーションドメインに転送されます。.NETには既定で2つのフォーマッタが用意されています。1つはSoapフォーマッタ(System.Runtime.Serialization.Formatters.Soap)で、もう1つはBinaryフォーマッタ(System.Runtime.Serialization.Formatters.Binary)です。
SoapフォーマッタはメッセージをSoap 1.1仕様に基づいてフォーマットし、Binaryフォーマッタはメッセージを純粋なバイナリ形式でフォーマットします。
リモート処理オブジェクトの種類
.NET Remotingは2種類のオブジェクトをサポートしています。1つはサーバーアクティブ化オブジェクト(「既知のオブジェクト」とも呼ばれます)で、もう1つはクライアントアクティブ化オブジェクトです。
サーバーアクティブ化オブジェクト
サーバーアクティブ化オブジェクトは、その名が示すとおり、サーバーによって生成され、その有効期間もサーバーによって管理されます。ここでポイントとなるのは、クライアントがNewまたはActivator.GetObjectを呼び出した時点では、これらのオブジェクトは生成されないことです。オブジェクトの実際のインスタンスが生成されるのは、クライアントがプロキシ上の特定のメソッドを実際に起動したときです。
この動作から1つ導かれることがあります。クライアントがNewまたはActivator.GetObjectメソッドを呼び出した時点でオブジェクトは生成されないので、サーバーアクティブ化オブジェクトでは、既定以外のコンストラクタは使えません。これらのオブジェクトでは、既定のコンストラクタ(パラメータを持たないコンストラクタ)だけがサポートされます。
サーバーアクティブ化オブジェクトは、2つのモードでアクティブにできます。
- Singleton ―― サーバー上にただ1つのオブジェクトが生成され、それがすべてのクライアントの要求を満たします。つまり、オブジェクトは共有され、オブジェクトの状態をすべてのクライアントが共有することになります。
- SingleCall ―― メソッド呼び出しごとにオブジェクトが生成され、オブジェクトがクライアント間で共有されることはありません。メソッド呼び出しが終了するたびにオブジェクトが破棄されるので、オブジェクトの状態は維持されません。
クライアントアクティブ化オブジェクト
クライアントアクティブ化オブジェクトは、サーバーによって生成され、その有効期間はクライアントによって管理されます。サーバーアクティブ化オブジェクトとは対照的に、クライアントアクティブ化オブジェクトは、クライアントがNewまたはその他のオブジェクト生成メソッドを呼び出したとき、直ちに生成されます。そのため、クライアントアクティブ化オブジェクトでは、既定のコンストラクタと既定以外のコンストラクタを共に使うことができます。クライアントアクティブ化オブジェクトは特定クライアントに限定され、クライアント間で共有されることはありません。オブジェクトのインスタンスはリース期限が切れるか、クライアントがオブジェクトを破棄するまで存続します。
設定
他の.NETテクノロジと同様、.NET Remotingも、その動作のために設定情報が必要です。リモート処理アプリケーションは、プログラムまたは.configファイルを通じて設定することができます。本稿では、.configファイルについて説明します。
.NET Remotingの設定情報は、次のXML形式の<application>タグ内にまとめて指定されます。
<configuration>
<System.Runtime.Remoting>
<application>
</application>
</System.Runtime.Remoting>
</configuration>
サーバーアクティブ化オブジェクトを公開する場合は、次の例のような<service>タグを使います。
<configuration>
<system.runtime.remoting>
<application>
<service>
<wellknown mode="SingleCall" type="MyObjects.Customer, MyObjects"
objectUri="Customer"/>
</service>
<channels>
<channel ref="tcp" port="1234"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
この設定情報ではSingleCallオブジェクト(完全型名MyObjects.Customer)を公開しています。このクラスは「MyObjects.DLL」アセンブリに入っており、オブジェクトのURIはCustomerです。ポート1234のTCPチャネルを通じて、このオブジェクトにアクセスできます。
個々のサーバーアクティブ化オブジェクトについて、固有の<wellknown>タグが必要になります。
クライアント側の対応する.configファイルは次のようになります。
<configuration>
<system.runtime.remoting>
<application name="MyClient">
<client>
<wellknown type="MyObjects.Customer,MyObjects"
url="tcp://localhost:1234/Customer"/>
</client>
<channels>
<channel ref="tcp"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
クライアント側の設定ファイルは、サーバー側の設定ファイルと若干異なります。ここでは、<service>タグの代わりに<client>タグを使用しています。また、クライアント側の<wellknown>タグでurl属性を使用して、サーバーオブジェクトの正確な位置を指定しています。
クライアントアクティブ化オブジェクトの場合は、<wellknown>タグの代わりに<activated>タグをサーバーとクライアントの両方で使用します。次の例は、クライアントアクティブ化オブジェクトの公開方法を示しています。
<system.runtime.remoting>
<application name="Data">
<service>
<activated type="MyObjects.Customer,MyObjects"/>
</service>
<channels>
<channel ref="tcp" port="1234"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
クライアント側の対応する設定ファイルは次のようになります。
<configuration>
<system.runtime.remoting>
<application name="Data">
<client url="tcp://localhost:1234/Data">
<!-- You can only use one url @ a time -->
<activated type="MyObjects.Customer,MyObjects"/>
</client>
<channels>
<channel ref="tcp"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
.NET追跡サービス
.NET追跡サービスでは、.NET Remotingインフラストラクチャに独自のコードを組み込むことで、オブジェクトのマーシャリングに関する通知を受け取ることができます。例えば、.NET追跡サービスは、オブジェクトが別のアプリケーションドメインにマーシャリングされたときなどにイベントを発生させます。.NET追跡サービスが発生させるイベントの中には、MBR(Marshal-by-Ref)オブジェクトのマーシャリングプロセスに関係するイベントがあります。これらのイベントに関与するコンポーネントは、.NET Remotingインフラストラクチャに登録することでイベントを受け取ることができます。
.NET追跡サービスを利用すると、動作中のアプリケーションを自由に監視し、アプリケーションパターンを分析して、一定の負荷がかかったときの挙動やピーク時のオブジェクト数などを調べることができます。これらのイベントによって、.NET追跡サービスから有用な多くの情報が得られます。この情報をログに記録して、傾向分析やその他の調査に使うことができます。
.NET追跡サービスの唯一の制約は、「クライアントアクティブ化オブジェクトでのみ動作する」ことです。
.NETの他のプラグ可能アーキテクチャと同様、.NET追跡サービスを利用するコンポーネントの作成はとても簡単です。必要な作業は次の2ステップだけです。
ItrackingHandlerインターフェイスを実装する.NETクラスを作成する(このクラスを追跡ハンドラと呼ぶことにします)。
TrackingServicesクラスを使用して、このクラスを.NET Remotingインフラストラクチャに登録する。
以下、詳しく説明します。
ITrackingHandlerインターフェイス
ItrackingHandlerインターフェイスはSystem.Runtime.Remoting.Services名前空間に含まれます。このインターフェイスを実装するクラスを使えば、オブジェクトのマーシャリングイベントを受け取ることができます。このインターフェイスには次のメソッドがあります。
| メソッド | 説明 |
MarshaledObject | マーシャリング対象オブジェクトを追跡ハンドラに知らせる。 |
UnmarshaledObject | マーシャリング解除対象オブジェクトを追跡ハンドラに知らせる。 |
DisconnectedObject | 切断対象オブジェクト(リース期限の切れたオブジェクトなど)を追跡ハンドラに知らせる。 |
このインターフェイスをクラス内で実装するときは次のようにします。
Public class MyTrackingHandler : ITrackingHandler
{
public void MarshaledObject(object obj, ObjRef or)
{
// Write your code here; most probably you would log the
// information
}
public void UnmarshaledObject(object obj, ObjRef or)
{
// Write your code here; most probably you would log the
// information
}
public void DisconnectedObject(object obj)
{
// Write your code here; most probably you would log the
// information
}
}
このコードを見ると、メソッドMarshaledObjectとUnmarshaledObjectが共に2つのパラメータ(object、ObjRef)を取るのに対し、メソッドDisconnectedObjectはパラメータを1つ(object)しか取らないことが分かります。objectパラメータは、操作(マーシャリング、マーシャリング解除、切断)の対象となるオブジェクトの参照です。ObjRefパラメータは、クライアントアプリケーションでのプロキシ生成に必要なすべての情報が入っているオブジェクトの参照です。ObjRefの参照先オブジェクトには、サーバーオブジェクトの型、サーバー上のオブジェクト位置など、プロキシに必須の接続・転送関連の情報が含まれています。
TrackingServicesヘルパークラス
TrackingServicesは、System.Runtime.Remoting.Services名前空間に含まれるヘルパークラスです。このクラスを使用して追跡ハンドラを操作します。以下に、このクラスの主なメソッドを示します。
| メソッド | 説明 |
RegisterTrackingHandler | オブジェクトマーシャリングイベント対象ハンドラの一覧に追跡ハンドラを追加する。 |
UnregisterTrackingHandler | オブジェクトマーシャリングイベント対象ハンドラの一覧から追跡ハンドラを削除する。 |
RegisteredHandlers | ITrackingHandler型配列(現在登録されている追跡ハンドラの参照が入っている)を返す。 |
次のコードは、.NET Remotingインフラストラクチャに独自の追跡ハンドラを登録する方法を示しています。
MyTrackingHandler objHandler = new MyTrackingHandler() ;
TrackingServices.RegisterTrackingHandler(objHandler) ;
ハンドラを登録すると、イベントが送られてくるようになります。イベントの受信を止めるには、次のようにUnregisterTrackingHandlerメソッドを呼び出す必要があります(objHandlerが登録済みの追跡ハンドラを指しているものと仮定)。
TrackingServices.UnregisterTrackingHandler(objHandler) ;
登録済みの追跡ハンドラを列挙するのも簡単です。次のコードは、現在登録されている追跡ハンドラの一覧を取得し、すべてのハンドラをスキャンします。
ITrackingHandler[] objHandlers = TrackingServices.RegisteredHandlers ;
foreach (ITrackingHandler objHandler in objHandlers)
{
// Use objHandler here
}
実用的なことをやってみよう
カスタム追跡ハンドラの作成に必要な基礎を学んだところで、次に実際のハンドラを書いてみましょう。
ここで作成するカスタム追跡ハンドラは、すべての情報をSQL Serverデータベースに記録します。これにより、データを分析し、アプリケーションのオブジェクト生成とマーシャリングを監視することができます。このデータに基づいて分析される項目は次のようなものです。
- 一定期間中に操作(マーシャリング、マーシャリング解除、切断)の対象となったオブジェクトの数
- 操作(マーシャリング、マーシャリング解除、切断)の対象となった各オブジェクトの状態
- オブジェクトの生成に使われたURI
サンプルコードに含まれるプロジェクトは次のとおりです。
- MyObjects
- TrackingHandlers
- TrackingListener
- TrackingClient
上記のプロジェクトの他に、「database.sql」ファイルが存在します。このファイルには、SQL Serverデータベース、データベーステーブル、およびストアドプロシージャを作成するSQLスクリプトが含まれています。
MyObjects.DLL
MyObjectsアセンブリにはCustomerというクラスが入っており、これが「TrackingListener.exe」でリモート処理クライアントに公開されます。
Customerは、既定のToStringメソッドをオーバーライドした簡単なC#クラスです。これでオブジェクトの内部状態を取得できます。オブジェクトの状態を取得するには、[serializable]属性を用いてシリアル化可能とマークするか、Iserializableインターフェイスを実装する方法も考えられますが、ここではToStringメソッドを使うことにしました。
以下は、Customerクラスのコードです。
public class Customer : MarshalByRefObject
{
private string mstrName ;
private string mstrAddress ;
private string mstrPhone ;
private DateTime mdtDOB ;
public Customer()
{
//
// TODO: Add constructor logic here
//
mstrName = "Mansoor Siddiqui" ;
mstrAddress = "9999 Street, Apt 9, City State, USA" ;
mstrPhone = "999-999-9999" ;
mdtDOB = DateTime.Parse("01/01/1980") ;
}
public override string ToString()
{
string strState ;
strState = Name + "|" + Address + "|" + Phone + "|" +
DOB.ToLongDateString() ;
return strState;
}
// Rest of the code has been skipped due to length
}
TrackingHandlers.DLL
TrackingHandlersアセンブリには、MyTrackingHandlerというカスタム追跡ハンドラが入っています。前述のように、クラスを追跡ハンドラにするためにはItrackingHandlerインターフェイスを実装する必要があります。
以下は、このカスタム追跡ハンドラのコードです。
public class MyTrackingHandler : ITrackingHandler
{
private Database mobjDatabase ;
private string mstrConnectionString ;
public MyTrackingHandler()
{
//
// TODO: Add constructor logic here
//
mobjDatabase = new Database() ;
mstrConnectionString = "Data Source=localhost;uid=sa;pwd=;Initial
Catalog=TrackingDB" ;
}
public void MarshaledObject(object obj, ObjRef or)
{
object[] arrobjParameters = new Object[7] ;
arrobjParameters[0] = "M" ;
FillParameters(arrobjParameters, obj, or) ;
mobjDatabase.ExecuteCommand(mstrConnectionString,
"usp_Add_TrackingLog", arrobjParameters) ;
}
public void DisconnectedObject(object obj)
{
object[] arrobjParameters = new Object[7] ;
arrobjParameters[0] = "D" ;
FillParameters(arrobjParameters, obj, null) ;
mobjDatabase.ExecuteCommand(mstrConnectionString,
"usp_Add_TrackingLog", arrobjParameters) ;
}
public void UnmarshaledObject(object obj, ObjRef or)
{
object[] arrobjParameters = new Object[7] ;
arrobjParameters[0] = "U" ;
FillParameters(arrobjParameters, obj, or) ;
mobjDatabase.ExecuteCommand(mstrConnectionString,
"usp_Add_TrackingLog", arrobjParameters);
}
private void FillParameters(object[] r_objArray, object obj,
ObjRef or)
{
r_objArray[1] = DateTime.Now ;
r_objArray[2] = obj.ToString() ;
r_objArray[3] = obj.GetHashCode() ;
if (or != null)
{
if (or.TypeInfo != null)
{
r_objArray[4] = or.TypeInfo.ToString() ;
r_objArray[5] = or.TypeInfo.TypeName ;
}
else
{
r_objArray[4] = "" ;
r_objArray[5] = "" ;
}
if (or.URI != null)
r_objArray[6] = or.URI.ToString() ;
else
r_objArray[6] = "" ;
}
else
{
r_objArray[4] = "" ;
r_objArray[5] = "" ;
r_objArray[6] = "" ;
}
}
MarshaledObject、UnmarshaledObject、DisconnectedObjectの3つのメソッドのコードによって情報がデータベースに保存されます。
TrackingListener.exe
「TrackingListener.exe」はサーバー側のリスナです。これが.NET Remotingパイプライン内に追跡ハンドラを作成してインストールし、オブジェクト生成要求を満たします。
以下は、TrackingListenerクラスのコードです。
class TrackingListener
{
[STAThread]
static void Main(string[] args)
{
RemotingConfiguration.Configure("TrackingListener.exe.config") ;
TrackingServices.RegisterTrackingHandler(new
TrackingHandlers.MyTrackingHandler()) ;
Console.WriteLine("Press enter to terminate ...") ;
Console.ReadLine() ;
}
}
このコードはとても簡単です。RemotingConfiguration.Configureメソッドは、.configファイルを使用するアプリケーションから呼び出されます。このメソッドを呼び出すと、アプリケーションは設定ファイルに指定された情報に基づいて設定されます。
リスナプロセスの設定情報は「TrackingListener.exe.config」ファイルに指定されています。以下は.configファイルの抜粋です。
<configuration>
<system.runtime.remoting>
<application name="TrackingTest">
<service>
<activated type="MyObjects.Customer, MyObjects"/>
</service>
<channels>
<channel ref="tcp" port="9000"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
クライアントアクティブ化オブジェクトが公開され、ポート9000のTCPチャネルを通じてこのオブジェクトにアクセスできることが分かります。
TrackingClient.exe
「TrackingClient.exe」は、サーバー上で活動を起こすためにCustomerクラスのインスタンスを生成するだけのWinFormアプリケーションです。
追跡ハンドラのテスト
追跡ハンドラをテストするには、以下の作業を行う必要があります。
まず、データベーススクリプトを実行してデータベースと関連データベースオブジェクトを作成します。本稿では、SQL Serverとリスナが同じコンピュータ上で動作すると仮定していますが、SQL Serverが別のコンピュータで動いている場合は、追跡ハンドラクラス内の接続文字列を変更する必要があります。
もっとよい手として、接続文字列をリスナプロセスの.configファイルに入れておき、リスナからコンストラクタ経由でハンドラに渡すという方法も考えられます。
サンプルコードの解凍先ディレクトリからリスナプロセス「TrackingListener.exe」を実行します。リスナプロセスによって所定のチャネルが登録され、コンソールウィンドウが開きます。これでCustomerオブジェクトを生成する準備ができました。
「TrackingClient.exe」を起動し、ダイアログボックスの[Create object]ボタンをクリックします。これでCustomerインスタンスが生成され、.NET Remotingインフラストラクチャから追跡ハンドラのMarshaledObjectメソッドが呼び出されます。オブジェクトの生成に成功したら、データベースのTrackingLogテーブルを調べます。筆者のコンピュータでは、データベーステーブルの内容は次のようになりました。
切断イベントをテストするため、5分ほど待ちます(クライアントアクティブ化オブジェクトの場合は5分が既定のリース期限なので、それを経過するとプロキシからオブジェクトが切断されます)。すると、筆者のコンピュータでは、データベースの内容が次のようになりました。
なお、このデータベーステーブルの「activity」列には「M」、「U」、「D」という値が格納されます。これらの値は、発生した活動(M=マーシャリング、U=マーシャリング解除、D=切断)を表します。
結論
.NETフレームワークとその関連テクノロジを利用すると、処理パイプライン内に独自のフックを設けることができます。.NET追跡ハンドラは、そうしたフックの例です。本稿で見たように、追跡ハンドラを書くのはとても簡単ですが、.NET Remoting、各種オブジェクト、設定ファイル、その他リモート処理要素についての基礎的な知識が必要です。