管理

C#中10种多进程之间的通讯方式

Posted on 2025-10-14 10:00  lzhdim  阅读(25)  评论(0)    收藏  举报

前言

在 C# 开发中,多线程通信对大多数开发者而言已是驾轻就熟——无论是 AsyncLocal、ThreadLocal,还是通过 CallContext 的 GetData/SetData 方法,都能信手拈来。然而,多进程通信(Inter-Process Communication, IPC)虽然应用场景广泛,但掌握程度普遍不如多线程通信深入。

本文将系统梳理 C# 中常见的多进程通信方式,不包含消息中间件(如 RabbitMQ、Kafka)、数据库、gRPC、WebSocket、SignalR 等高级通信手段,仅聚焦于 .NET Framework / .NET 中原生或基于 Windows 系统支持的 IPC 机制,并以 C# 代码示例说明。

⚠️ 注:本文示例基于 .NET Framework 编写,部分技术(如 .NET Remoting)在 .NET Core/.NET 5+ 中已被弃用,仅适用于传统 Windows 桌面应用。


1. 共享内存(Memory-Mapped Files)

共享内存是高性能进程间通信的常用方式。C# 提供了 MemoryMappedFile 类,封装了底层 Win32 API,使用简便。

服务端(写入数据)

// 使用 Accessor 方式写入
usingvar mmfAccessor = MemoryMappedFile.CreateNew("ProcessCommunicationAccessor", 500);
usingvar accessor = mmfAccessor.CreateViewAccessor();
byte[] helloBytes = Encoding.UTF8.GetBytes("Accessor");
accessor.WriteArray(0, helloBytes, 0, helloBytes.Length);
richTextBox1.Text += Environment.NewLine + "Accessor Send Val: Accessor";

// 使用 Stream 方式写入
usingvar mmfStream = MemoryMappedFile.CreateNew("ProcessCommunicationStream", 500);
usingvar stream = mmfStream.CreateViewStream();
byte[] streamBytes = Encoding.UTF8.GetBytes("Stream");
stream.Write(streamBytes, 0, streamBytes.Length);
richTextBox1.Text += Environment.NewLine + "Stream Send Val: Stream";

客户端(读取数据)

// 读取 Accessor 数据
usingvar mmfAccessor = MemoryMappedFile.OpenExisting("ProcessCommunicationAccessor");
usingvar accessor = mmfAccessor.CreateViewAccessor();
byte[] buffer = newbyte[500];
accessor.ReadArray(0, buffer, 0, buffer.Length);
string str = Encoding.UTF8.GetString(buffer).Trim('\0');
richTextBox1.Text += Environment.NewLine + "Accessor Read Val: " + str;

// 读取 Stream 数据
usingvar mmfStream = MemoryMappedFile.OpenExisting("ProcessCommunicationStream");
usingvar stream = mmfStream.CreateViewStream();
usingvar reader = new StreamReader(stream);
string streamStr = reader.ReadToEnd().Trim('\0');
richTextBox1.Text += Environment.NewLine + "Stream Read Val: " + streamStr;

💡 提示:建议使用 CreateOrOpen 避免因文件不存在导致异常。注意字符串末尾可能包含空字符(\0),需手动去除。


2. Windows 消息队列(MSMQ)

MSMQ 是 Windows 内置的消息队列服务,需手动启用(控制面板 → 程序和功能 → 启用或关闭 Windows 功能 → Microsoft Message Queue)。

服务端(发送消息)

using var queue = new MessageQueue(@".\Private$\MessageQueue");
queue.Send("Message HelloWorld");
richTextBox1.Text += Environment.NewLine + "MessageQueue Send Val: Message HelloWorld";

客户端(异步接收)

var context = WindowsFormsSynchronizationContext.Current;
var queue = new MessageQueue(@".\Private$\MessageQueue");
queue.Formatter = new XmlMessageFormatter(new Type[] { typeof(string) });

