.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、各種オブジェクト、設定ファイル、その他リモート処理要素についての基礎的な知識が必要です。
