共享内存是一种通信效率非常高的进程通信方案,在某些场景下非常适用。如:跨进程间播放音视频资源。
在.NET中,MemoryMappedFile
就可以帮助我们完成这项工作。
共享内存打开与创建
我们可以通过MemoryMappedFile.OpenExisting
来打开一个已存在的共享内存块,mapName
作为唯一标识。如果打开的内存块标识不存在,则会抛出异常。
var memoryMappedFile = MemoryMappedFile.OpenExisting(mapName);
我们也可以通过MemoryMappedFile.CreateOrOpen
来打开或者创建一个共享内存块,如果共享内存不存在则会自动创建一个。
var memoryMappedFile = MemoryMappedFile.CreateOrOpen(mapName, capacity, MemoryMappedFileAccess.ReadWrite);
共享内存数据读写
共享内存的数据读取可以有两种方式:
- 通过流式读写
我们可以通过创建一个内存视图流来进行读写。基本使用方法和传统Stream
类是一样的。
var streamWriter = _memoryMappedFile.CreateViewStream()
- 随机访问
随机访问的方式相对来讲会更灵活,但是需要自己控制数据读写的位置。可以通过传入的position
来控制开始读写的位置。
var accessor = _memoryMappedFile.CreateViewAccessor();
基于流式读取的完整示例
public enum ShareMemoryChannelFlag
{
/// <summary>
/// 数据新写入的,尚未被读取
/// </summary>
Unread = 0,
/// <summary>
/// 数据已经被读取过
/// </summary>
Read = 1,
/// <summary>
/// 该内存块已经被关闭
/// </summary>
Close = 2,
}
/// <summary>
/// 共享内存管道
/// </summary>
internal class ShareMemoryChannel : IDisposable
{
/// <summary>
/// 以只读的模式初始化内存通道
/// </summary>
/// <param name="channelName"></param>
public ShareMemoryChannel(string channelName)
{
if (string.IsNullOrEmpty(channelName))
{
throw new ArgumentException("channelName invalid!");
}
ChannelName = channelName;
}
/// <summary>
/// 以可读可写的方式初始化内存通道
/// </summary>
/// <param name="channelName"></param>
/// <param name="capacity"></param>
public ShareMemoryChannel(string channelName, long capacity) : this(channelName)
{
//最小大小必须能容纳通信协议的字节大小
if (capacity > sizeof(int))
{
Capacity = capacity;
}
else
{
throw new ArgumentException("capacity must more then four!");
}
}
private MemoryMappedFile _memoryMappedFile;
/// <summary>
/// 管道名称
/// </summary>
public string ChannelName { get; }
/// <summary>
/// 最大容量
/// </summary>
public long Capacity { get; }
/// <summary>
/// 表示
/// </summary>
public bool IsCapacityValid
{
get
{
if (Capacity > 4)
{
return true;
}
return false;
}
}
/// <summary>
/// 是否已经释放
/// </summary>
public bool IsDisposed { get; private set; }
/// <summary>
/// 读取数据
/// </summary>
/// <returns></returns>
public byte[] Read(bool isReadOnlyChanged = true)
{
if (IsDisposed)
{
throw new InvalidOperationException("无法访问已释放的资源");
}
if (_memoryMappedFile == null)
{
//用来控制是不是可以写入数据。读写数据会存在数据同步问题,这里简单的示例,不做同步逻辑。
if (IsCapacityValid)
{
_memoryMappedFile = MemoryMappedFile.CreateOrOpen(ChannelName, Capacity, MemoryMappedFileAccess.ReadWrite);
}
else
{
_memoryMappedFile = MemoryMappedFile.OpenExisting(ChannelName);
}
}
if (_memoryMappedFile == null)
{
//net core 可以用 Array.Empty<Byte>()
return new byte[0];
}
using (var stream = _memoryMappedFile.CreateViewStream())
{
var flag = (ShareMemoryChannelFlag)stream.ReadByte();
bool isUnread = flag == ShareMemoryChannelFlag.Unread;
if (isUnread)
{
//将未读标记改成已读
stream.Position = 0;
stream.WriteByte((int)ShareMemoryChannelFlag.Read);
}
if (!isReadOnlyChanged || (isUnread && isReadOnlyChanged))
{
var len = sizeof(int);
byte[] bufferSize = new byte[len];
stream.Read(bufferSize, 0, len);
var length = BitConverter.ToInt32(bufferSize, 0);
var buffer = new byte[length];
stream.Read(buffer, 0, length);
return buffer;
}
}
//net core 可以用 Array.Empty<Byte>()
return new byte[0];
}
/// <summary>
/// 写入数据
/// </summary>
/// <param name="bytes"></param>
public bool Write(byte[] bytes)
{
if (IsDisposed)
{
throw new InvalidOperationException("无法访问已释放的资源");
}
if (_memoryMappedFile == null)
{
_memoryMappedFile = MemoryMappedFile.CreateOrOpen(ChannelName, Capacity, MemoryMappedFileAccess.ReadWrite);
}
if (_memoryMappedFile == null || bytes == null || bytes.Length == 0)
{
return false;
}
using (var streamWriter = _memoryMappedFile.CreateViewStream())
{
streamWriter.WriteByte((int)ShareMemoryChannelFlag.Unread);
var bufferSize = BitConverter.GetBytes(bytes.Length);
streamWriter.Write(bufferSize, 0, bufferSize.Length);
streamWriter.Write(bytes, 0, bytes.Length);
streamWriter.Flush();
return true;
}
}
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
if (_memoryMappedFile != null)
{
_memoryMappedFile.Dispose();
_memoryMappedFile = null;
IsDisposed = true;
}
}
}
共享内存的优缺点
优点:
- 高效性:共享内存允许多个进程直接访问同一块内存区域,避免了数据复制的开销。这使得数据传输速度更快,适用于需要高性能和低延迟的应用程序。
- 简单性:使用共享内存进行通信相对简单,可以通过读写共享内存来实现进程之间的数据交换,而不需要复杂的通信协议或消息传递机制。
- 容量大:共享内存可以提供较大的数据容量,因为它利用系统内存资源而不受进程内存限制的约束。这使得共享内存适用于处理大规模数据集或大量数据的应用程序。
- 实时性:由于共享内存直接映射到物理内存中,因此可以实时地进行数据读写操作。这对于需要实时响应和数据同步的应用程序非常重要。
缺点:
- 同步问题:由于多个进程可以同时访问共享内存,因此需要采取适当的同步机制来确保数据的一致性和正确性。否则,可能会导致数据竞争和不确定的结果。
- 数据变更:数据写入没有高效的数据通知,必须业务方自己实现该逻辑。
- 资源占用:初始化的时候必须指定一个内存块大小,某些场景下可能会导致内存资源的浪费。
博客地址:https://huchengv5.github.io/
微信公众号:
欢迎转载分享,如若转载,请标注署名。
本文会经常更新,请阅读原文: https://huchengv5.gitee.io//post/NET-%E5%85%B1%E4%BA%AB%E5%86%85%E5%AD%98%E7%9A%84%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名胡承(包含链接: https://huchengv5.gitee.io/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。