1. 程式人生 > >C# .Net 多程序同步 通訊 共享記憶體 記憶體對映檔案 Memory Mapped

C# .Net 多程序同步 通訊 共享記憶體 記憶體對映檔案 Memory Mapped

節點通訊存在兩種模型:共享記憶體(Shared memory)和訊息傳遞(Messages passing)。

        記憶體對映檔案對於託管世界的開發人員來說似乎很陌生,但它確實已經是很遠古的技術了,而且在作業系統中地位相當。實際上,任何想要共享資料的通訊模型都會在幕後使用它。

        記憶體對映檔案究竟是個什麼?記憶體對映檔案允許你保留一塊地址空間,然後將該物理儲存對映到這塊記憶體空間中進行操作。物理儲存是檔案管理,而記憶體對映檔案是作業系統級記憶體管理

優勢
     1.訪問磁碟檔案上的資料不需執行I/O操作和快取操作(當訪問檔案資料時,作用尤其顯著);
     2.讓執行在同一臺機器上的多個程序共享資料(單機多程序間資料通訊效率最高);

利用檔案與記憶體空間之間的對映,應用程式(包括多個程序)可以通過直接在記憶體中進行讀寫來修改檔案。.NET Framework 4 用託管程式碼按照本機Windows函式訪問記憶體對映檔案的方式來訪問記憶體對映檔案,管理 Win32 中的記憶體對映檔案

有兩種型別的記憶體對映檔案:

  • 持久記憶體對映檔案

    持久檔案是與磁碟上的原始檔關聯的記憶體對映檔案。在最後一個程序使用完此檔案後,資料將儲存到磁碟上的原始檔中。這些記憶體對映檔案適合用來處理非常大的原始檔。

  • 非持久記憶體對映檔案

    非持久檔案是未與磁碟上的原始檔關聯的記憶體對映檔案。當最後一個程序使用完此檔案後,資料將丟失,並且垃圾回收功能將回收此檔案。

    這些檔案適用於為程序間通訊 (IPC) 建立共享記憶體。

    1)在多個程序之間進行共享(程序可通過使用由建立同一記憶體對映檔案的程序所指派的公用名來對映到此檔案)。

    2)若要使用一個記憶體對映檔案,則必須建立該記憶體對映檔案的完整檢視或部分檢視還可以建立記憶體對映檔案的同一部分的多個檢視,進而建立併發記憶體為了使兩個檢視能夠併發,必須基於同一記憶體對映檔案建立這兩個檢視

    3)如果檔案大於應用程式用於記憶體對映的邏輯記憶體空間(在 32 位計算機上為2GB),則還需要使用多個檢視。

有兩種型別的檢視:流訪問檢視和隨機訪問檢視。使用流訪問檢視可對檔案進行順序訪問;在使用持久檔案時,隨機訪問檢視是首選方法。

    .Net 共享記憶體 記憶體對映檔案原理:通過作業系統的記憶體管理器訪問的,因此會自動將此檔案分隔為多個頁,並根據需要對其進行訪問。您不需要自行處理記憶體管理。如下圖:

C# .Net 共享記憶體 演示程式碼如下:

//持久記憶體對映檔案:基於現有檔案建立一個具有指定公用名的記憶體對映檔案

using (var mmf = MemoryMappedFile.CreateFromFile(@"c:\記憶體對映檔案.data", FileMode.Open, "公用名"))
{
//通過指定的 偏移量和大小建立記憶體對映檔案檢視伺服器
using (var accessor = mmf.CreateViewAccessor(offset, length)) //偏移量,可以控制資料儲存的記憶體位置;大小,用來控制儲存所佔用的空間
{
//Marshal提供了一個方法集,這些方法用於分配非託管記憶體、複製非託管記憶體塊、將託管型別轉換為非託管型別,此外還提供了在與非託管程式碼互動時使用的其他雜項方法。

int size = Marshal.SizeOf(typeof(char));

//修改記憶體對映檔案檢視
for (long i = 0; i < length; i += size)
{
char c= accessor.ReadChar(i);
accessor.Write(i, ref c);
}
}
}

//另一個程序或執行緒可以,在系統記憶體中開啟一個具有指定名稱的現有記憶體對映檔案

using (var mmf = MemoryMappedFile.OpenExisting("公用名"))
{
using (var accessor = mmf.CreateViewAccessor(4000000, 2000000))
{
int size = Marshal.SizeOf(typeof(char));
for (long i = 0; i < length; i += size)
{
char c = accessor.ReadChar(i);
accessor.Write(i, ref c);
}
        }
}

//非持久記憶體對映檔案:未對映到磁碟上的現有檔案的記憶體對映檔案

using (MemoryMappedFile mmf = MemoryMappedFile.CreateNew("testmap", 10000))
{
bool mutexCreated;
//程序間同步
Mutex mutex = newMutex(true, "testmapmutex", out mutexCreated);
using (var stream = mmf.CreateViewStream()) //建立檔案記憶體檢視流 基於流的操作
{
var writer = newBinaryWriter(stream);
writer.Write(1);
}
mutex.ReleaseMutex();

Console.WriteLine("Start Process B and press ENTER to continue.");
Console.ReadLine();

mutex.WaitOne();
using (MemoryMappedViewStream stream = mmf.CreateViewStream())
{
var reader = newBinaryReader(stream);
Console.WriteLine("Process A says: {0}", reader.ReadBoolean());
Console.WriteLine("Process B says: {0}", reader.ReadBoolean());
}
mutex.ReleaseMutex();
}

