.NET 4.0中使用内存映射文件实现进程通讯
操作系统很早就开始使用内存映射文件(Memory Mapped File)来作为进程间的共享存储区,这是一种非常高效的进程通讯手段。Win32 API中也包含有创建内存映射文件的函数,然而,这些函数都运行于非托管环境下,在.NET中只能通过平台调用机制来使用它们,用起来很不方便。幸运的是,.NET 4.0新增加了一个System.IO. MemoryMappedFiles命名空间,其中添加了几个类和相应的枚举类型,从而使我们可以很方便地创建内存映射文件。
1 内存映射文件原理
所谓内存映射文件,其实就是在内存中开辟出一块存放数据的专用区域,这区域往往与硬盘上特定的文件相对应。进程将这块内存区域映射到自己的地址空间中,访问它就象是访问普通的内存一样。
图 1 内存映射文件原理图
在.NET中,使用MemoryMappedFile对象表示一个内存映射文件,通过它的CreateFromFile()方法根据磁盘现有文件创建内存映射文件,调用这一方法需要提供一个与磁盘现有文件相对应的FileStream对象。
以下示例代码动态创建一个MyFile.dat文件,然后将其映射到系统内存中,设定容量为1M:
FileStream fs = new FileStream("MyFile.dat", FileMode.Create,
FileAccess.ReadWrite);
MemoryMappedFile memoryFile = MemoryMappedFile.CreateFromFile(fs, "MyFile", 1024*1024);
注意用于创建内存映射文件的文件流必须是可读写的。
扩充阅读:
关于内存映射文件的容量
默认情况下,在调用MemoryMappedFile.CreateFromFile()方法时如果不指定文件容量,那么,创建的内存映射文件的容量等同于文件的大小。
在上面的示例代码中,由于磁盘文件是临时生成的,其长度为0,所以,必须在创建内存映射文件时同时指定其容量。
在设定内存映射文件的容量时,其值不能小于磁盘文件的现有长度,但可以比它大。但要注意这将导致一个戏剧化的结果:磁盘文件自动增长到声明的容量大小!
可以多次调用MemoryMappedFile.CreateFromFile(),每次传给它一个更大的容量数值以不断扩充磁盘文件的大小。
当不再使用一个MemoryMappedFile对象时,注意应该及时地调用其Dispose()方法释放它所占有的系统资源。因为MemoryMappedFile实际上对应着运行操作系统核心的核心对象,如果不及时关闭,会造成操作系统核心资源(比如句柄)的浪费,要等到MemoryMappedFile对象被CLR垃圾回收,或者整个进程中止时,这些资源才会被操作系统回收再利用。
另外,内存映射文件的容量其实是指最大允许分配给内存映射文件的内存存储区字节数,并不意味着系统会马上分配指定容量的内存。进程中访问这块映射到磁盘文件中的存储区时,操作系统如果发现其内容还未装入内存,就会从磁盘文件中装入相应内容到内存中。因此,不用担心声明一个大的内存映射文件容量会导致内存的浪费。
当MemoryMappedFile对象创建之后,我们并不能直接对其进行读写,必须通过一个MemoryMappedViewAccessor对象来访问这个内存映射文件。
MemoryMappedFile. CreateViewAccessor()方法可以创建MemoryMappedViewAccessor对象,而此对象提供了一系列读写的方法,用于向内存映射文件中读取和写入数据。
以下示例代码创建了一个内存映射文件访问对象并使用它写入数据:
FileStream fs =…; //创建FileStream对象
MemoryMappedFile memoryFile=…; //创建内存映射文件
//创建内存映射文件访问对象
MemoryMappedViewAccessor accessor=
memoryFile.CreateViewAccessor(0, 1024);
for (int i = 0; i < 1024; i+=2)
accessor.Write(i, ‘c’);
上述代码中要注意,在创建内存映射文件访问对象需要指定它所能访问的内存映射文件的内容范围,这个“范围”称为“内存映射视图(Memory Mapped View)”。可以将它与“放大镜”类比,当使用一个放大镜阅读书籍时,一次只能放大指定部分的文字。类似地,我们只能在内存映射视图所规定的范围内存取内存映射文件。
在上述代码中,我们看到内存映射视图对象accessor只提取了内存映射文件开头1024个字节的内容,然后,向其中写入了512个“c”字符。
当调用内存映射视图对象的Write()方法时,需要指明从哪个位置(即方法的第一个参数)开始写入数据,并且需要计算清楚要写入的数据占几个字节,这样,当写入下一个数据时,就知道应该从哪个位置开始。
注意,Write()方法中的位置是相对视图对象而非内存映射文件本身,因此,此位置数值再加上视图距内存映射文件开头的位置数据才是写入的数据在文件中的真实位置。
Write()方法有多个重载形式,可以向内存映射文件中写入多种类型的数据,但要注意计算清楚其写入的位置,避免造成数据覆盖问题。
类似地,内存映射视图对象提供了多个重载的Read()方法,可以从内存映射文件中读取数据。
比较有趣的是,在同一个进程中可以针对同一个内存映射文件创建多个视图对象,从而允许我们同时修改同一个文件的不同部分,在关闭视图对象时由操作系统保证将所有修改都写回到原始文件中。
下面我们来看一个示例。
2 在同一进程内同时读写同一内存映射文件
示例项目UseMMFInProcess运行时会在程序的当前目录下创建一个“MyFile.dat”文件,然后,创建了两个内存映射视图对象,分别向文件的前半部分和后半部分写入不同的数据,然后再从中读出来(图 2)。
图 2 示例项目UseMMFInProcess
这个示例展示的技术很基础,请读者自行查看源码。
3 使用内存映射文件在进程间传送值类型数据
在前面的例子中,内存映射文件直接与某个特定的磁盘文件相对应,事实上,我们也可以不用创建磁盘文件而直接使用Windows的分页文件。这种方式是实现进程间互传数据的典型方式。
调用MemoryMappedFile.CreateNew()或MemoryMappedFile.CreateOrOpen()方法可以在系统内存(System Memory)中直接创建一个内存映射文件,这个内存映射文件所对应的“物理文件”是Windows的系统分页文件。两个方法都需要给映射文件指定一个唯一的名称。不同之处在于CreateOrOpen ()方法在指定名称的映射文件存在时就直接将其返回给进程,而CreateNew()方法始终是新创建一个内存映射文件。
扩充阅读:
Windows的系统分页文件和休眠文件
默认情况下,在安装Windows的分区根目录下,会找到两个具有“隐藏”属性的pagefile.sys和hiberfil.sys文件,前者(pagefile.sys)就是Windows的分页文件,用于保存从物理内存中换出的内存页,我们可以用它的一部分来创建内存映射文件。后者(hiberfil.sys)则是“系统休眠”文件,当Windows启用了休眠功能时,就会在硬盘上找到这个文件,它的内容是系统休眠时物理内存中的数据,当计算机从休眠中“醒”过来时,通过从此文件中加载信息以恢复上次工作的状态。
内存映射文件创建好以后,可以如同前面介绍的方法一样创建视图对象,然后使用Read和Write系列方法存取。
只要指定同一个名字,那么,多个进程就可以使用同一个内存映射文件交换数据。示例UseMMFBetweenProcess展示了在两个进程间相互交换一个结构体变量的情况:
图 3 示例项目UseMMFBetweenProcess
两个进程要交换的数据格式如下:
public struct MyStructure
{
public int IntValue
{ get; set; }
public float FloatValue
{ get; set; }
}
启动UseMMFBetweenProcess程序的两个实例,在其中一个窗体上输入两个数字之后,点击“保存”按钮,然后在另一个进程的窗体上点击“提取”,可以看到另一个进程写入的信息出现在本进程的文本框中。
示例程序采用MemoryMappedFile.CreateOrOpen()方法创建或打开一个内存映射文件,然后调用MemoryMappedViewAccessor类的泛型方法Write<T>()和Read<T>()向内存映射文件中写入和读取数据。
注意,泛型方法Write<T>()和Read<T>()中的泛型参数T必须是值类型(比如整型int和结构体struct),特别地,对于用户自定义的结构体,要求其成员也必须是值类型。
例如,以下结构体将无法写入到内存映射文件中,因为其成员Info是string类型的,这是一个引用类型。
public struct ErrorStruct
{
public string Info;
}
之所以要求泛型参数不能是引用类型,其道理非常简单,如果结构体中的某个成员是引用类型,那么在程序运行时,计算机无法知道应该向内存映射文件中写入多少个字节,因为引用类型的变量所引用的对象位于托管堆中,其占用存储空间的大小不经过计算是难以确定的,而完成这个计算工作将花费不少的系统资源(想想一个对象可能又会引用到另一个对象就明白了),这会严重影响内存映射文件读写操作效率。
两个进程不能交换引用类型的数据,这个限制似乎还不小,但事实上,我们完成可以通过对象序列化技术来突破这个限制,在两个进程间交换任意大小的对象(只要内存映射文件有足够的容量)。请看下一小节的示例UseMMFBetweenProcess2。
4 利用序列化技术通过内存映射文件实现进程通讯
图4 示例:UseMMFBetweenProcess2
如图 4所示,运行示例程序的多个实例,加载图片并输入图片说明,点击相应按钮后,可以在多个进程间直接交换以下格式的信息:
[Serializable]
class MyPic
{
public Image pic; //图片
public string picInfo; //图片信息说明
}
请注意这是一个引用类型的数据对象,并且它附加了可序列化“[Serializable]”的代码属性。
如果要向内存映射文件中序列化对象,必须将内存映射文件转换为可顺序读取的流。幸运的是,MemoryMappedFile类的CreateViewStream()方法可以创建一个MemoryMappedViewStream对象,通过它即可序列化对象,其代码框架如下:
//创建或打开内存映射文件
MemoryMappedFile memoryFile = MemoryMappedFile.CreateOrOpen(...);
//创建内存映射流
MemoryMappedViewStream stream = memoryFile.CreateViewStream();
//创建要在进程间交换的信息对象
MyPic obj =...;
//向内存映射流中序列化对象
IFormatter formatter = new BinaryFormatter();
stream.Seek(0, SeekOrigin.Begin);
formatter.Serialize(stream, obj);
请读者自行阅读源码了解更多技术细节。
=================================================
原文地址:http://blog.csdn.net/bitfan/article/details/4438458
.NET 4.0中使用内存映射文件实现进程通讯的更多相关文章
- C++中使用内存映射文件处理大文件
引言 文件操作是应用程序最为基本的功能之一,Win32 API和MFC均提供有支持文件处理的函数和类,常用的有Win32 API的CreateFile().WriteFile().ReadFile() ...
- C#内存映射文件学习[转]
原文链接 内存映射文件是由一个文件到进程地址空间的映射. C#提供了允许应用程序把文件映射到一个进程的函(MemoryMappedFile.CreateOrOpen).内存映射文件与虚拟内存有些类似, ...
- Windows进程间通讯(IPC)----内存映射文件
内存映射文件原理 内存映射文件是通过在虚拟地址空间中预留一块区域,然后通过从磁盘中已存在的文件为其调度物理存储器,访问此虚拟内存空间就相当于访问此磁盘文件了. 内存映射文件实现过程 HANDLE hF ...
- C# .Net 多进程同步 通信 共享内存 内存映射文件 Memory Mapped 转 VC中进程与进程之间共享内存 .net环境下跨进程、高频率读写数据 使用C#开发Android应用之WebApp 分布式事务之消息补偿解决方案
C# .Net 多进程同步 通信 共享内存 内存映射文件 Memory Mapped 转 节点通信存在两种模型:共享内存(Shared memory)和消息传递(Messages passing). ...
- 内存映射文件详解-----C++实现
先不说内存映射文件是什么.贴个代码先,. #include <iostream> #include <fcntl.h> #include <io.h> #inclu ...
- MemoryMappedFile 内存映射文件 msdn
http://msdn.microsoft.com/zh-cn/library/dd997372%28v=vs.110%29.aspx 内存映射文件 .NET Framework 4.5 其他版本 1 ...
- C# .Net 多进程同步 通信 共享内存 内存映射文件 Memory Mapped 转
原文:C# .Net 多进程同步 通信 共享内存 内存映射文件 Memory Mapped 转 节点通信存在两种模型:共享内存(Shared memory)和消息传递(Messages passing ...
- C# .Net 多进程同步 通信 共享内存 内存映射文件 Memory Mapped
节点通信存在两种模型:共享内存(Shared memory)和消息传递(Messages passing). 内存映射文件对于托管世界的开发人员来说似乎很陌生,但它确实已经是很远古的技术了,而且在操作 ...
- 内存映射文件(Memory-Mapped File)
Java Memory-Mapped File所使用的内存分配在物理内存而不是JVM堆内存,且分配在OS内核. 1: 内存映射文件及其应用 - 实现一个简单的消息队列 / 计算机程序的思维逻辑 在一般 ...
随机推荐
- Myeclipse 工具优化 [内存一直增加, jsp,javascript 编辑很卡]
首先看这篇随笔 地址: Myeclipse/STS 首次在本地部署配置一个Spring MVC 项目 (十二) [http://www.cnblogs.com/editor/p/3915239.htm ...
- C# p2p UDP穿越NAT,UDP打洞源码
思路如下(参照源代码): 1. frmServer启动两个网络侦听,主连接侦听,协助打洞的侦听. 2. frmClientA和frmClientB分别与frmServer的主连接保持联系. 3. 当f ...
- 分模块开发创建service子模块——(八)
1.右击父工程新建maven子模块
- [WC2008]游览计划 「斯坦那树模板」
斯坦那树 百度释义 斯坦纳树问题是组合优化问题,与最小生成树相似,是最短网络的一种.最小生成树是在给定的点集和边中寻求最短网络使所有点连通.而最小斯坦纳树允许在给定点外增加额外的点,使生成的最短网络开 ...
- .net4.0切换2.0时,SplitContainer”的对象强制转换为类型
问 题:将dotnet framework 4.0 切换到2.0时,编译没有问题,在运行时出现如下错误:System.InvalidCastException: 无法将类型为“System.Windo ...
- java判断部署项目使用的服务器类型
有两种方式 1.项目引入portal-kernel.jar.项目运行时使用 根据返回boolean值判断类型! 2.自己写java类 package webService.ZFGX.service; ...
- centos 6.8 启动损坏修复实验
前两天遇到了一个问题,centos必须借助CD的启动才能进入系统,当时想着做个测试,这会儿正好有时间,在VMWare里面试试. 思想是这样的,删除boot里面的文件,然后重启看效果. 具体过程如下: ...
- 026 Spark 的官网(版本为1.6.1的总官网)
1. 多多读官网,所有的只知识点都可以从上面的总纲中查到.
- Java之路(二) 操作符
操作符比较简单,这里只点一下注意事项和要点,请牢记. 操作符接受一个或多个参数,并生成一个新值. Java中,几乎所有的操作符都只能操作基本类型. 例外是 = == 和 !=,它们可以操作所有的对象. ...
- 【Android Studio】为Android Studio设置HTTP代理
[Android Studio]为Android Studio设置HTTP代理 大陆的墙很厚很高,初次安装Android Studio下载SDK等必定失败,设置代理方法如下: 1. 到androi ...