始めに
かくかくしかじかによりIPC(プロセス間通信)を使用する事にしました。
で、色々調べまして一旦はNetNamedPipeBindingを検討しました。
その後 System.IO.Pipes を見つけまして、こちらを採用。
自分のためのメモとしても記事にまとめました。
共通クラス
サーバーとクライアント双方に共通となる項目を抜き出しました。
が、あまり多くはありませんでした。
using System;
using System.Text;
namespace NamedPipeMng
{
/// <summary>
/// 名前つきパイプ共通クラス
/// </summary>
abstract public class NamedPipeCommon
{
/// <summary>
/// パイプ名称
/// </summary>
public string _PipeName { get { return "NamedPipeSrv01"; } }
}
}
_PipeName はサーバーとクライアント共通で使用する名前付きパイプの名前です。
サーバ回線
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タイムアウトの記事を大いに参考にいたしました。
クライアント回線
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ともにサーバー回線とクライアント回線を両方をインスタンス化します。
お互いに自分のクライアント回線から相手のサーバー回線にデータを送信する事で、相互通信を実現できます。
この場合パイプの名称が重複しない様注意してください。
です!