queue.ReceiveCompleted += (sender, e) =>
{
    var msg = queue.EndReceive(e.AsyncResult);
    string msgVal = (string)msg.Body;
    
    context.Send(_ => 
    {
        richTextBox1.Text += Environment.NewLine + "MessageQueue Read Val: " + msgVal;
    }, null);
    
    queue.BeginReceive(); // 继续监听
};

queue.BeginReceive();

⚠️ 注意:需确保队列存在,且客户端与服务端使用相同的格式化器。


3. 命名管道(Named Pipes)

位于 System.IO.Pipes 命名空间,支持本地或跨网络通信(需配置权限)。

服务端

var server = new NamedPipeServerStream("ProcessCommunicationPipe", 
    PipeDirection.InOut, 10, PipeTransmissionMode.Message, PipeOptions.Asynchronous);

await server.WaitForConnectionAsync();

// 接收客户端消息
byte[] buffer = newbyte[1024];
int bytesRead = await server.ReadAsync(buffer, 0, buffer.Length);
string msg = Encoding.UTF8.GetString(buffer, 0, bytesRead);
richTextBox1.Text += Environment.NewLine + "Server Receive Val: " + msg;

// 发送消息到客户端
byte[] data = Encoding.UTF8.GetBytes(textBox1.Text);
await server.WriteAsync(data, 0, data.Length);
richTextBox1.Text += Environment.NewLine + "Server Send Val: " + textBox1.Text;

客户端

var client = new NamedPipeClientStream(".", "ProcessCommunicationPipe", 
    PipeDirection.InOut, PipeOptions.Asynchronous);

await client.ConnectAsync();

// 接收服务端消息
byte[] buffer = newbyte[1024];
int bytesRead = await client.ReadAsync(buffer, 0, buffer.Length);
string msg = Encoding.UTF8.GetString(buffer, 0, bytesRead);
richTextBox1.Text += Environment.NewLine + "Client Receive Val: " + msg;

// 发送消息到服务端
byte[] data = Encoding.UTF8.GetBytes(textBox1.Text);
await client.WriteAsync(data, 0, data.Length);
richTextBox1.Text += Environment.NewLine + "Client Send Val: " + textBox1.Text;

💡 建议使用 async/await 替代 ContinueWith,代码更清晰。


4. 匿名管道(Anonymous Pipes)

仅适用于父子进程通信,不支持网络,且为单向通信。

服务端(父进程)

var server = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable);

var clientProcess = new Process
{
    StartInfo = new ProcessStartInfo
    {
        FileName = @"E:\...\ProcessCommunicationClient.exe",
        Arguments = server.GetClientHandleAsString(),
        UseShellExecute = false
    }
};
clientProcess.Start();
server.DisposeLocalCopyOfClientHandle(); // 重要!

byte[] buffer = newbyte[1024];
int bytesRead = await server.ReadAsync(buffer, 0, buffer.Length);
string msg = Encoding.UTF8.GetString(buffer, 0, bytesRead);
richTextBox1.Text += Environment.NewLine + "匿名 Server Receive Val: " + msg;

客户端(子进程)

// Program.Main(string[] args)
var client = new AnonymousPipeClientStream(PipeDirection.Out, args[0]);

byte[] data = Encoding.UTF8.GetBytes(textBox2.Text);
await client.WriteAsync(data, 0, data.Length);
richTextBox1.Text += Environment.NewLine + "匿名 Client Send Val: " + textBox2.Text;


5–7. .NET Remoting(IPC / HTTP / TCP)

⚠️ 警告:.NET Remoting 已在 .NET Core 中移除,仅适用于 .NET Framework。

三者用法高度一致,仅通道类型不同。

通用服务对象(需继承 MarshalByRefObject)

public class ProcessCommunicationIpc : MarshalByRefObject
{
    public string Name { get; private set; }
    public void SetName(string name) => Name = name;
}

IPC 服务端

