1. 程式人生 > >在Linux和Windows平臺上操作MemoryMappedFile(簡稱MMF)

在Linux和Windows平臺上操作MemoryMappedFile(簡稱MMF)

作業系統很早就開始使用記憶體對映檔案(Memory Mapped File)來作為程序間的共享儲存區,這是一種非常高效的程序通訊手段。.NET 4.0新增加了一個System.IO. MemoryMappedFiles名稱空間,其中添加了幾個類和相應的列舉型別,從而使我們可以很方便地建立記憶體對映檔案。Mono 3.2也有這個類來操作Linux下的記憶體對映檔案,《MemoryMappedFile 在 Mono in Linux 的開發筆記》詳細的介紹了Mono和.NET 4的實現區別,為了讓程式碼能夠在Linux和Windows平臺都正常執行,建議統一使用

MemoryMappedFile.CreateFromFile
(
    FileStream fileStream,
    String mapName,
    Int64 capacity,
    MemoryMappedFileAccess access,
    System.IO.MemoryMappedFiles.MemoryMappedFileSecurity memoryMappedFileSecurity,
    HandleInheritability inheritability,
    Boolean leaveOpen
)

方法來建立MMF,並且在呼叫前確保指定的檔案流大小與capacity引數值相同。

下面我給出在Windows和Linux下都執行正常的程式碼:

using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections.Generic;
using System.Text;
using System.Security.AccessControl;
using System.Configuration;

