.NETで名前付きパイプを試す(1) - クライアントからサーバにメッセージを送る

.NET Frameworkを使って名前付きパイプでプロセス間通信を実装する方法が気になったので調べています。このエントリはその勉強の記録です。

名前付きパイプを実装するには、NamedPipeServerStreamとNamedPaipeClientStreamを使います。

クラス名にもありますが、名前付きパイプはクライアントサーバ型の通信です。通信するといってもあまり特別なことはなくてFileStreamやMemoryStreamと同じように扱えばOKのようです。Streamに書き込む(=送信)、Streamから読み込む(=受信)といった操作で通信できるようです。

ということでまずはすごく単純なクライアント側のプログラムとサーバ側のプログラムを作ってみます。それぞれの動きは次のようにします。

クライアント側のプログラムの動き

  • サーバに接続
  • メッセージ(文字列)を送信する

サーバ側のプログラムの動き

  • クライアントからの接続を待つ
  • メッセージ(文字列)を受信する

実用にはほど遠いですが一旦これを目指します。文字列以外も送る、送受信するなどやりたいことはたくさんありますが、最初は高望みしないことにします。

クライアント側のプログラム

まずはクライアント側。サーバに接続してメッセージを送信します。

// クライアント側
class Program {
    private static async Task Client() {
        // ローカルの"testpipe"という名前のパイプのクライアント
        using (var stream = new NamedPipeClientStream("testpipe")) {
            // サーバに接続
            await stream.ConnectAsync();

            // メッセージを送信(書き込み)
            using (var writer = new StreamWriter(stream)) {
                Console.WriteLine($"Send");
                var message = "あいうえお";
                await writer.WriteLineAsync(message);
                Console.WriteLine($"Sent: {message}");
            }
        }
    }

    static void Main(string[] args) {
        Console.WriteLine("Client");

        Client().Wait();
    }
}

// 実行結果
/*
Client
Send
Sent: あいうえお
*/

コメントとして実行結果ものせていますがサーバ側も動かした場合の実行結果です。

また試していませんが、NamedPipeClientStreamを作るときにサーバ名を指定できるようです。指定しない場合はローカル上のパイプになります。

サーバ側のプログラム

次にサーバ。クライアントからの接続を待ってメッセージを受信します。

// サーバ側
class Program {
    private static async Task Server() {
        // "testpipe"という名前のパイプのサーバ
        using (var stream = new NamedPipeServerStream("testpipe")) {
            // クライアントからの接続を待つ
            await stream.WaitForConnectionAsync();

            // メッセージを受信(読み込み)
            using (var reader = new StreamReader(stream)) {
                Console.WriteLine("Receive");
                var message = await reader.ReadLineAsync();
                Console.WriteLine($"Received: {message}");
            }
        }
    }

    static void Main(string[] args) {
        Console.WriteLine("Server");

        Server().Wait();
    }
}

// 実行結果
/*
Server
Receive
Received: あいうえお
*/

この2つのプログラムを実行するとクライアントからサーバにメッセージを送信できることが確認できます。

とりあえず送信できたので今回はこのあたりで。

ちなみにソリューションにプロジェクトを2つ入れて同時にデバッグ実行すると楽かもです。

ichiroku11.hatenablog.jp

参考

Visual Studioでソリューション内の複数のプロジェクトをデバッグ実行する

実行可能なプロジェクトが複数あるソリューションで、複数のプロジェクトをデバッグ実行する方法です。

今日知りました。

ソリューションのプロパティからスタートアッププロジェクトで「マルチスタートアッププロジェクト」を選びます。そして実行したいプロジェクトのアクションを「開始」にします。

詳しくはこのあたりに。

さすがVisual Studio。知らなかったー。

T-SQL - DROP IF EXISTS

先週SQL Server 2016の提供が開始されました。

新機能を調べだしたところですが、まずはとりあえずDROP IF EXISTSを試してみました。

開発中、試行錯誤してたりとかでテーブル構造がなかなか固まらず、テーブルを作っては消してまた作るということを繰り返すことがありました。 私はGUIでテーブルを作らずクエリで作る方で、さらにGUIでテーブルを消すこともめんどくさくて、次のようなクエリをよく書いていました。

