C#で名前付きパイプストリーム

# 始めに
かくかくしかじかによりIPC(プロセス間通信)を使用する事にしました。
で、色々調べまして一旦はNetNamedPipeBindingを検討しました。
その後 System.IO.Pipes を見つけまして、こちらを採用。
自分のためのメモとしても記事にまとめました。

# 共通クラス

サーバーとクライアント双方に共通となる項目を抜き出しました。
が、あまり多くはありませんでした。

```NamedPipeCommon.cs
using System;
using System.Text;

namespace NamedPipeMng
{
    /// <summary>
    /// 名前つきパイプ共通クラス
    /// </summary>
    abstract public class NamedPipeCommon
    {
        /// <summary>
        /// パイプ名称
        /// </summary>
        public string _PipeName { get { return "NamedPipeSrv01"; } }
    }
}
```

_PipeName はサーバーとクライアント共通で使用する名前付きパイプの名前です。

# サーバ回線

```NamedPipeServerMng.cs
using System;
using System.Collections.Generic;
using System.Text;

using System.IO;
using System.IO.Pipes;
using System.Threading;

namespace NamedPipeMng
{
    /// <summary>
    /// 名前つきパイプ サーバ
    /// </summary>
    public class NamedPipeServerMng : NamedPipeCommon
    {
        /// <summary>
        /// 受信文字列転送デリゲート
        /// </summary>
        /// <param name="msg">受信文字列/メッセージ</param>
        /// <param name="iscontrol">true:=制御メッセージ, false:=受信文字列</param>
        public delegate void ReceiveMsg(string msg, bool iscontrol = false);
        /// <summary>
        /// パイプサーバ待ち受け処理スレッド
        /// </summary>
        Thread _Thread { set; get; }
        /// <summary>
        /// パイプサーバ待ち受け中フラグ
        /// </summary>
        bool IsListen { set; get; }
        /// <summary>
        /// 受信文字列転送処理
        /// </summary>
        ReceiveMsg _ReceiveMsg { set; get; }
        /// <summary>
        /// パイプサーバ待ち受け時間
        /// </summary>
        int _TimeoutCount { get { return 60 * 1000; } }
        /// <summary>
        /// パイプ通信終了メッセージ
        /// </summary>
        string _CloseMsg { get { return "CloseMsg"; } }
        /// <summary>
        /// コンストラクタ
        /// </summary>
        public NamedPipeServerMng()
        {
            this._Thread = null;
            this.IsListen = false;
            this._ReceiveMsg = null;
        }
        /// <summary>
        /// 待ちうけ処理開始
        /// </summary>
        /// <param name="putmsg"></param>
        /// <returns></returns>
        public bool Start(ReceiveMsg putmsg)
        {
            this._ReceiveMsg = putmsg;
            return Start();
        }
        /// <summary>
        /// 待ちうけ処理開始
        /// </summary>
        /// <returns></returns>
        public bool Start()
        {
            try
            {
                this._Thread = new Thread(new ThreadStart(this.ThreadMethod));
                this._Thread.IsBackground = true;
                this._Thread.Start();
            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }
        /// <summary>
        /// 待ちうけ処理終了
        /// </summary>
        public void Stop()
        {
            this.IsListen = false;
        }
        /// <summary>
        /// 待ちうけ処理スレッド
        /// </summary>
        private void ThreadMethod()
        {
            string input = string.Empty;

            this.IsListen = true;
            var timeout = false;

            // パイプサーバ作成
            Func<NamedPipeServerStream> createpipe = delegate() {
                var ps = new PipeSecurity();
                ps.AddAccessRule(new PipeAccessRule("Everyone", PipeAccessRights.FullControl, System.Security.AccessControl.AccessControlType.Allow));
                return new NamedPipeServerStream(this._PipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous, 1024, 1024, ps); 
            };

            var pipe = createpipe();
            while (this.IsListen)
            {
                var aresult = pipe.BeginWaitForConnection((ar) => { this.SendMessage("Connection now!", true); Console.WriteLine("Connection now!"); }, null);
                this.SendMessage("start wait for connection.", true);

                var connected = aresult.AsyncWaitHandle.WaitOne(this._TimeoutCount);
                if (!connected)
                {
                    this.ConnectionTimeout();
                    this.SendMessage("TimeOut!!.", true);
                }
                pipe.EndWaitForConnection(aresult);
                try
                {
                    if (pipe.IsConnected)
                    {
                        using (var sr = new StreamReader(pipe))
                        {
                            this.SendMessage("Wait For Input from Client.", true); ;
                            input = sr.ReadToEnd();
                            this.SendMessage(string.Format("ReceiveMessage[[{0}]]", input),true);
                            if (input == this._CloseMsg) { timeout = true; }
                            else
                            {
                                this.SendMessage(input);
                            }
                        }
                    }
                }
                catch (Exception ex) { this.SendMessage(ex.Message, true); }
                if (!timeout) pipe = createpipe(); 
            }
            if (pipe.IsConnected) pipe.Disconnect();
            pipe.Close();
        }
        /// <summary>
        /// パイプサーバ待ちうけタイムアウト
        /// </summary>
        private void ConnectionTimeout()
        {
            var pipe = new NamedPipeClientStream(this._PipeName);
            pipe.Connect(100);
            if (pipe.IsConnected)
            {
                using (var sw = new StreamWriter(pipe))
                {
                    sw.WriteLine(this._CloseMsg);
                    pipe.WaitForPipeDrain();
                }
            }
            pipe.Close();
        }
        /// <summary>
        /// 文字列を呼び出し元へ送信
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="iscontrol"></param>
        private void SendMessage(string msg, bool iscontrol = false)
        {
            if (this._ReceiveMsg != null)
            {
                this._ReceiveMsg(msg, iscontrol);
            }
            Console.WriteLine(msg);
        }
    }
}
```
サーバ回線を使用するプロセスでNamedPipeServerMng のオブジェクトを作成します。
Start()関数で回線を開き、Stop()で回線を閉じます。
回線を開いている間にクライアントがデータを送信してきた場合に_ReceiveMsg に設定したデリゲートに転送することでその後自由に処理することが出来ます。

