C# 基于MemoryMappedFile实现进程间通信(服务端+客户端完整范例)

C# 基于MemoryMappedFile实现进程间通信(服务端+客户端完整范例)

在C#开发中,进程间通信(IPC)是高频需求,常见方案包含管道、Socket、消息队列等。而MemoryMappedFile(内存映射文件) 凭借内存级别的读写性能,成为高吞吐量IPC场景的优选方案。本文将从零实现基于MemoryMappedFile的服务端-客户端通信范例,涵盖核心API使用、数据交互逻辑及异常处理,帮助你快速掌握该技术。

一、MemoryMappedFile核心概念

MemoryMappedFile本质是将文件(或一段内存)映射到进程的虚拟地址空间,多个进程可通过同一映射名称访问同一块内存区域,实现数据共享。核心优势:

  • 读写性能接近原生内存操作,无频繁IO开销;
  • 无需额外网络/管道协议封装,代码轻量化;
  • 支持跨进程、跨会话(需权限配置)通信。

二、实现思路

  1. 服务端:创建命名内存映射文件,开辟固定大小内存区域,持续监听客户端写入的数据,并给出响应;
  2. 客户端:连接服务端创建的内存映射文件,写入请求数据,等待并读取服务端响应;
  3. 数据交互:定义简单的数据格式(请求-响应),通过内存区域的字节数组完成数据传输;
  4. 同步机制:使用Mutex(互斥锁)保证多进程读写的线程安全,避免数据竞争。

三、完整代码实现

1. 通用常量与工具类(Shared.cs)

先定义通信所需的通用常量和字节数组与字符串的转换工具,避免服务端/客户端代码冗余:

csharp 复制代码
using System;
using System.Text;

namespace MemoryMappedFileDemo
{
    /// <summary>
    /// 通信通用常量与工具类
    /// </summary>
    public static class Shared
    {
        // 内存映射文件唯一标识(全局唯一,避免冲突)
        public const string MapName = "MyMemoryMappedFile_2024";
        // 互斥锁名称(用于进程间同步)
        public const string MutexName = "MyMemoryMappedFile_Mutex_2024";
        // 内存区域大小(字节),根据实际需求调整
        public const int MemorySize = 4096;
        // 数据分隔符(区分请求和响应)
        public const string Separator = "|";

        /// <summary>
        /// 字符串转字节数组(统一编码:UTF8)
        /// </summary>
        public static byte[] StringToBytes(string str)
        {
            if (string.IsNullOrEmpty(str))
                return Array.Empty<byte>();
            return Encoding.UTF8.GetBytes(str);
        }

        /// <summary>
        /// 字节数组转字符串
        /// </summary>
        public static string BytesToString(byte[] bytes)
        {
            if (bytes == null || bytes.Length == 0)
                return string.Empty;
            return Encoding.UTF8.GetString(bytes).Trim('\0'); // 去除内存填充的空字符
        }
    }
}

2. 服务端实现(Server.cs)

服务端负责创建内存映射文件,循环监听客户端请求并返回响应:

csharp 复制代码
using System;
using System.IO.MemoryMappedFiles;
using System.Threading;