var channel = new IpcChannel("127.0.0.1:8081");
ChannelServices.RegisterChannel(channel, true);
RemotingConfiguration.RegisterWellKnownServiceType(
    typeof(ProcessCommunicationIpc), "Ipc.rem", WellKnownObjectMode.Singleton);

IPC 客户端

var channel = new IpcChannel();
ChannelServices.RegisterChannel(channel, true);
var obj = new ProcessCommunicationIpc(); // 透明代理
obj.SetName(textBox3.Text);

服务端读取(轮询或事件)

var proxy = (ProcessCommunicationIpc)Activator.GetObject(
    typeof(ProcessCommunicationIpc), "ipc://127.0.0.1:8081/Ipc.rem");
string name = proxy.Name;

HTTP 和 TCP 仅需将 IpcChannel 替换为 HttpChannel/TcpChannel,URL 改为 http://... 或 tcp://...。


8. Socket 通信

最通用的通信方式,支持跨平台、跨网络。

服务端

var server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
server.Bind(new IPEndPoint(IPAddress.Loopback, 8084));
server.Listen(10);
server.BeginAccept(AcceptCallback, server);

void AcceptCallback(IAsyncResult ar)
{
    var serverSocket = (Socket)ar.AsyncState;
    var client = serverSocket.EndAccept(ar);
    // 启动接收线程
    var buffer = newbyte[1024];
    client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, client);
    serverSocket.BeginAccept(AcceptCallback, serverSocket);
}

void ReceiveCallback(IAsyncResult ar)
{
    var client = (Socket)ar.AsyncState;
    int bytesRead = client.EndReceive(ar);
    string msg = Encoding.UTF8.GetString(buffer, 0, bytesRead);
    // 更新 UI
}

客户端

var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
await client.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 8084));

// 发送
byte[] data = Encoding.UTF8.GetBytes(textBox6.Text);
await client.SendAsync(data, SocketFlags.None);

// 接收
byte[] buffer = new byte[1024];
int bytesRead = await client.ReceiveAsync(buffer, SocketFlags.None);
string msg = Encoding.UTF8.GetString(buffer, 0, bytesRead);


9. Win32 API:SendMessage

适用于 Windows 窗体应用,通过窗口消息传递数据。

服务端(重写 WndProc)

protected override void WndProc(ref Message m)
{
    const int WM_CUSTOM = 0x1050;
    if (m.Msg == WM_CUSTOM)
    {
        int wParam = (int)m.WParam;
        int lParam = (int)m.LParam;
        richTextBox1.Text += $"\nWin32 Msg: {wParam}, {lParam}";
    }
    base.WndProc(ref m);
}

客户端(发送消息)

[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

var process = Process.GetProcessesByName("ProcessCommunication").FirstOrDefault();
if (process?.MainWindowHandle != IntPtr.Zero)
{
    SendMessage(process.MainWindowHandle, 0x1050, (IntPtr)10, (IntPtr)20);
}

💡 可通过 Marshal 传递结构体,但需注意内存安全。


10. Mutex(互斥体)

用于进程间同步,确保同一时间仅一个进程访问临界区。

服务端 & 客户端(相同代码)

var mutex = new Mutex(false, "Global\\ProcessCommunicationMutex");

Task.Run(() =>
{
    while (true)
    {
        mutex.WaitOne(); // 进入临界区
        // 更新 UI
        mutex.ReleaseMutex(); // 释放
        Thread.Sleep(1000);
    }
});

🔒 建议使用 Global\ 前缀确保跨会话可见(尤其在 Windows 服务中)。


结语

本文介绍了 C# 中 10 种多进程通信方式,涵盖共享内存、管道、消息队列、Remoting、Socket、Win32 API 和同步原语。每种方式各有适用场景:

  • 高性能本地通信:共享内存、命名管道;
  • 可靠异步通信:MSMQ;
  • 通用网络通信:Socket;
  • 简单同步控制:Mutex;
  • 遗留系统集成:.NET Remoting(慎用)。

 

Copyright © 2000-2022 Lzhdim Technology Software All Rights Reserved