018.C#管道服务,本机两软件间通讯交互

1. 命名管道 (Named Pipes)

推荐指数: ⭐⭐⭐⭐⭐ 适用场景: 本地机器上两个 C# 进程之间进行可靠、流式的数据交换。这是 Windows IPC 中最常用的方法之一。

命名管道是一种非常高效且可靠的 IPC 机制,它在本地机器上表现得像网络套接字,但配置和管理要简单得多。

工作原理

一个应用程序充当管道服务器 (NamedPipeServerStream),等待连接;另一个应用程序充当管道客户端 (NamedPipeClientStream),连接到该命名管道。数据可以双向传输。

C# 关键类

  • System.IO.Pipes.NamedPipeServerStream
  • System.IO.Pipes.NamedPipeClientStream

优点

  • 可靠性高: 提供面向连接的可靠数据流。
  • 配置简单: 只需要一个唯一的管道名称即可。
  • 性能优秀: 专为本地 IPC 设计,速度快。

一.命名管道教程:服务器与客户端通信

准备工作

  1. 创建两个独立的 C# 控制台应用程序项目(例如,使用 .NET Core 或 .NET Framework)。

    • 项目 A:命名为 PipeServer
    • 项目 B:命名为 PipeClient
  2. 定义一个管道名称。这个名称必须在服务器和客户端之间完全一致。 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}");
                }
            }
        }
    }
}

第三部分:运行和测试

  1. 启动服务器: 首先运行 PipeServer 应用程序。它会显示消息 等待客户端连接到管道: MySuperSecretPipe...

  2. 启动客户端: 接着运行 PipeClient 应用程序。

    • 如果连接成功,它会显示 连接成功!请输入消息...
    • 同时,PipeServer 会显示 客户端已连接!开始读取数据...
  3. 发送消息:PipeClient 的控制台中输入消息并按回车。

    • 例如,输入 Hello Monica!
    • PipeServer 的控制台会立即显示:[客户端消息]: Hello Monica!
  4. 退出:PipeClient 中输入 exit

    • 客户端和服务器都会检测到退出命令并关闭连接。

这种模数是客户端向方面向服务端发送信息,服务器只能接收并读取,也称为单工或只读模数,不过我们还可以进一步优化进阶,实现双工模数,即两软件都可读可互发消息

二.进阶:实现双向通信

上面的例子是单向的(客户端发送,服务器接收)。要实现双向通信,您只需要做两处修改:

  1. 更改管道方向:

    • 在服务器和客户端创建 NamedPipeStream 时,将 PipeDirection 参数设置为 PipeDirection.InOut
  2. 在每个应用程序中设置读写线程:

    • 由于读操作(ReadLineAsync)是阻塞的,您不能在同一个主线程中既等待读取又尝试写入。
    • 在服务器和客户端,您需要启动一个单独的 Task 或 Thread 专门负责持续读取来自管道另一端的数据。主线程则可以负责用户输入和写入。

示例(双向通信结构)

PipeServerPipeClient 中,连接成功后,可以这样设置:

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);
    }
}
相关推荐
猫天意2 小时前
YOLOv11魔改高效涨点 | 注意力篇 | 坐标注意力CoordAttention:将位置信息硬核嵌入通道,精准捕获长程空间依赖,即插即用,涨点神器!!!
开发语言·人工智能·深度学习·神经网络·yolo·目标检测·低光照增强
黎雁·泠崖2 小时前
Java面向对象:this关键字+构造方法+标准JavaBean
java·开发语言·python
码小猿的CPP工坊2 小时前
C++弱引用智能指针std::weak_ptr使用介绍
开发语言·c++
sheji34162 小时前
【开题答辩全过程】以 基于Java的智慧环卫垃圾收运管理系统设计与实现为例,包含答辩的问题和答案
java·开发语言
Flash.kkl2 小时前
Linux——线程的同步和互斥
linux·开发语言·c++
sunfove2 小时前
Python 面向对象编程:从过程式思维到对象模型
linux·开发语言·python
努力学习的小廉3 小时前
【QT(七)】—— 常用控件(四)
开发语言·qt
CoderCodingNo3 小时前
【GESP】C++六级考试大纲知识点梳理, (3) 哈夫曼编码与格雷码
开发语言·数据结构·c++
故事不长丨3 小时前
C#log4net详解:从入门到精通,配置、实战与框架对比
c#·.net·wpf·log4net·日志·winform·日志系统