namespace MemoryMappedFileDemo
{
    class Server
    {
        static void Main(string[] args)
        {
            Console.WriteLine("===== 内存映射文件服务端 =====");
            try
            {
                // 1. 创建内存映射文件(全局命名,允许其他进程访问)
                using (var mmf = MemoryMappedFile.CreateNew(Shared.MapName, Shared.MemorySize))
                // 2. 创建互斥锁(保证读写同步,初始拥有锁)
                using (var mutex = new Mutex(true, Shared.MutexName))
                {
                    Console.WriteLine("服务端已启动,等待客户端请求...");
                    Console.WriteLine("按任意键退出");

                    // 循环监听请求
                    while (!Console.KeyAvailable)
                    {
                        // 释放互斥锁,允许客户端写入数据
                        mutex.ReleaseMutex();
                        // 短暂休眠,避免CPU空转
                        Thread.Sleep(100);
                        // 重新获取互斥锁,准备读取数据
                        mutex.WaitOne();

                        // 3. 创建内存访问器(读写权限)
                        using (var accessor = mmf.CreateViewAccessor(0, Shared.MemorySize))
                        {
                            // 读取客户端写入的数据
                            byte[] buffer = new byte[Shared.MemorySize];
                            accessor.ReadArray(0, buffer, 0, buffer.Length);
                            string clientRequest = Shared.BytesToString(buffer);

                            // 如果有有效请求,处理并返回响应
                            if (!string.IsNullOrEmpty(clientRequest))
                            {
                                Console.WriteLine($"\n收到客户端请求:{clientRequest}");
                                
                                // 模拟业务处理
                                string serverResponse = $"服务端响应:[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 已处理请求:{clientRequest}";
                                
                                // 清空内存区域,避免残留数据
                                Array.Clear(buffer, 0, buffer.Length);
                                // 将响应写入内存
                                byte[] responseBytes = Shared.StringToBytes(serverResponse);
                                accessor.WriteArray(0, responseBytes, 0, responseBytes.Length);
                                
                                Console.WriteLine($"发送响应:{serverResponse}");
                            }
                        }
                    }

                    // 释放互斥锁(退出前)
                    mutex.ReleaseMutex();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"服务端异常:{ex.Message}");
            }
            Console.WriteLine("服务端已退出");
        }
    }
}

3. 客户端实现(Client.cs)

客户端连接服务端创建的内存映射文件,发送请求并读取响应:

csharp 复制代码
using System;
using System.IO.MemoryMappedFiles;
using System.Threading;

namespace MemoryMappedFileDemo
{
    class Client
    {
        static void Main(string[] args)
        {
            Console.WriteLine("===== 内存映射文件客户端 =====");
            try
            {
                // 1. 连接已存在的内存映射文件(服务端创建的)
                using (var mmf = MemoryMappedFile.OpenExisting(Shared.MapName))
                // 2. 获取互斥锁(同步读写)
                using (var mutex = Mutex.OpenExisting(Shared.MutexName))
                {
                    Console.WriteLine("请输入要发送的请求(输入exit退出):");
                    string input;
                    while ((input = Console.ReadLine()) != "exit")
                    {
                        if (string.IsNullOrEmpty(input))
                        {
                            Console.WriteLine("请求内容不能为空,请重新输入");
                            continue;
                        }

                        // 3. 获取互斥锁,准备写入请求
                        mutex.WaitOne();
                        try
                        {
                            // 创建内存访问器
                            using (var accessor = mmf.CreateViewAccessor(0, Shared.MemorySize))
                            {
                                // 清空内存区域
                                byte[] emptyBuffer = new byte[Shared.MemorySize];
                                accessor.WriteArray(0, emptyBuffer, 0, emptyBuffer.Length);

                                // 写入请求数据
                                byte[] requestBytes = Shared.StringToBytes(input);
                                accessor.WriteArray(0, requestBytes, 0, requestBytes.Length);
                                Console.WriteLine($"已发送请求:{input}");
                            }
                        }
                        finally
                        {
                            // 释放互斥锁,允许服务端处理
                            mutex.ReleaseMutex();
                        }

                        // 等待服务端响应(短暂休眠,模拟等待处理)
                        Thread.Sleep(500);

                        // 4. 重新获取锁,读取响应
                        mutex.WaitOne();
                        try
                        {
                            using (var accessor = mmf.CreateViewAccessor(0, Shared.MemorySize))
                            {
                                byte[] responseBuffer = new byte[Shared.MemorySize];
                                accessor.ReadArray(0, responseBuffer, 0, responseBuffer.Length);
                                string serverResponse = Shared.BytesToString(responseBuffer);
                                Console.WriteLine($"收到服务端响应:{serverResponse}\n");
                            }
                        }
                        finally
                        {
                            mutex.ReleaseMutex();
                        }
                    }
                }
            }
            catch (FileNotFoundException)
            {
                Console.WriteLine("错误:未找到服务端创建的内存映射文件,请先启动服务端!");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"客户端异常:{ex.Message}");
            }
            Console.WriteLine("客户端已退出");
        }
    }
}