ConnectionTimeout()関数はサーバー回線の待ち受けタイムアウト処理を行います。
クライアントからの接続を待ち受け中に_TimeoutCount で設定した時間（秒）接続がなかった場合、一旦回線を閉じます。
閉じている間にIsListen をチェックし、終了の判定を行います。
このあたりの処理は、こちら[NamedPipeServerStreamのConnectタイムアウト](http://ns.ttj.nu/whatiwannatalkabout/2016/03/27/namedpipeserverstream%E3%81%AEconnect%E3%82%BF%E3%82%A4%E3%83%A0%E3%82%A2%E3%82%A6%E3%83%88/)の記事を大いに参考にいたしました。

# クライアント回線

``` NamedPipeClientMng.cs
using System;
using System.Text;

using System.IO;
using System.IO.Pipes;
using System.Threading;
namespace NamedPipeMng
{
    /// <summary>
    /// 名前つきパイプ クライアント
    /// </summary>
    public class NamedPipeClientMng : NamedPipeCommon
    {
        /// <summary>
        /// スレッド
        /// </summary>
        Thread _Thread { set; get; }
        /// <summary>
        /// 送信文字列
        /// </summary>
        string _Message { set; get; }
        /// <summary>
        /// 
        /// </summary>
        byte[] buffer { set; get; }
        /// <summary>
        /// コンストラクタ
        /// </summary>
        public NamedPipeClientMng()
        {
            this._Thread = null;
        }
        /// <summary>
        /// 文字列送信要求
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public bool SendMessage(string message)
        {
            Console.WriteLine("{0}.{1:00#} SendMessage 01", DateTime.Now.ToLongTimeString(), DateTime.Now.Millisecond);
            try
            {
                this._Message = message;
                this._Thread = new Thread(new ThreadStart(this.ThreadProc));
                this._Thread.IsBackground = true;
                this._Thread.Start();
            }
            catch (Exception)
            {
                return false;
            }
            Console.WriteLine("{0}.{1:00#} SendMessage 99", DateTime.Now.ToLongTimeString(), DateTime.Now.Millisecond);
            return true;
        }
        /// <summary>
        /// 文字列送信処理
        /// </summary>
        private void ThreadProc()
        {
            Console.WriteLine("{0}.{1:00#} ThreadProc 01", DateTime.Now.ToLongTimeString(), DateTime.Now.Millisecond);
            var pipe = new NamedPipeClientStream(".", this._PipeName, PipeDirection.InOut);
            using (var sw = new StreamWriter(pipe))
            {
                pipe.Connect();
                sw.Write(this._Message);
                sw.Flush();
                pipe.WaitForPipeDrain();
            }
            Console.WriteLine("{0}.{1:00#} ThreadProc 99", DateTime.Now.ToLongTimeString(), DateTime.Now.Millisecond);
        }
        /// <summary>
        /// パイプサーバープロセス起動要求
        /// </summary>
        /// <param name="ProcName">プロセス名称</param>
        /// <param name="exepath">実行ファイルフルパス</param>
        /// <returns>実行中プロセス名称</returns>
        public static string WakeupPipeServerProc(string ProcName, string exepath)
        {
            var ps = System.Diagnostics.Process.GetProcessesByName(ProcName);
            if (ps.Length <= 0)
            {
                var p = System.Diagnostics.Process.Start(exepath);
                System.Threading.Thread.Sleep(1000);
            }

            ps = System.Diagnostics.Process.GetProcessesByName(ProcName);
            Console.WriteLine("Process:{0}", ps.Length);
            foreach (var p in ps)
            {
                Console.WriteLine("ProcessName::{0}", p.ProcessName);
            }

            if (ps.Length > 0)
                return ps[0].ProcessName;
            else
                return string.Empty;
        }
        /// <summary>
        /// パイプサーバープロセス終了要求
        /// </summary>
        public static void ShutdownPipeServerProc(string ProcName)
        {
            var ps = System.Diagnostics.Process.GetProcessesByName(ProcName);
            foreach(var p in ps)
            {
                p.CloseMainWindow();
            }
        }
    }
}
```
クライアント側もサーバー側と同様、クライアント回線を使用するプロセスでNamedPipeClientMng のオブジェクトを作成します。
オブジェクト作成後SendMessage()関数で送信したい文字列を送信します。
あとは勝手に回線を開き送信、回線を閉じるところまで行います。

NamedPipeClientMng ではサーバー回線のNamedPipeServerMng と違いWakeupPipeServerProc()関数とShutdownPipeServerProc()関数の二つの静的関数を持たせています。
クライアントプロセス起動時などにWakeupPipeServerProc()関数を使用してサーバープロセスを起動します。
これで、送信先のプロセスが起動され、（恐らくは）パイプが待ち受けていると期待できます。
合わせてクライアントプロセス終了時にShutdownPipeServerProc()関数を読んでサーバープロセスを終了させます。

# おしまい
3つのプログラムをまとめてライブラリ(DLL)にしました。
でサーバープロセスとクライアントプロセスで共通に使用することで、パイプの名前を合わせることが出来ます。
相互通信する場合には、プロセスＡ、Ｂともにサーバー回線とクライアント回線を両方をインスタンス化します。
お互いに自分のクライアント回線から相手のサーバー回線にデータを送信する事で、相互通信を実現できます。
この場合パイプの名称が重複しない様注意してください。
です！