019.C#管道服务,两软件间用json数据交互

C# 管道服务(通常指命名管道 Named Pipes 或匿名管道 Anonymous Pipes)是基于 流 (Stream) 的机制,主要用于进程间通信 (IPC)。

从技术底层来看,管道传输的唯一数据类型是 字节数组 (byte[])

然而,在实际应用中,您可以传输任何可以被转换为字节流的数据类型。以下是几种常见的数据传输类型和方式:

1. 基础数据类型 (底层机制)

字节数组 (byte[])

这是管道服务直接处理的原始数据类型。无论您发送什么高级数据,它最终都必须被编码或序列化为字节数组才能通过管道传输。

2. 常见应用数据类型

字符串 (String)

字符串是最常用的数据类型之一。要通过管道传输字符串,您必须使用特定的编码 (Encoding) 将其转换为字节数组。

  • 发送方:

    cs 复制代码
    byte[] data = Encoding.UTF8.GetBytes("Hello, Pipe!"); pipeStream.Write(data, 0, data.Length);
  • 接收方:

    cs 复制代码
    string message = Encoding.UTF8.GetString(receivedBytes);

常用的编码包括 UTF8ASCIIUnicode

复杂对象 (Objects)

对于自定义类、结构体、列表等复杂数据结构,您需要使用序列化 (Serialization) 技术将其转换为字节流。

常用的序列化格式包括:

  1. JSON (JavaScript Object Notation)

    • 这是目前最推荐和最流行的方式,因为它跨平台、可读性高,且 C# 内置了高效的 System.Text.Json 库。
    • 流程: 对象 -> JSON 字符串 -> 字节数组。
  2. XML (Extensible Markup Language)

    • 适用于需要高度结构化和元数据描述的场景。
  3. 二进制格式 (Binary)

    • 虽然传统的 BinaryFormatter 因为安全和性能问题已被微软弃用,但像 Protobuf (Protocol Buffers) 这样的高效二进制序列化库仍然是追求极致性能时的优秀选择。

总结

C# 管道服务本身只传输字节

您可以传输任何 数据类型,前提是您在发送端能够将其可靠地序列化 (转换为字节流),并在接收端使用相同的逻辑和编码方式进行反序列化(将字节流还原为原始数据类型)。

数据类型 传输前处理方式 推荐库/方法
原始数据 无需处理 byte[]
字符串 编码 (Encoding) System.Text.Encoding.UTF8
复杂对象 序列化 (Serialization) System.Text.Json (JSON)
高性能对象 二进制序列化 Protobuf (需要安装 NuGet 包)

一.两软件间使用管道命令实现JSON数据交互

由于管道传输的是原始字节流,您需要解决两个核心问题:

  1. 序列化与反序列化: 将 C# 对象转换为 JSON 字符串,再转换为字节数组发送;接收时反向操作。
  2. 消息边界处理: 确保接收方知道一个完整的 JSON 消息在哪里结束,另一个消息在哪里开始。

下面是实现这一目标的详细步骤和代码结构。


核心步骤:带长度前缀的传输协议

为了解决消息边界问题,我们通常采用一个简单的协议:在发送实际的 JSON 数据之前,先发送一个表示数据长度的整数(通常是 4 字节)。

发送流程:

  1. 对象 -> JSON 字符串。
  2. JSON 字符串 -> 字节数组 (dataBytes)。
  3. 获取 dataBytes 的长度 (length)。
  4. length 转换为 4 字节的数组 (lengthBytes)。
  5. 写入 lengthBytes (4 字节)。
  6. 写入 dataBytes (N 字节)。

接收流程:

  1. 从管道读取 4 字节 (lengthBytes)。
  2. lengthBytes 转换回整数 (length)。
  3. 从管道读取 length 字节 (dataBytes)。
  4. dataBytes 转换为 JSON 字符串。
  5. JSON 字符串 -> C# 对象。

示例代码结构

我们将使用 System.Text.Json 进行序列化,并使用命名管道 (NamedPipeServerStreamNamedPipeClientStream)。

1. 定义数据模型

首先,定义一个要在两个应用程序之间传输的数据类。(两个项目内都有有这个数据类)

cs 复制代码
public class MessageData
{
    public int Id { get; set; }
    public string Content { get; set; }
    public DateTime Timestamp { get; set; } = DateTime.Now;
}
2. 服务端 (发送方) 示例

服务端负责创建管道,序列化数据,并发送带长度前缀的消息。

