stackprobe7s_memo

何処にも披露する見込みの無いものを書き落とす場所

名前付きイベントだけを使ってプロセス間通信を行う(C#)

説明

特徴

  • 名前付きイベントだけを使ってプロセス間通信できるんじゃね?と思って試してみたかっただけの代物。
  • ソケットとか共有メモリとかファイルとか一切使えないけど「名前付きイベント」だけは使えるという特殊な状況下でプロセス間通信したい時に使えるかも。
  • 遅くて、実用的ではない。
    • コンパクトさを目指したので、多少速くする余地はあると思う。
    • 数十バイト程度の数回の通信なら気にならないと思う。

仕様

  • 送受信データは文字列
  • '\0' は送れない。
  • 一方通行 (クライアント ⇒ サーバー)

使い方

  1. サーバー側プロセスで NRecver.NRecv(ident, recved) を実行しておく。
    • 引数
      • ident -- イベントの名前に使用する。ぶつかりにくく無難な文字列 (UUIDなど) にすること。クライアント側と同じであること。
      • recved -- 文字列を受信する度に呼ばれる。
    • ブロッキングモードで動作する。
      • 停止するには別スレッドで NRecver.NRecvEnd(ident) を実行する。
  2. クライアント側プロセスで NSender.NSend(ident, message) を実行する。
    • 引数
      • ident -- サーバー側と同じ文字列
      • message -- 送信するメッセージ

サーバー側 (受信側)

public class NRecver
{
	public void NRecv(string ident, Action<string> recved)
	{
		using (var s = new EventWaitHandle(false, EventResetMode.AutoReset, ident + "S"))
		using (var k = new EventWaitHandle(false, EventResetMode.AutoReset, ident + "K"))
		using (var b = new EventWaitHandle(false, EventResetMode.AutoReset, ident + "B"))
		using (var r = new EventWaitHandle(false, EventResetMode.AutoReset, ident + "R"))
		{
			MemoryStream mem = new MemoryStream();
			byte chr = 0;
			bool recving = false;

			// cleanup
			k.WaitOne(0); // 2bs
			b.WaitOne(0);

			for (int i = 0; ; )
			{
				s.WaitOne();
				if (k.WaitOne(0)) break;
				bool bit = b.WaitOne(0);
				r.Set();

				if (recving)
				{
					if (bit)
						chr |= (byte)(1 << i);

					if (8 <= ++i)
					{
						if (chr == 0)
						{
							recved(Encoding.UTF8.GetString(mem.ToArray()));
							mem = new MemoryStream();
							recving = false;
						}
						else
							mem.WriteByte(chr);

						i = chr = 0;
					}
				}
				else
					recving = bit;
			}
		}
	}

	public void NRecvEnd(string ident)
	{
		using (var s = new EventWaitHandle(false, EventResetMode.AutoReset, ident + "S"))
		using (var k = new EventWaitHandle(false, EventResetMode.AutoReset, ident + "K"))
		{
			k.Set();
			s.Set();
		}
	}
}

クライアント側 (送信側)

public class NSender
{
	private const int SEND_TIMEOUT_MILLIS = 2000;

	public void NSend(string ident, string message)
	{
		using (var s = new EventWaitHandle(false, EventResetMode.AutoReset, ident + "S"))
		using (var b = new EventWaitHandle(false, EventResetMode.AutoReset, ident + "B"))
		using (var r = new EventWaitHandle(false, EventResetMode.AutoReset, ident + "R"))
		{
			r.WaitOne(100); // cleanup

			foreach (byte[] bMes in new byte[][]
			{
				new byte[] { 0x00, 0x80 },
				Encoding.UTF8.GetBytes(message.Replace("\0", "")),
				new byte[] { 0x00 }
			})
			{
				for (int i = 0; i / 8 < bMes.Length; i++)
				{
					if ((bMes[i / 8] & (1 << (i % 8))) != 0)
						b.Set();

					s.Set();

					if (!r.WaitOne(SEND_TIMEOUT_MILLIS))
						throw new TimeoutException();
				}
			}
		}
	}
}