namespace ManagedMMF
{
    class
Program { static void Main(string[] args) { // Build a sample object and report records HikingDatabase hikingData = BuildDatabase(5000, 50); Console.WriteLine("Dummy database object created with " + hikingData.hikes.Length + " records."); string mmfile = ConfigurationManager.AppSettings["mmf"]; // Write object to MMF WriteObjectToMMF(mmfile, hikingData); // Clear object and report hikingData = null; Console.WriteLine("Database object has been destroyed."); // Read new object from MMF and report records hikingData = ReadObjectFromMMF(mmfile) as HikingDatabase; Console.WriteLine("Dummy database object re-loaded from MMF with " + hikingData.hikes.Length + " records."); // Wait for input and terminate Console.ReadLine(); } #region Generic MMF read/write object functions static void WriteObjectToMMF(string mmfFile, object objectData) { string mapName = "MyFile"; if (IsMono()) { mapName = mmfFile; } // Convert .NET object to byte array byte[] buffer = ObjectToByteArray(objectData); using (FileStream fs = new FileStream(mmfFile, FileMode.Create, FileAccess.ReadWrite)) { fs.SetLength(buffer.Length); // Create a new memory mapped file using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(fs, mapName, buffer.Length, MemoryMappedFileAccess.ReadWrite, new MemoryMappedFileSecurity() { }, HandleInheritability.Inheritable, true)) { // Create a view accessor into the file to accommmodate binary data size using (MemoryMappedViewAccessor mmfWriter = mmf.CreateViewAccessor(0, buffer.Length)) { // Write the data mmfWriter.WriteArray<byte>(0, buffer, 0, buffer.Length); } } } } static object ReadObjectFromMMF(string mmfFile) { string mapName = "MyFile"; if (IsMono()) { mapName = mmfFile; } using (FileStream fs = new FileStream(mmfFile, FileMode.Open, FileAccess.ReadWrite)) { // Get a handle to an existing memory mapped file using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(fs, mapName, fs.Length, MemoryMappedFileAccess.ReadWrite, new MemoryMappedFileSecurity() { }, HandleInheritability.Inheritable, true)) { // Create a view accessor from which to read the data using (MemoryMappedViewAccessor mmfReader = mmf.CreateViewAccessor()) { // Create a data buffer and read entire MMF view into buffer byte[] buffer = new byte[mmfReader.Capacity]; mmfReader.ReadArray<byte>(0, buffer, 0, buffer.Length); // Convert the buffer to a .NET object return ByteArrayToObject(buffer); } } } } static bool IsMono() { Type t = Type.GetType("Mono.Runtime"); return t != null; } #endregion #region Object/Binary serialization static object ByteArrayToObject(byte[] buffer) { BinaryFormatter binaryFormatter = new BinaryFormatter(); // Create new BinaryFormatter MemoryStream memoryStream = new MemoryStream(buffer); // Convert byte array to memory stream, set position to start return binaryFormatter.Deserialize(memoryStream); // Deserializes memory stream into an object and return } static byte[] ObjectToByteArray(object inputObject) { BinaryFormatter binaryFormatter = new BinaryFormatter(); // Create new BinaryFormatter MemoryStream memoryStream = new MemoryStream(); // Create target memory stream binaryFormatter.Serialize(memoryStream, inputObject); // Convert object to memory stream return memoryStream.ToArray(); // Return memory stream as byte array } #endregion static HikingDatabase BuildDatabase(int recordCount, int gpsCoordCount) { Random rand = new Random(); HikingDatabase hikingData = new HikingDatabase(); hikingData.Description = "My hikes, 2010 to 2012"; hikingData.hikes = new Hike[recordCount]; for (int i = 0; i < hikingData.hikes.Length; i++) { hikingData.hikes[i] = new Hike(); hikingData.hikes[i].Description = "This is a description of this particular record. "; hikingData.hikes[i].Date = DateTime.Now.ToLongDateString(); hikingData.hikes[i].GPSTrack = new Coord[gpsCoordCount]; for (int j = 0; j < hikingData.hikes[i].GPSTrack.Length; j++) { hikingData.hikes[i].GPSTrack[j] = new Coord(); hikingData.hikes[i].GPSTrack[j].x = rand.NextDouble() * 1000000; hikingData.hikes[i].GPSTrack[j].y = rand.NextDouble() * 1000000; hikingData.hikes[i].GPSTrack[j].z = rand.NextDouble() * 1000; } } return hikingData; } } #region Sample object for I/O [Serializable] public class HikingDatabase { public string Description; public Hike[] hikes; } [Serializable] public class Hike { public string Description; public string Date; public Coord[] GPSTrack; } [Serializable] public class Coord { public double x; public double y; public double z; } #endregion } 所謂記憶體對映檔案,其實就是在記憶體中開闢出一塊存放資料的專用區域,這區域往往與硬碟上特定的檔案相對應。程序將這塊記憶體區域對映到自己的地址空間中,訪問它就象是訪問普通的記憶體一樣。
在.NET中,使用MemoryMappedFile物件表示一個記憶體對映檔案,通過它的CreateFromFile()方法根據磁碟現有檔案建立記憶體對映檔案,呼叫這一方法需要提供一個與磁碟現有檔案相對應的FileStream物件。
當MemoryMappedFile物件建立之後,我們並不能直接對其進行讀寫,必須通過一個MemoryMappedViewAccessor物件來訪問這個記憶體對映檔案。MemoryMappedFile. CreateViewAccessor()方法可以建立MemoryMappedViewAccessor物件,而此物件提供了一系列讀寫的方法,用於向記憶體對映檔案中讀取和寫入資料。
在建立記憶體對映檔案訪問物件需要指定它所能訪問的記憶體對映檔案的內容範圍,這個“範圍”稱為“記憶體對映檢視(Memory Mapped View)”。可以將它與“放大鏡”類比,當使用一個放大鏡閱讀書籍時,一次只能放大指定部分的文字。類似地,我們只能在記憶體對映檢視所規定的範圍內存取記憶體對映檔案。
如果要向記憶體對映檔案中序列化物件,必須將記憶體對映檔案轉換為可順序讀取的流。幸運的是,MemoryMappedFile類的CreateViewStream()方法可以建立一個MemoryMappedViewStream物件,通過它即可序列化物件。這個物件允許序列訪問對映檢視;這個可能是使用對映檢視流(mapped view streams)與使用允許隨即訪問的accessor物件相比的最大缺點。

A quick (low-latency) IPC channel for .NET (Using MemoryMappedFile and Event)
https://github.com/geffzhang/QuickIPC
相關文章: