C# 基于MemoryMappedFile实现进程间通信(服务端+客户端完整范例)
在C#开发中,进程间通信(IPC)是高频需求,常见方案包含管道、Socket、消息队列等。而MemoryMappedFile(内存映射文件) 凭借内存级别的读写性能,成为高吞吐量IPC场景的优选方案。本文将从零实现基于MemoryMappedFile的服务端-客户端通信范例,涵盖核心API使用、数据交互逻辑及异常处理,帮助你快速掌握该技术。
一、MemoryMappedFile核心概念
MemoryMappedFile本质是将文件(或一段内存)映射到进程的虚拟地址空间,多个进程可通过同一映射名称访问同一块内存区域,实现数据共享。核心优势:
- 读写性能接近原生内存操作,无频繁IO开销;
- 无需额外网络/管道协议封装,代码轻量化;
- 支持跨进程、跨会话(需权限配置)通信。
二、实现思路
- 服务端:创建命名内存映射文件,开辟固定大小内存区域,持续监听客户端写入的数据,并给出响应;
- 客户端:连接服务端创建的内存映射文件,写入请求数据,等待并读取服务端响应;
- 数据交互:定义简单的数据格式(请求-响应),通过内存区域的字节数组完成数据传输;
- 同步机制:使用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. 编译运行
- 创建C# 控制台应用(.NET Framework/.NET Core/.NET 5+均可);
- 将上述3个文件(Shared.cs、Server.cs、Client.cs)添加到项目;
- 分别将Server和Client设置为启动项,编译生成两个可执行文件。
2. 测试流程
-
先运行服务端 ,控制台输出:
===== 内存映射文件服务端 ===== 服务端已启动,等待客户端请求... 按任意键退出 -
再运行客户端 ,输入任意内容(如"Hello MemoryMappedFile"),客户端输出:
===== 内存映射文件客户端 ===== 请输入要发送的请求(输入exit退出): Hello MemoryMappedFile 已发送请求:Hello MemoryMappedFile 收到服务端响应:服务端响应:[2024-01-21 15:30:00] 已处理请求:Hello MemoryMappedFile -
服务端同步显示:
收到客户端请求:Hello MemoryMappedFile 发送响应:服务端响应:[2024-01-21 15:30:00] 已处理请求:Hello MemoryMappedFile
五、关键注意事项
- 命名唯一性 :
MapName和MutexName需全局唯一,建议添加项目专属前缀,避免与系统其他进程冲突; - 权限问题 :若跨用户会话通信,需为MemoryMappedFile和Mutex配置足够的权限(可通过
MemoryMappedFileSecurity设置); - 内存大小 :
MemorySize需根据实际传输数据大小设定,过小会导致数据截断,过大则浪费内存; - 同步机制:必须使用Mutex/Semaphore等同步原语,否则多进程同时读写会导致数据错乱;
- 异常处理 :客户端需捕获
FileNotFoundException(服务端未启动),服务端需处理线程中断、权限不足等异常。
六、扩展场景
- 大数据传输:可将内存区域划分为"指令区+数据区",通过指令区标记数据长度、类型,实现分片传输;
- 多客户端通信:服务端可维护客户端标识,通过内存分区实现一对多通信;
- 持久化映射 :使用
MemoryMappedFile.CreateFromFile将内存映射到物理文件,实现数据持久化。
总结
- MemoryMappedFile通过内存映射实现高性能进程间通信,核心是创建共享内存区域并通过互斥锁保证读写同步;
- 服务端负责创建内存映射文件和互斥锁,监听并处理客户端请求;客户端连接共享内存,发送请求并读取响应;
- 使用时需注意命名唯一性、内存大小配置、同步机制及异常处理,避免数据竞争和进程冲突。
该范例可直接用于生产环境的基础IPC场景,根据实际需求扩展数据格式、同步策略和异常处理即可。