cs 复制代码
using System.IO.Pipes;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace PipeServer
{
    internal class Program
    {
        private const string PipeName = "JsonPipeExample";
        static async Task Main(string[] args)
        {
            await StartServerAsync();
        }

        public static async Task StartServerAsync()
        {
            Console.WriteLine("等待用户端连接...");

            // 创建命名管道服务器
            using var pipeServer = new NamedPipeServerStream(
                PipeName,
                PipeDirection.Out,
                1, // 最大实例数
                PipeTransmissionMode.Byte,
                PipeOptions.Asynchronous);

            await pipeServer.WaitForConnectionAsync();
            Console.WriteLine("用户端连接成功.");

            // 准备要发送的数据
            var dataToSend = new MessageData { Id = 101, Content = "Hello from Server!" };

            // 序列化对象为 JSON 字符串
            string jsonString = JsonSerializer.Serialize(dataToSend);

            // 转换为 UTF8 字节数组
            byte[] dataBytes = Encoding.UTF8.GetBytes(jsonString);
            int length = dataBytes.Length;

            // 1. 写入长度前缀 (4 字节)
            byte[] lengthBytes = BitConverter.GetBytes(length);
            await pipeServer.WriteAsync(lengthBytes, 0, lengthBytes.Length);

            // 2. 写入 JSON 数据
            await pipeServer.WriteAsync(dataBytes, 0, dataBytes.Length);

            Console.WriteLine($"Sent JSON message (Length: {length} bytes): {jsonString}");

            pipeServer.Disconnect();
        }



    }
}
3. 客户端 (接收方) 示例

客户端负责连接管道,读取长度前缀,然后读取相应长度的数据,并进行反序列化。

cs 复制代码
using System.IO.Pipes;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace PipeClient
{
    internal class Program
    {
        private const string PipeName = "JsonPipeExample";
        static async Task Main(string[] args)
        {
            await StartClientAsync();
        }
       

        public static async Task StartClientAsync()
        {
            Console.WriteLine("连接到服务器...");

            // 创建命名管道客户端
            using var pipeClient = new NamedPipeClientStream(".", PipeName, PipeDirection.In);

            try
            {
                pipeClient.Connect(5000); // 尝试连接,超时5秒
                Console.WriteLine("已连接至服务器");

                // 1. 读取长度前缀 (4 字节)
                byte[] lengthBytes = new byte[4];
                int bytesRead = await pipeClient.ReadAsync(lengthBytes, 0, 4);

                if (bytesRead < 4)
                {
                    Console.WriteLine("无法读取消息长度");
                    return;
                }

                // 将 4 字节转换为整数长度
                int dataLength = BitConverter.ToInt32(lengthBytes, 0);

                // 2. 读取 JSON 数据
                byte[] dataBytes = new byte[dataLength];
                int totalBytesRead = 0;

                // 循环读取,直到读完所有数据
                while (totalBytesRead < dataLength)
                {
                    int currentRead = await pipeClient.ReadAsync(dataBytes, totalBytesRead, dataLength - totalBytesRead);
                    if (currentRead == 0) break; // 连接断开
                    totalBytesRead += currentRead;
                }

                if (totalBytesRead != dataLength)
                {
                    Console.WriteLine($"收到的数据不完整。 预期的: {dataLength}, 实际的: {totalBytesRead}");
                    return;
                }

                // 3. 反序列化
                string jsonString = Encoding.UTF8.GetString(dataBytes);

                // 反序列化 JSON 字符串为对象
                var receivedData = JsonSerializer.Deserialize<MessageData>(jsonString);

                Console.WriteLine("\n--- 读取数据 ---");
                Console.WriteLine($"Raw JSON: {jsonString}");
                Console.WriteLine($"ID: {receivedData.Id}");
                Console.WriteLine($"Content: {receivedData.Content}");
                Console.WriteLine($"Timestamp: {receivedData.Timestamp}");

            }
            catch (TimeoutException)
            {
                Console.WriteLine("连接超时:");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"An error occurred: {ex.Message}");
            }
        }
    }
}

运行建议

要测试上述代码,您需要创建两个独立的 C# 控制台应用程序:

  1. App A (Server): 调用 PipeServer.StartServerAsync()
  2. App B (Client): 调用 PipeClient.StartClientAsync()

注意: 您必须先启动服务器应用程序,然后再启动客户端应用程序,以便客户端能够连接到正在等待的命名管道。

相关推荐
计算机程序设计小李同学2 小时前
基于JavaServer Pages(JSP)技术开发的食谱分享平台
java·开发语言
划水的code搬运工小李2 小时前
VOFA修改数据解析GPCHC
开发语言·qt
我是一只小青蛙8882 小时前
C++模板进阶技巧全解析
java·开发语言
组合缺一2 小时前
FastJson2 与 SnackJson4 有什么区别?
java·json·fastjson·snackjson
燃于AC之乐2 小时前
C/C++内存管理核心解析:分布、管理方式与定位new应用
开发语言·c++·内存管理
写代码的【黑咖啡】3 小时前
Python中的JSON处理(标准库)
开发语言·python·json
Elias不吃糖10 小时前
Java Lambda 表达式
java·开发语言·学习
guygg8810 小时前
一级倒立摆MATLAB仿真程序
开发语言·matlab
我的炸串拌饼店10 小时前
火山方舟API C#服务类设计解析
c#·调用火山方舟api