LoginSignup
12
15

More than 5 years have passed since last update.

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

Posted at

始めに

かくかくしかじかにより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タイムアウトの記事を大いに参考にいたしました。

クライアント回線

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)にしました。
でサーバープロセスとクライアントプロセスで共通に使用することで、パイプの名前を合わせることが出来ます。
相互通信する場合には、プロセスA、Bともにサーバー回線とクライアント回線を両方をインスタンス化します。
お互いに自分のクライアント回線から相手のサーバー回線にデータを送信する事で、相互通信を実現できます。
この場合パイプの名称が重複しない様注意してください。
です!

12
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
15