1. 命名管道 (Named Pipes)
推荐指数: ⭐⭐⭐⭐⭐ 适用场景: 本地机器上两个 C# 进程之间进行可靠、流式的数据交换。这是 Windows IPC 中最常用的方法之一。
命名管道是一种非常高效且可靠的 IPC 机制,它在本地机器上表现得像网络套接字,但配置和管理要简单得多。
工作原理
一个应用程序充当管道服务器 (NamedPipeServerStream),等待连接;另一个应用程序充当管道客户端 (NamedPipeClientStream),连接到该命名管道。数据可以双向传输。
C# 关键类
System.IO.Pipes.NamedPipeServerStreamSystem.IO.Pipes.NamedPipeClientStream
优点
- 可靠性高: 提供面向连接的可靠数据流。
- 配置简单: 只需要一个唯一的管道名称即可。
- 性能优秀: 专为本地 IPC 设计,速度快。
一.命名管道教程:服务器与客户端通信
准备工作
-
创建两个独立的 C# 控制台应用程序项目(例如,使用 .NET Core 或 .NET Framework)。
- 项目 A:命名为
PipeServer - 项目 B:命名为
PipeClient
- 项目 A:命名为
-
定义一个管道名称。这个名称必须在服务器和客户端之间完全一致。 PipeName="MySuperSecretPipe"PipeName="MySuperSecretPipe"
第一部分:管道服务器 (PipeServer 项目)
服务器的任务是创建管道,等待客户端连接,然后接收数据。
PipeServer 代码
将以下代码放入 PipeServer 项目的 Program.cs 文件中:
cs
using System.IO.Pipes;
using System;
using System.IO;
using System.Threading.Tasks;
namespace PipeServer
{
internal class Program
{
private const string PipeName = "MySuperSecretPipe";
static async Task Main(string[] args)
{
Console.WriteLine("--- 命名管道服务器启动 ---");
await RunServerAsync();
}
static async Task RunServerAsync()
{
// 1. 创建命名管道服务器
// PipeDirection.In 表示服务器只接收数据(对于双向通信,使用 PipeDirection.InOut)
using (var pipeServer = new NamedPipeServerStream(
PipeName,
PipeDirection.In,
1, // 最大实例数
PipeTransmissionMode.Byte,
PipeOptions.Asynchronous))
{
Console.WriteLine($"等待客户端连接到管道: {PipeName}...");
// 2. 等待客户端连接 (这是一个阻塞操作,但我们使用了 async/await)
await pipeServer.WaitForConnectionAsync();
Console.WriteLine("客户端已连接!开始读取数据...");
try
{
// 3. 使用 StreamReader 读取文本数据
using (var sr = new StreamReader(pipeServer))
{
string message;
// 循环读取数据,直到客户端断开连接或发送空消息
while ((message = await sr.ReadLineAsync()) != null)
{
Console.WriteLine($"[客户端消息]: {message}");
if (message.ToLower() == "exit")
{
Console.WriteLine("收到 'exit' 命令,断开连接。");
break;
}
}
}
}
catch (IOException ex)
{
// 客户端意外断开连接时会抛出异常
Console.WriteLine($"[错误或断开连接]: {ex.Message}");
}
finally
{
Console.WriteLine("客户端断开连接。");
// 如果需要等待新的客户端,可以在这里递归调用 RunServerAsync()
}
}
}
}
}
第二部分:管道客户端 (PipeClient 项目)
客户端的任务是连接到服务器创建的管道,然后发送数据。
PipeClient 代码
将以下代码放入 PipeClient 项目的 Program.cs 文件中:
cs
using System.IO.Pipes;
using System;
using System.IO;
using System.Threading.Tasks;
namespace PipeClient
{
internal class Program
{
private const string PipeName = "MySuperSecretPipe";
static async Task Main(string[] args)
{
Console.WriteLine("--- 命名管道客户端启动 ---");
await RunClientAsync();
}
static async Task RunClientAsync()
{
// 1. 创建命名管道客户端
// PipeDirection.Out 表示客户端只发送数据(对于双向通信,使用 PipeDirection.InOut)
using (var pipeClient = new NamedPipeClientStream(
".", // 服务器名称,"." 表示本机
PipeName,
PipeDirection.Out,
PipeOptions.Asynchronous))
{
try
{
Console.WriteLine($"尝试连接到服务器管道: {PipeName}...");
// 2. 连接到服务器 (设置超时时间为 5 秒)
await pipeClient.ConnectAsync(5000);
Console.WriteLine("连接成功!请输入消息 (输入 'exit' 退出):");
// 3. 使用 StreamWriter 写入文本数据
using (var sw = new StreamWriter(pipeClient))
{
// 确保数据立即发送
sw.AutoFlush = true;
while (true)
{
Console.Write("> ");
string input = Console.ReadLine();
if (string.IsNullOrEmpty(input)) continue;
// 4. 发送消息
await sw.WriteLineAsync(input);
if (input.ToLower() == "exit")
{
break;
}
}
}
}
catch (TimeoutException)
{
Console.WriteLine("连接超时,请确保服务器程序已启动。");
}
catch (Exception ex)
{
Console.WriteLine($"[错误]: {ex.Message}");
}
}
}
}
}
第三部分:运行和测试
-
启动服务器: 首先运行
PipeServer应用程序。它会显示消息等待客户端连接到管道: MySuperSecretPipe... -
启动客户端: 接着运行
PipeClient应用程序。- 如果连接成功,它会显示
连接成功!请输入消息... - 同时,
PipeServer会显示客户端已连接!开始读取数据...
- 如果连接成功,它会显示
-
发送消息: 在
PipeClient的控制台中输入消息并按回车。- 例如,输入
Hello Monica! PipeServer的控制台会立即显示:[客户端消息]: Hello Monica!
- 例如,输入
-
退出: 在
PipeClient中输入exit。- 客户端和服务器都会检测到退出命令并关闭连接。
这种模数是客户端向方面向服务端发送信息,服务器只能接收并读取,也称为单工或只读模数,不过我们还可以进一步优化进阶,实现双工模数,即两软件都可读可互发消息
二.进阶:实现双向通信
上面的例子是单向的(客户端发送,服务器接收)。要实现双向通信,您只需要做两处修改:
-
更改管道方向:
- 在服务器和客户端创建
NamedPipeStream时,将PipeDirection参数设置为PipeDirection.InOut。
- 在服务器和客户端创建
-
在每个应用程序中设置读写线程:
- 由于读操作(
ReadLineAsync)是阻塞的,您不能在同一个主线程中既等待读取又尝试写入。 - 在服务器和客户端,您需要启动一个单独的 Task 或 Thread 专门负责持续读取来自管道另一端的数据。主线程则可以负责用户输入和写入。
- 由于读操作(
示例(双向通信结构)
在 PipeServer 或 PipeClient 中,连接成功后,可以这样设置:
cs
// 假设 pipeStream 是 NamedPipeServerStream 或 NamedPipeClientStream
// 1. 启动一个后台任务持续监听读取
Task.Run(async () =>
{
using (var reader = new StreamReader(pipeStream))
{
string incomingMessage;
while ((incomingMessage = await reader.ReadLineAsync()) != null)
{
Console.WriteLine($"[收到]: {incomingMessage}");
}
}
});
// 2. 主线程负责写入
using (var writer = new StreamWriter(pipeStream))
{
writer.AutoFlush = true;
while (true)
{
Console.Write("发送 > ");
string outgoingMessage = Console.ReadLine();
await writer.WriteLineAsync(outgoingMessage);
}
}