-- テーブルが存在していればテーブルを消す
if object_id(N't') is not null
    drop table t;

-- テーブルを作る
create table t(c int);

このテーブルが存在していれば消すということが、 SQL Server 2016からはすっきり書けるようになりました。それがDROP IF EXISTS。

-- テーブルが存在していればテーブルを消す
drop table if exists t;

ぶっちゃけ地味ですがわかりやすいですね。DROP文全般で使えるようですし、ALTER TABLEでカラムを削除するときも使えるみたいです。

参考

次はJSON関係を試してみようかなーと思います。

FlagsAttributeを指定したenumの文字列

ものすごく小ネタですが、FlagsAttributeを指定したenumの文字列表現はどうなるんだろ?とふと気になって試してみました。

// トッピング
[Flags]
public enum Toppings {
    None = 0x00,   // なし
    Nitamago = 0x01,   // 煮卵
    CharSiu = 0x02,    // チャーシュー
    Menma = 0x04,  // メンマ
    Moyashi = 0x08,    // もやし
}

出力してみます。フラグを組み合わせた場合はカンマ区切りの文字列となるようです。文字列の並びはenumの値順ですかね。

Console.WriteLine(Toppings.CharSiu);
// CharSiu

Console.WriteLine(Toppings.CharSiu | Toppings.Menma);
// CharSiu, Menma

Console.WriteLine(Toppings.CharSiu | Toppings.Nitamago | Toppings.Menma);
// Nitamago, CharSiu, Menma

Console.WriteLine(Toppings.None);
// None

もちろんカンマ区切りの文字列をパースできると。

var toppings = default(Toppings);
if (Enum.TryParse<Toppings>("CharSiu, Moyashi, Nitamago", out toppings)) {
    Console.WriteLine(toppings);
    // Nitamago, CharSiu, Moyashi
}

どうでもいいことですが、あぶりメンマが好きです。

ASP.NET MVC - FilterAttribute.OrderとFilterScopeによるフィルタの実行順を確認する

FilterAttribute.OrderとFilterScopeによるフィルタの実行順を確認します。

同じフィルタが複数ある場合に

  1. FilterAttributeのOrderプロパティの昇順(小さい値から大きい値の順序)
  2. FilterScopeの昇順

でフィルタがソートされて実行される、というあたりを確認します。

FilterAttribute.Order

Orderプロパティは想像しやすいかなと思います。

なので略。

FilterScope

FilterScopeはMVC内部で使われているもので、使う側が指定するものではないようです。

ざっくり言うとフィルタの指定した場所によって決まるものという解釈でいいのかなと思います。

  • FilterScope.First・・・コントローラ自体のフィルタ
  • FilterScope.Global・・・グローバルフィルタに登録したフィルタ
  • FilterScope.Controller・・・コントローラのクラスに指定したFilterAttribute
  • FilterScope.Action・・・アクションメソッドに指定したFilterAttribute
  • FilterScope.Last・・・定義されているだけで使われていないような・・・?

といった感じです。昇順もこの順番です。

FilterScope.Firstを少し補足します。

コントローラは各フィルタのインターフェイスを実装しています。メソッドをオーバーライドすることによってフィルタの処理を書くことができます。このときコントローラ自体のスコープはFilterScope.Firstとなります。またOrderはInt32.MinValueとして扱われているようです。つまりコントローラ自体のフィルタ(オーバーライドしたメソッド)は必ず最初に実行されることになります。

サンプルで確認

ここからはアクションフィルタに絞ってサンプルコードで確認してみます。

まずはアクションフィルタを実装したフィルタ属性を用意します。この属性はトレース出力だけしています。

// アクションフィルタだけを実装したフィルタ属性
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class TestFilterAttribute : FilterAttribute, IActionFilter {
    public string Label { get; set; } // 確認用に出力する文字列

    public void OnActionExecuting(ActionExecutingContext filterContext) {
        // Orderとラベルをトレース
        Trace.WriteLine($"{nameof(TestFilterAttribute)}({Order}, \"{Label}\").{nameof(OnActionExecuting)}");
    }

    public void OnActionExecuted(ActionExecutedContext filterContext) {
        Trace.WriteLine($"{nameof(TestFilterAttribute)}({Order}, \"{Label}\").{nameof(OnActionExecuted)}");
    }
}