using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("testmap"))
{
Mutex mutex = Mutex.OpenExisting("testmapmutex");
mutex.WaitOne();
using (var stream = mmf.CreateViewStream(1, 0))//注意這裡的偏移量
{
var writer = newBinaryWriter(stream);
writer.Write(0);
}
mutex.ReleaseMutex();

}

C# .Net  程序間通訊 共享記憶體 完整示例: C#共享記憶體非持久化方式通訊的例子,通訊時的執行緒和程序控制也沒有問題。如下是實現的程式碼。

先啟動訊息服務IMServer_Message

再啟動狀態服務IMServer_State

IMServer_Message回車一次(建立共享記憶體公用名和公用執行緒鎖,並檢視流方式寫共享記憶體),

IMServer_State回車一次(獲取共享記憶體並檢視流方式寫、檢視訪問器寫入結構體型別)

並立刻IMServer_Message再回車一次(讀取剛剛寫入的資訊),

觀察IMServer_State屏顯變化並等待(執行緒鎖)5s(執行緒鎖被釋放)後

IMServer_Message上觀察屏顯(顯示剛剛寫入共享記憶體的資訊)

IMServer_Message.exe 程式碼

using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
using System.Threading;

namespace IMServer_Message
{
    /// <summary>
    /// 用於共享記憶體方式通訊的 值型別 結構體
    /// </summary>
    public struct ServiceMsg
    {
        public int Id;
        public long NowTime;
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.Write("請輸入共享記憶體公用名(預設:testmap):");
            string shareName = Console.ReadLine();
            if (string.IsNullOrEmpty(shareName))
                shareName = "testmap";
            using (MemoryMappedFile mmf = MemoryMappedFile.CreateOrOpen(shareName, 1024000,MemoryMappedFileAccess.ReadWrite))
            {
                bool mutexCreated;
                //程序間同步
                var mutex = new Mutex(true, "testmapmutex", out mutexCreated);
                using (MemoryMappedViewStream stream = mmf.CreateViewStream()) //建立檔案記憶體檢視流
                {
                    var writer = new BinaryWriter(stream);
                    for (int i = 0; i < 5; i++)
                    {
                        writer.Write(i);
                        Console.WriteLine("{0}位置寫入流:{0}", i);
                    }
                }

                mutex.ReleaseMutex();

                Console.WriteLine("啟動狀態服務,按【回車】讀取共享記憶體資料");
                Console.ReadLine();

                mutex.WaitOne();
                using (MemoryMappedViewStream stream = mmf.CreateViewStream())
                {
                    var reader = new BinaryReader(stream);
                    for (int i = 0; i < 10; i++)
                    {
                        Console.WriteLine("{1}位置:{0}", reader.ReadInt32(), i);
                    }
                }

                using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(1024, 10240))
                {
                    int colorSize = Marshal.SizeOf(typeof (ServiceMsg));
                    ServiceMsg color;
                    for (int i = 0; i < 50; i += colorSize)
                    {
                        accessor.Read(i, out color);
                        Console.WriteLine("{1}\tNowTime:{0}", new DateTime(color.NowTime), color.Id);
                    }
                }
                mutex.ReleaseMutex();
            }
            Console.WriteLine("測試: 我是 即時通訊 - 訊息服務 我啟動啦!!!");
            Console.ReadKey();
        }
    }
}

IMServer_State.exe程式碼

using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
using System.Threading;

namespace IMServer_State
{
    /// <summary>
    /// 用於共享記憶體方式通訊的 值型別 結構體
    /// </summary>
    public struct ServiceMsg
    {
        public int Id;
        public long NowTime;
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.Write("請輸入共享記憶體公用名(預設:testmap):");
            string shareName = Console.ReadLine();
            if (string.IsNullOrEmpty(shareName))
                shareName = "testmap";
            using (MemoryMappedFile mmf = MemoryMappedFile.CreateOrOpen(shareName, 1024000,MemoryMappedFileAccess.ReadWrite))
            {
                Mutex mutex = Mutex.OpenExisting("testmapmutex");
                mutex.WaitOne();
                using (MemoryMappedViewStream stream = mmf.CreateViewStream(20, 0)) //注意這裡的偏移量
                {
                    var writer = new BinaryWriter(stream);
                    for (int i = 5; i < 10; i++)
                    {
                        writer.Write(i);
                        Console.WriteLine("{0}位置寫入流:{0}", i);
                    }
                }
                using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(1024, 10240))
                {
                    int colorSize = Marshal.SizeOf(typeof (ServiceMsg));
                    var color = new ServiceMsg();
                    for (int i = 0; i < colorSize*5; i += colorSize)
                    {
                        color.Id = i;
                        color.NowTime = DateTime.Now.Ticks;
                        //accessor.Read(i, out color);
                        accessor.Write(i, ref color);
                        Console.WriteLine("{1}\tNowTime:{0}", new DateTime(color.NowTime), color.Id);
                        Thread.Sleep(1000);
                    }
                }
                Thread.Sleep(5000);

                mutex.ReleaseMutex();
            }
            Console.WriteLine("測試: 我是 即時通訊 - 狀態服務 我啟動啦!!!");
            Console.ReadKey();
        }
    }
}