パイプ関連記事 もくじ
やりたいこと
以前、あるプロセスと別のプロセスを連携させるために、原始的(?)にファイルにお互いから書き込み/読み込みを行って、情報をやり取りする方法を取ったのだが、自分が書き込んだあとに相手が読み込むまでのタイムラグや、同時にファイルを読み書きしようとしたらおかしくなる(書き込み途中のものを見てしまう、下手するとファイルを開く権限がないとかで落ちる)、ファイルを消したらおかしくなる、など、問題が多発した。
なにか他の方法で、プロセス間で情報をやり取りしたい。
やったこと
そういう「プロセス間で情報をやりとりする」ことを、**IPC(Interprocess communication)**というらしい。
https://docs.microsoft.com/ja-jp/windows/uwp/communication/interprocess-communication
Microsoftのページによると、複数のIPCするための方法がある様子。
- App services
- COM
- File System
- BroadFileSystemAccess
- PublisherCacheFolder
- SharedAccessStorageManager
- FullTrustProcessLauncher
- LaunchUriForResultsAsync
- Loopback
- Pipes
- Registry
- RPC
- Shared Memory
たくさんある...あとMSのページをざっと見ただけでは、私にはそれぞれがどういうやり方なのか全然わからなかった...
雰囲気的に、説明をざっと見た感じでは、「パイプ」より上は、デスクトップアプリではなくパッケージされたアプリ向けの話な気がする。(本当にパッと見ただけ。違ってたらすみません)
全部は見切れないので、今回は身近で実績のあった**「名前付きパイプ」**でまずはやってみることにした。
実験プログラム
ベースは下記のMSのサンプル。
下記MSサンプルではThread
とか使ってたのでTask
に直して、余計なのを消した。
https://docs.microsoft.com/ja-jp/dotnet/standard/io/how-to-use-named-pipes-for-network-interprocess-communication
※一方通行のパイプ通信などもあるようだが、今回は双方向(PipeDirection.InOut
)で試した。
注意
以下のサンプルプログラムは、クライアントが接続を切ったとき等、動作がうまくいかないときがあるので、別で作成した下記のものを使うこと。
https://tera1707.com/entry/2022/10/12/001814
実験プログラムの動作イメージ
実験プログラムの説明
同じのを2つ立ち上げて、1個はパイプサーバー、もう一つをクライアントにすると、お互いに名前付きパイプで通信をすることができる。
- クライアントは、サーバーに向けて文字列を送る。
- サーバーは、それを受けてクライアントに受信した旨を返信する。
※エラー処理とかちゃんとしてないので、片方だけ落としたりするともう片方が例外で落ちます。
using System;
using System.IO;
using System.IO.Pipes;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
namespace PipeJikken
{
class Program
{
private static int numThreads = 1;
static void Main(string[] args)
{
Console.WriteLine("パイプ通信の実験をします。");
Console.WriteLine("サーバーになりますか?クライアントになりますか?");
Console.WriteLine("サーバー:s クライアント:c を入力してください");
var sorc = Console.ReadLine();
if (sorc == "s")
{
CreatePipeServerTask("pipe1").Wait();
}
else
{
CreateClientTask("pipe1").Wait();
}
}
public static Task CreatePipeServerTask(string pipeName)
{
Console.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff") + " " + "Please enter a message, and then press Enter.");
return Task.Run(() =>
{
// numThreadsで指定した数まで同じ名前のパイプを作れる
NamedPipeServerStream pipeServer = null;
while (true)
{
try
{
pipeServer = new NamedPipeServerStream(pipeName, PipeDirection.InOut, numThreads);
// クライアントの接続待ち
pipeServer.WaitForConnection();
StreamString ss = new StreamString(pipeServer);
while (true)
{
// 受信待ち
var read = ss.ReadString();
// 受信したら応答を送信
var write = ss.WriteString("Server read OK.");
Console.WriteLine("Read Data = " + read);
if (read == "end") break;
}
}
catch (OverflowException ofex)
{
// クライアントが切断
Console.WriteLine(ofex.Message);
}
finally
{
pipeServer?.Close();
}
}
});
}
public static Task CreateClientTask(string pipeName)
{
return Task.Run(() =>
{
while (true)
{
NamedPipeClientStream pipeClient = null;
try
{
pipeClient = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.Impersonation);
pipeClient.Connect();
var ss = new StreamString(pipeClient);
while (true)
{
// 入力された文字列を送信する
var writeData = Console.ReadLine();
var write = ss.WriteString(writeData);
// 応答待ち
var read = ss.ReadString();
Console.WriteLine("Server Response = " + read);
if (writeData == "end") break;
}
}
catch (OverflowException ofex)
{
Console.WriteLine(ofex.Message);
}
catch (IOException ioe)
{
// 送信失敗
Console.WriteLine(ioe.Message);
}
finally
{
pipeClient?.Close();
}
}
});
}
}
// MSサンプルそのまま(streamに文字列を読み書きしてくれるクラス)
public class StreamString
{
private Stream ioStream;
private UnicodeEncoding streamEncoding;
public StreamString(Stream ioStream)
{
this.ioStream = ioStream;
streamEncoding = new UnicodeEncoding();
}
public string ReadString()
{
int len = 0;
len = ioStream.ReadByte() * 256;
len += ioStream.ReadByte();
byte[] inBuffer = new byte[len];
ioStream.Read(inBuffer, 0, len);
return streamEncoding.GetString(inBuffer);
}
public int WriteString(string outString)
{
byte[] outBuffer = streamEncoding.GetBytes(outString);
int len = outBuffer.Length;
if (len > UInt16.MaxValue)
{
len = (int)UInt16.MaxValue;
}
ioStream.WriteByte((byte)(len / 256));
ioStream.WriteByte((byte)(len & 255));
ioStream.Write(outBuffer, 0, len);
ioStream.Flush();
return outBuffer.Length + 2;
}
}
}
実験プログラム2(お互いが自分の好きなタイミングで送信を開始する)
上の実験プログラムだと、サーバーはパイプを開いて受信待ちに入り、クライアントが送信をしたら、サーバーが受信後その応答を返す、という流れなので、サーバー側から送信を開始する、ということができてなかった。
メイン関数の部分を下記のように変えると、お互いが好きなタイミングで送信できた。
(単にパイプを2つ作ってサーバーとクライアントをテレコにしただけ。両方が、サーバーでクライアントという感じ。これが良いのかどうかはわからない...)
static void Main(string[] args)
{
Console.WriteLine("パイプ通信の実験をします。");
Console.WriteLine("サーバーになりますか?クライアントになりますか?");
Console.WriteLine("サーバー:s クライアント:c を入力してください");
var sorc = Console.ReadLine();
if (sorc == "s")
{
CreatePipeServerTask("pipe1");
CreateClientTask("pipe2").Wait();
}
else
{
CreateClientTask("pipe1");
CreatePipeServerTask("pipe2").Wait();
}
}
// こっから下は実験1と同じ
匿名パイプと名前付きパイプはどう違う?
下記ページによると、名前付きパイプだと、同じコンピュータ上のプロセスだけではなく、ネットワーク上の別のコンピュータ上のプロセスとも通信できるらしい。
https://docs.microsoft.com/ja-jp/windows/win32/ipc/named-pipes
現状未検証なので、一度やってみたい。
参考
いろんなプロセス間通信(MSdocs)
https://docs.microsoft.com/ja-jp/windows/uwp/communication/interprocess-communication
C#名前付きパイプ(MSdocs)
ここのサンプルを元にコード作成。
https://docs.microsoft.com/ja-jp/dotnet/standard/io/how-to-use-named-pipes-for-network-interprocess-communication
名前付きパイプの説明(MSdocs)
https://docs.microsoft.com/ja-jp/windows/win32/ipc/named-pipes
C++での名前付きパイプでの通信をこちらでされてる様子。
C++でやる際には参考にさせて頂こうと思う。
http://country-programmer.dfkp.info/2021/01/named_pipe_windows_004/
また同じ方が、別のプロセス間通信のやり方である「共有メモリ」をこちらで実践されている。
これも参考になりそう...
http://country-programmer.dfkp.info/2021/02/shared_memory_001/