Last-modified: Tue, 19 Mar 2013 11:05:11 JST
Counter:7351 Today:1 Yesterday:0 Online:8
このエントリーをはてなブックマークに追加

非同期名前付きパイプの基礎 / クライアントSide

About

非同期操作を行う名前付きパイプについて解説します。このページの上のページが関連する項目の先頭です。サンプルプログラムも項目の先頭に公開されます。ここではクライアント側の実装について解説しています。項目の順はサーバ側が先となっているので、サーバ側の資料を未読の場合には、先にサーバ側から読まれることを推奨します。

初期化と接続、受信待機の開始

先にサンプルコードを示して後に続いて解説することにします。

        //名前付きパイプのクライアントです.
        static NamedPipeClientStream client;

        //クライアントを初期化します.
        //サーバ名,パイプの名前,パイプの方向,(非)同期接続
        //サーバ名はローカルの時"."を指定します.
        client = new NamedPipeClientStream
            (".", "testpipe", PipeDirection.In, PipeOptions.Asynchronous);

        //接続を試みます.
        //接続に関しては、非同期の枠組みが用意されていません.
        client.Connect(3000);

        //接続したら非同期の受信待機を開始します.
        if (client.IsConnected)
        {
            BeginRead(new SampleState());
        }

クライアントの初期化

クライアントの初期化はサーバほど複雑ではありません。コンストラクタの引数には[サーバ名、パイプ名、パイプの方向、同期であるか非同期であるか]を設定します。ここではコンセプトの通り非同期通信を行うように設定しています。

パイプ名やパイプの方向設定についてはサーバと同じですが、サーバ名だけ注意する必要があります。名前付きパイプはネットワークを介して利用することもできるため、その対象となるサーバ名を設定する値が用意されています。ここではローカル、つまり1台のPCないで動作するようにしているため、ローカルを指定する値を与えます。ローカルを指定するには引数に"."を与えます。

接続の開始

.NetFramework4.5の時点では、接続を非同期で行うメソッド(機能)は用意されていません。クライアントからサーバに接続する作業に関しては、同期操作になります。接続にはConnectメソッドを利用します。引数にはタイムアウト(ms)を指定して、その時間が経過するまでに接続を試みます。

非同期な受信待機の開始

接続を完了した場合には、受信待機を開始します。サーバ側の接続待機と似たような関係になっています。ここでは受信待機の開始を行うためのメソッド、BeginReadメソッドを用意しました。これはメソッド化することに理由があるためです。非同期な受信待機の具体的な処理内容については、次の項目で解説します。また引数に与えているSampleStateは今は気にしないでください。

非同期な受信待機と任意の値

        //受信したデータを保管する配列です.
        static byte[] receiveField;
…
        static void BeginRead(SampleState userObject)
        {
            //受信データを保存する領域を初期化しておきます.
            receiveField = new byte[100];

            //受信待機を開始します.
            //受信データの保存先, 受け取りデータの保存開始位置, 最大保存量,
            //受信時のコールバック, 任意の情報, を指定します
            client.BeginRead
                (receiveField, 0, receiveField.Length,
                ReceiveCallback, userObject);

            Console.WriteLine("Now waiting.");
        }

ここでは非同期な受信待機を開始するメソッドBeginReadを実装しています。まずデータを受信する場所を確保しておきます。ここではフィールドにbyte配列receiveFieldを用意しました。一般に、データはbyteデータとして送受信されます。受信の開始に当たって、適当なサイズでbyte配列(receiveField)を初期化しておきます。

非同期な受信待機を開始するにはBeginReadメソッドを利用します。引数には[受信データを保存する場所、保存するデータの開始位置、最大保存量、コールバック、任意のデータ]を指定します。

指定するコールバックはデータの受信時に呼び出されます。コールバックの内容については後の項目で解説するので、ここではサーバ側では解説を割愛した、任意のデータについて解説します。

任意のデータ(state)には、ユーザが定義したあらゆるデータを指定することができます。指定したデータは、同じく指定したコールバックの引数から参照することができます。ここではシンプルな例として、SampleState構造体を定義し、int型のcountパラメータを設けました。データを受信するたびにcountをインクリメントすることで、設定した任意のデータがどのようにあつかわれるのかを示します。

データの受信とコールバック

        static void ReceiveCallback(IAsyncResult result)
        {

            //受信待機を終了します.
            //1回のBeginReadとセットにして、1回実行される必要があります.
            client.EndRead(result);

            //任意に設定した情報を取得します
            //ここでは受信回数を数えます.
            SampleState userObject = ((SampleState)result.AsyncState);
            userObject.count++;

            //受信内容と受診回数を出力します.
            Console.WriteLine
                ("Receive [" + userObject.count + "] "
                    + Encoding.Unicode.GetString(receiveField));

            //再度待機を開始して無限に受信します.
            BeginRead(userObject);
        }

サーバ側で解説したように、非同期な処理を行う場合には、その開始処理と終了処理が1対1で実行される必要があります。それはクライアント側でも同様で、1回のBegeinReadの実行に対し、1回のEndReadを実行する必要があります。

クライアント側ではユーザの任意のデータを指定したので、まずはそのデータの取り出してみます。ユーザの与えた任意のデータはIAsyncResult.AsyncStateに与えられています。ここでは何も難しい処理を行うことなく、キャストしてSampleState型を復元し、パラメータcountを1増やしています(インクリメント)。

ここではサンプルのため受信したデータを出力して結果を確認します。byte配列を文字列に戻す方法については、ここではサンプルコードを示して解説とします。

最後の処理が重要となります。一度のデータの送受信しか行われないアプリケーション(プログラム)は現実的にはほとんど存在しないでしょう。繰り返しデータの受信を行う場合、クライアントは再び受信を待機する必要があります。そこでクライアントの受信処理をメソッドとして定義し、再帰的に呼び出すように設計しています。この時、引数に与えたSampleStateは繰り返し利用されるので、そのパラメータcountは実質的に受信回数を示すことになります。

これでクライアント側の解説も終わりです。

最小限のサンプルを示すため、クライアント側はシンプルなコンソールアプリケーションとして実装されています。実用的なクライアントを開発するにあたっては、staticであっては問題のある変数やメソッドが多くあることに注意してください。