四、运行步骤与效果

1. 编译运行

  1. 创建C# 控制台应用(.NET Framework/.NET Core/.NET 5+均可);
  2. 将上述3个文件(Shared.cs、Server.cs、Client.cs)添加到项目;
  3. 分别将Server和Client设置为启动项,编译生成两个可执行文件。

2. 测试流程

  1. 先运行服务端 ,控制台输出:

    复制代码
    ===== 内存映射文件服务端 =====
    服务端已启动,等待客户端请求...
    按任意键退出
  2. 再运行客户端 ,输入任意内容(如"Hello MemoryMappedFile"),客户端输出:

    复制代码
    ===== 内存映射文件客户端 =====
    请输入要发送的请求(输入exit退出):
    Hello MemoryMappedFile
    已发送请求:Hello MemoryMappedFile
    收到服务端响应:服务端响应:[2024-01-21 15:30:00] 已处理请求:Hello MemoryMappedFile
  3. 服务端同步显示:

    复制代码
    收到客户端请求:Hello MemoryMappedFile
    发送响应:服务端响应:[2024-01-21 15:30:00] 已处理请求:Hello MemoryMappedFile

五、关键注意事项

  1. 命名唯一性MapNameMutexName需全局唯一,建议添加项目专属前缀,避免与系统其他进程冲突;
  2. 权限问题 :若跨用户会话通信,需为MemoryMappedFile和Mutex配置足够的权限(可通过MemoryMappedFileSecurity设置);
  3. 内存大小MemorySize需根据实际传输数据大小设定,过小会导致数据截断,过大则浪费内存;
  4. 同步机制:必须使用Mutex/Semaphore等同步原语,否则多进程同时读写会导致数据错乱;
  5. 异常处理 :客户端需捕获FileNotFoundException(服务端未启动),服务端需处理线程中断、权限不足等异常。

六、扩展场景

  1. 大数据传输:可将内存区域划分为"指令区+数据区",通过指令区标记数据长度、类型,实现分片传输;
  2. 多客户端通信:服务端可维护客户端标识,通过内存分区实现一对多通信;
  3. 持久化映射 :使用MemoryMappedFile.CreateFromFile将内存映射到物理文件,实现数据持久化。

总结

  1. MemoryMappedFile通过内存映射实现高性能进程间通信,核心是创建共享内存区域并通过互斥锁保证读写同步;
  2. 服务端负责创建内存映射文件和互斥锁,监听并处理客户端请求;客户端连接共享内存,发送请求并读取响应;
  3. 使用时需注意命名唯一性、内存大小配置、同步机制及异常处理,避免数据竞争和进程冲突。

该范例可直接用于生产环境的基础IPC场景,根据实际需求扩展数据格式、同步策略和异常处理即可。

相关推荐
河码匠2 小时前
namespace 网络命名空间、使用网络命名空间实现虚拟路由
linux·网络
真的想上岸啊2 小时前
3、用SSH方式登录板子
linux
开开心心就好2 小时前
打印机驱动搜索下载工具,自动识别手动搜
java·linux·开发语言·网络·stm32·物联网·电脑
时光追逐者2 小时前
C#/.NET/.NET Core技术前沿周刊 | 第 66 期(2026年1.12-1.18)
c#·.net·.netcore
狂放不羁霸2 小时前
Tailscale | 校园网外笔记本借助校园网内主机连接实验室服务器
运维·服务器·vscode
海域云-罗鹏2 小时前
马来西亚工厂与内地数据中心SD-WAN组网全指南
服务器·网络
新兴AI民工2 小时前
【Linux内核十一】进程管理模块:stop调度器(一)
linux·服务器·linux内核
刘叨叨趣味运维2 小时前
Linux发行版选择指南:找到你的最佳拍档
linux
txinyu的博客2 小时前
解析muduo源码之 TimeZone.h & TimeZone.cc
linux·服务器·网络·c++