技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

C#:共有メモリで可変長データを繰り返し送受信する

共有メモリは、同一メモリ上で実行されるプロセス間でデータをやりとりする場合に使用する仕組みです。
通常、プロセスで確保しているメモリは他のプロセスから参照することができないのですが、プロセス間で予め共有メモリとして使用するメモリのアドレスを共有することで、そのメモリは他のプロセスから参照可能となります。
ファイル等を介したやりとりよりも高速なため、高速化が求められる時に使用することが多いです。
 
目的の一つが高速化のため、データが作成され次第次々と共有する、という使い方になることが多いと思います。
今回は、そのような使い方を想定して、可変長データを繰り返し送受信するサンプルプログラムを作成しました。
書き込んだデータのサイズを伝えること、共有メモリの読み取り位置をずらしていくことがポイントとなります。
 
なお、今回は省略していますが、本来であれば書き込み中の読み取りを防ぐため、Mutex等の排他制御の仕組みを併用するべきです。
(参考までに、Mutexについては過去に記事(https://akira2kun.hatenablog.com/entry/2020/04/12/170559)を書いています)
 
【サンプルコード】
・Sender.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.MemoryMappedFiles;

namespace Sender
{
    class Sender
    {
        // 本当はMutex等で排他制御するべき
        static void Main(string[ ] args)
        {
            // 共有メモリの作成("HelloWorld"という名前で1024バイトで定義)
            MemoryMappedFile mmf =
                MemoryMappedFile.CreateNew("HelloWorld", 1024);
           
            // 書き込みオブジェクトの生成
            MemoryMappedViewAccessor mmva = mmf.CreateViewAccessor();

            // 書き込みオブジェクトの現在のバイト位置
            int i = 0;

            // 書き込み1回目
            // 共有メモリの状態
            // |int(オブジェクトの数)|char(Hello )|
            string str1 = "Hello ";
            char
data1 = str1.ToCharArray();
            mmva.Write(i, data1.Length);
            i = i + sizeof(int);
            mmva.WriteArray<char>(i, data1, 0, data1.Length);
            i = i + sizeof(char) * data1.Length;
            System.Threading.Thread.Sleep(1000);

            // 書き込み2回目
            // 共有メモリの状態
            // |(1回目の内容)|int(オブジェクトの数)|char(World)|
            string str2 = "World";
            char
data2 = str2.ToCharArray();
            mmva.Write(i, data2.Length);
            i = i + sizeof(int);
            mmva.WriteArray<char>(i, data2, 0, data2.Length);
            i = i + sizeof(char) * data2.Length;
            System.Threading.Thread.Sleep(1000);

            // 書き込み3回目
            // 共有メモリの状態
            // |(1~2回目の内容)|int(オブジェクトの数)|char(!)|
            string str3 = "!";
            char
data3 = str3.ToCharArray();
            mmva.Write(i, data3.Length);
            i = i + sizeof(int);
            mmva.WriteArray<char>(i, data3, 0, data3.Length);
            i = i + sizeof(char) * data3.Length;
            System.Threading.Thread.Sleep(1000);

            // 書き込みオブジェクトの破棄
            mmva.Dispose();

            // 共有メモリの破棄
            mmf.Dispose();
        }
    }
}

・Receiver.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.MemoryMappedFiles;

namespace Receiver
{
    class Receiver
    {
        // 本当はMutex等で排他制御するべき
        static void Main(string[ ] args)
        {
            // 作成済み共有メモリ("HelloWorld")のオブジェクト生成
            MemoryMappedFile mmf =
                MemoryMappedFile.OpenExisting("HelloWorld");
           
            // 読み込みオブジェクトの生成
            MemoryMappedViewAccessor mmva = mmf.CreateViewAccessor();

            // 読み込みオブジェクトの現在のバイト位置
            int i = 0;

            // 読み込み
            // オブジェクトの数が書き込まれていたらその分データを読む
            int size = 0;
            string str = "";
            while (!str.Equals("!"))
            {
                size = mmva.ReadInt32(i);
                if (size != 0)
                {
                    char[] data = new char[size];
                    i = i + sizeof(int);
                    mmva.ReadArray<char>(i, data, 0, data.Length);
                    str = new string(data);
                    Console.WriteLine(str);
                    i = i + sizeof(char) * data.Length;
                }
                System.Threading.Thread.Sleep(100);
            }
           
            // 読み込みオブジェクトの破棄
            mmva.Dispose();
           
            // コンソール表示を維持
            Console.ReadKey();
        }
    }
}

【実行バッチ】
・test.bat
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe Sender.cs
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe Receiver.cs
start Sender.exe
timeout 1
start Receiver.exe
pause
exit

【実行結果】
・Receiverのコンソール
Hello
World
!