问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

命名管道(Named Pipes)深度解析(C#—C++应用间交互)

创作时间:
作者:
@小白创作中心

命名管道(Named Pipes)深度解析(C#—C++应用间交互)

引用
1
来源
1.
https://www.cnblogs.com/Firepad-magic/p/18741533

命名管道(Named Pipes)是Windows系统中一种进程间通信(IPC)机制,支持跨进程甚至跨网络的双向数据流传输。本文将从核心概念、C#实现模型、基础代码示例、高级特性与最佳实践等多个维度,深入解析命名管道的使用方法和应用场景。

一、核心概念

命名管道(Named Pipes)是Windows系统中一种进程间通信(IPC)机制,支持跨进程甚至跨网络的双向数据流传输。其核心特点如下:

  • 命名唯一性:通过全局唯一的管道名称(如 \\.\pipe\MyPipe)标识通信端点。
  • 双工通信:支持同步/异步的读写操作,服务端与客户端可同时收发数据。
  • 安全控制:可设置ACL(访问控制列表),限制特定用户或进程的访问权限。

二、C#实现模型

在C#中,通过 System.IO.Pipes 命名空间实现,核心类为:

  • NamedPipeServerStream:服务端管道,监听并接受客户端连接。
  • NamedPipeClientStream:客户端管道,主动连接服务端。

通信流程

服务端创建管道 → 等待客户端连接 → 客户端连接管道 → 数据交换

三、基础代码示例

1. 服务端实现(接收数据)

using System.IO.Pipes;
using System.Text;

// 服务端代码 
var pipeName = "UnityDataPipe";
using (var server = new NamedPipeServerStream(
    pipeName,
    PipeDirection.InOut, // 双向通信 
    maxNumberOfServerInstances: 1,
    PipeTransmissionMode.Message)) // 按消息分块 
{
    Console.WriteLine("等待客户端连接...");
    server.WaitForConnection();

    // 读取数据 
    byte[] buffer = new byte[1024];
    int bytesRead = server.Read(buffer, 0, buffer.Length);
    string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
    Console.WriteLine($"收到消息: {message}");

    // 发送响应 
    byte[] response = Encoding.UTF8.GetBytes("ACK");
    server.Write(response, 0, response.Length);
}

2. 客户端实现(发送数据)

using (var client = new NamedPipeClientStream(
    ".",
    "UnityDataPipe",
    PipeDirection.InOut))
{
    client.Connect(3000); // 超时3秒 

    // 发送数据 
    string message = "点云数据路径: D:/data.pcd"; 
    byte[] data = Encoding.UTF8.GetBytes(message);
    client.Write(data, 0, data.Length);

    // 读取响应 
    byte[] response = new byte[256];
    int bytesRead = client.Read(response, 0, response.Length);
    Console.WriteLine($"服务端响应: {Encoding.UTF8.GetString(response, 0, bytesRead)}");
}

3. Unity端与C++应用通信

C#服务端

using System;
using System.IO;
using UnityEngine;
using System.IO.Pipes;
using System.Threading;
using System.Text;
using System.Threading.Tasks;

public class NamedPathPipeServer
{
    // 配置参数 
    public string pipeName = "UnityFilePathPipe";
    public int bufferSize = 4096; // 路径字符串通常较短 
    private NamedPipeServerStream _server;
    private CancellationTokenSource _cts;
    private string _receivedPath = null;
    private object _pathLock = new object();

    public void Start() => StartServer();
    public void OnDestroy() => StopServer();
    public void OnApplicationQuit() => StopServer();

    // 启动服务端 
    private void StartServer()
    {
        _cts = new CancellationTokenSource();
        Task.Run(() => ListenForPaths(_cts.Token), _cts.Token);
    }

    // 监听循环 
    private async Task ListenForPaths(CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            try 
            {
                using (_server = new NamedPipeServerStream(
                    pipeName,
                    PipeDirection.In,
                    1,
                    PipeTransmissionMode.Message, // 按消息分块 
                    PipeOptions.Asynchronous))
                {
                    await _server.WaitForConnectionAsync(token);
                    byte[] buffer = new byte[bufferSize];
                    int bytesRead = await _server.ReadAsync(buffer, 0, buffer.Length, token);
                    string path = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                    lock (_pathLock)
                    {
                        _receivedPath = path;
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine($"管道异常: {e.Message}");
            }
        }
    }

    // Unity主线程处理 
    public void Update()
    {
        string pathToLoad = null;
        lock (_pathLock)
        {
            if (!string.IsNullOrEmpty(_receivedPath))
            {
                pathToLoad = _receivedPath;
                _receivedPath = null;
            }
        }
        if (pathToLoad != null)
        {
            Debug.Log($"收到点云路径: {pathToLoad}");
            LoadPointCloudFromPath(pathToLoad);
        }
    }

    private void LoadPointCloudFromPath(string filePath)
    {
        // 此处实现文件读取逻辑(示例伪代码)
        if (File.Exists(filePath))
        {
            byte[] data = File.ReadAllBytes(filePath);
            Vector3[] points = ParsePointCloud(data); // 解析为坐标数组 
            RenderPointCloud(points);
        }
        else 
        {
            Debug.LogError($"文件不存在: {filePath}");
        }
    }

    // 解析点云 
    private Vector3[] ParsePointCloud(byte[] data)
    {
        return new Vector3[0]; // 示例代码,实际解析请根据文件格式自行实现
    }

    // 渲染点云 
    private void RenderPointCloud(Vector3[] points)
    {
        // 此处实现渲染逻辑(示例伪代码)
        foreach (var point in points)
        {
            Debug.DrawRay(point, Vector3.up, Color.red, 0.1f, false);
        }
    }

    private void StopServer()
    {
        _cts?.Cancel();
        _server?.Close();
    }
}

C++客户端

void SendPathToUnity(const std::string& path) {
    HANDLE hPipe = CreateFileA(
        "\\\\.\\pipe\\UnityFilePathPipe",
        GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED,
        NULL
    );
    if (hPipe == INVALID_HANDLE_VALUE) {
        // 错误处理 
        return;
    }
    DWORD bytesWritten;
    WriteFile(hPipe, path.c_str(), path.size(), &bytesWritten, NULL);
    CloseHandle(hPipe);
}

四、高级特性与最佳实践

1. 异步通信模式

// 服务端异步等待连接 
await server.WaitForConnectionAsync();

// 异步读写 
byte[] buffer = new byte[4096];
int bytesRead = await server.ReadAsync(buffer, 0, buffer.Length);
await server.WriteAsync(responseBuffer, 0, responseBuffer.Length);

2. 多客户端支持

// 服务端循环处理多个客户端 
while (true)
{
    using (var server = new NamedPipeServerStream(...))
    {
        await server.WaitForConnectionAsync();
        Task.Run(() => HandleClient(server)); // 为每个客户端创建独立任务 
    }
}

3. 消息分帧协议

  • 长度前缀法:发送数据前附加4字节长度头。
// 发送端 
byte[] data = ...;
byte[] lengthHeader = BitConverter.GetBytes(data.Length);
client.Write(lengthHeader, 0, 4);
client.Write(data, 0, data.Length);

// 接收端 
byte[] header = new byte[4];
await stream.ReadAsync(header, 0, 4);
int length = BitConverter.ToInt32(header, 0);
byte[] payload = new byte[length];
await stream.ReadAsync(payload, 0, length);

4. 安全性控制

// 设置管道权限(仅允许当前用户)
var pipeSecurity = new PipeSecurity();
pipeSecurity.AddAccessRule(new PipeAccessRule(
    WindowsIdentity.GetCurrent().User,
    PipeAccessRights.ReadWrite,
    AccessControlType.Allow));
var server = new NamedPipeServerStream(
    pipeName,
    PipeDirection.InOut,
    1,
    PipeTransmissionMode.Message,
    PipeOptions.None,
    4096, 4096,
    pipeSecurity);

五、性能优化策略

优化方向
实现方法
缓冲区管理
使用固定大小缓冲区池(避免频繁内存分配)
批量传输
合并小数据包,减少系统调用次数(如每100ms发送一次数据)
零拷贝技术
通过MemoryMappedFile共享内存区域(需配合事件同步)
多线程处理
分离读写线程,利用BlockingCollection实现生产者-消费者模型

六、适用场景与局限性

适用场景

  • Unity与本地C++程序实时数据交换
  • 需要严格权限控制的内部进程通信
  • 高吞吐量但低延迟的本地IPC需求

局限性

  • 跨平台限制:原生命名管道主要支持Windows,Linux/macOS需通过第三方库(如 Mono.Posix
  • 网络延迟:跨网络通信时性能低于专用网络协议(如gRPC)

七、与其他IPC机制对比

机制
延迟
吞吐量
跨平台
复杂度
命名管道
TCP Socket
共享内存
极低
极高
gRPC

通过合理使用命名管道,开发者可在C#项目中实现高效可靠的本地进程通信,尤其适用于需要高实时性的数据交换场景。

📚 扩展学习资源

  1. 微软官方文档:NamedPipeServerStream

本文原文来自Cnblogs

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号