次に用意した属性をグローバルフィルタに設定します。Orderを省略した場合は-1になります。

// グローバルフィルタとして属性追加(ラベル:"Global")
// Orderを指定しない場合は-1
GlobalFilters.Filters.Add(new TestFilterAttribute { Label = "Global" });
GlobalFilters.Filters.Add(new TestFilterAttribute { Order = 1, Label = "Global" });
GlobalFilters.Filters.Add(new TestFilterAttribute { Order = 2, Label = "Global" });

さらに用意した属性をコントローラのクラスとメソッドにも指定します。またコントローラ自体のアクションフィルタもオーバーライドしてトレースするようにしておきます。

// クラスに属性(ラベル:"Controller")
[TestFilter(Label = "Controller")]
[TestFilter(Order = 1, Label = "Controller")]
[TestFilter(Order = 2, Label = "Controller")]
public class DefaultController : Controller {
    // コントローラ自体のアクションフィルタ
    protected override void OnActionExecuting(ActionExecutingContext filterContext) {
        Trace.WriteLine($"{nameof(DefaultController)}.{nameof(OnActionExecuting)}");
    }
    protected override void OnActionExecuted(ActionExecutedContext filterContext) {
        Trace.WriteLine($"{nameof(DefaultController)}.{nameof(OnActionExecuted)}");
    }

    // メソッドに属性(ラベル:"Action")
    [TestFilter(Label = "Action")]
    [TestFilter(Order = 1, Label = "Action")]
    [TestFilter(Order = 2, Label = "Action")]
    public ActionResult Index() {
        Trace.WriteLine($"{nameof(DefaultController)}.{nameof(Index)}");
        return new EmptyResult();
    }
}

そしてデバッグ実行すると出力結果は次のように。わかりやすいように改行しつつコメントも足しています。

// コントローラ自体のメソッドが真っ先に呼ばれる
DefaultController.OnActionExecuting

// Order = -1(Orderを指定しない)のアクションフィルタ
// Global、Controller、Actionの順で実行される
TestFilterAttribute(-1, "Global").OnActionExecuting
TestFilterAttribute(-1, "Controller").OnActionExecuting
TestFilterAttribute(-1, "Action").OnActionExecuting

// Order = 1のアクションフィルタ
TestFilterAttribute(1, "Global").OnActionExecuting
TestFilterAttribute(1, "Controller").OnActionExecuting
TestFilterAttribute(1, "Action").OnActionExecuting

// Order = 2のアクションフィルタ
TestFilterAttribute(2, "Global").OnActionExecuting
TestFilterAttribute(2, "Controller").OnActionExecuting
TestFilterAttribute(2, "Action").OnActionExecuting

// ここでアクションメソッドの呼び出し
DefaultController.Index

// OnActionExecutedは逆順
TestFilterAttribute(2, "Action").OnActionExecuted
TestFilterAttribute(2, "Controller").OnActionExecuted
TestFilterAttribute(2, "Global").OnActionExecuted

TestFilterAttribute(1, "Action").OnActionExecuted
TestFilterAttribute(1, "Controller").OnActionExecuted
TestFilterAttribute(1, "Global").OnActionExecuted

TestFilterAttribute(-1, "Action").OnActionExecuted
TestFilterAttribute(-1, "Controller").OnActionExecuted
TestFilterAttribute(-1, "Global").OnActionExecuted

DefaultController.OnActionExecuted

Orderの昇順、Orderが同じ場合はグローバルフィルタ、クラスのフィルタ属性、メソッドのフィルタ属性の順(FilterScopeの昇順)で実行されることを確認できました。さらにOnActionExecutingは上記の実行順で実行され、OnActionExecutedはその逆順で実行されることも確認できました。

ちなみにOrderもFilterScopeも同じ場合の実行順は未定義とのことです。

種類、順序、スコープが同じフィルターの実行順序は未定義です。

おしまい。