C#.Net多进程同步通信共享内存内存映射⽂件
MemoryMapped
节点通信存在两种模型:共享内存(Shared memory)和消息传递(Messages passing)。
内存映射⽂件对于托管世界的开发⼈员来说似乎很陌⽣,但它确实已经是很远古的技术了,⽽且在操作系统中地位相当。实际上,任何想要共享数据的通信模型都会在幕后使⽤它。
内存映射⽂件究竟是个什么?内存映射⽂件允许你保留⼀块地址空间,然后将该物理存储映射到这块内存空间中进⾏操作。物理存储是⽂件管理,⽽内存映射⽂件是操作系统级内存管理。
优势:
1.访问磁盘⽂件上的数据不需执⾏I/O操作和缓存操作(当访问⽂件数据时,作⽤尤其显著);
2.让运⾏在同⼀台机器上的多个进程共享数据(单机多进程间数据通信效率最⾼);
利⽤⽂件与内存空间之间的映射,应⽤程序(包括多个进程)可以通过直接在内存中进⾏读写来修改⽂件。.NET Framework 4 ⽤托管代码按照本机Windows函数访问内存映射⽂件的⽅式来访问内存映射⽂件,。
有两种类型的内存映射⽂件:
持久内存映射⽂件
持久⽂件是与磁盘上的源⽂件关联的内存映射⽂件。在最后⼀个进程使⽤完此⽂件后,数据将保存到磁盘上的源⽂件中。这些内存映射⽂件适合⽤来处理⾮常⼤的源⽂件。
⾮持久内存映射⽂件
⾮持久⽂件是未与磁盘上的源⽂件关联的内存映射⽂件。当最后⼀个进程使⽤完此⽂件后,数据将丢失,并且垃圾回收功能将回收此⽂件。这些⽂件适⽤于为进程间通信 (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 = new Mutex(true, "testmapmutex", out mutexCreated);
using (var stream = mmf.CreateViewStream()) //创建⽂件内存视图流基于流的操作
{
var writer = new BinaryWriter(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 = new BinaryReader(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 = new BinaryWriter(stream);
writer.Write(0);
}
mutex.ReleaseMutex();
}
C# .Net  进程间通信共享内存完整⽰例: C#共享内存⾮持久化⽅式通讯的例⼦,通讯时的线程和进程控制也没有问题。如下是实现的代码。
先启动消息服务IMServer_Message,
再启动状态服务IMServer_State,
IMServer_Message回车⼀次(创建共享内存公⽤名和公⽤线程锁,并视图流⽅式写共享内存),
IMServer_State回车⼀次(获取共享内存并视图流⽅式写、视图访问器写⼊结构体类型)
并⽴刻IMServer_Message再回车⼀次(读取刚刚写⼊的信息),
观察IMServer_State屏显变化并等待(线程锁)约5s(线程锁被释放)后
在IMServer_Message上观察屏显(显⽰刚刚写⼊共享内存的信息)
代码
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();
}
}
}
代码
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();
}
}
}