共享内存是一种通信效率非常高的进程通信方案,在某些场景下非常适用。如:跨进程间播放音视频资源。

在.NET中,MemoryMappedFile就可以帮助我们完成这项工作。

共享内存打开与创建

我们可以通过MemoryMappedFile.OpenExisting来打开一个已存在的共享内存块,mapName作为唯一标识。如果打开的内存块标识不存在,则会抛出异常。

var memoryMappedFile = MemoryMappedFile.OpenExisting(mapName);

我们也可以通过MemoryMappedFile.CreateOrOpen来打开或者创建一个共享内存块,如果共享内存不存在则会自动创建一个。

var memoryMappedFile = MemoryMappedFile.CreateOrOpen(mapName, capacity, MemoryMappedFileAccess.ReadWrite);

共享内存数据读写

共享内存的数据读取可以有两种方式:

  1. 通过流式读写

我们可以通过创建一个内存视图流来进行读写。基本使用方法和传统Stream类是一样的。


var streamWriter = _memoryMappedFile.CreateViewStream()

  1. 随机访问

随机访问的方式相对来讲会更灵活,但是需要自己控制数据读写的位置。可以通过传入的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;
            }
        }
    }

共享内存的优缺点

优点:

  1. 高效性:共享内存允许多个进程直接访问同一块内存区域,避免了数据复制的开销。这使得数据传输速度更快,适用于需要高性能和低延迟的应用程序。
  2. 简单性:使用共享内存进行通信相对简单,可以通过读写共享内存来实现进程之间的数据交换,而不需要复杂的通信协议或消息传递机制。
  3. 容量大:共享内存可以提供较大的数据容量,因为它利用系统内存资源而不受进程内存限制的约束。这使得共享内存适用于处理大规模数据集或大量数据的应用程序。
  4. 实时性:由于共享内存直接映射到物理内存中,因此可以实时地进行数据读写操作。这对于需要实时响应和数据同步的应用程序非常重要。

缺点:

  1. 同步问题:由于多个进程可以同时访问共享内存,因此需要采取适当的同步机制来确保数据的一致性和正确性。否则,可能会导致数据竞争和不确定的结果。
  2. 数据变更:数据写入没有高效的数据通知,必须业务方自己实现该逻辑。
  3. 资源占用:初始化的时候必须指定一个内存块大小,某些场景下可能会导致内存资源的浪费。

博客地址: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/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系