命名管道的示例

155

我该如何编写一个简单的测试应用程序,以演示如何使用IPC/命名管道?

例如,如果要编写一个控制台应用程序,其中程序1向程序2发送“Hello World”消息,然后程序2接收到该消息并回复“Roger That”给程序1,应如何编写代码?

4个回答

198
using System;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            StartServer();
            Task.Delay(1000).Wait();


            //Client
            var client = new NamedPipeClientStream("PipesOfPiece");
            client.Connect();
            StreamReader reader = new StreamReader(client);
            StreamWriter writer = new StreamWriter(client);

            while (true)
            {
                string input = Console.ReadLine();
                if (String.IsNullOrEmpty(input)) break;
                writer.WriteLine(input);
                writer.Flush();
                Console.WriteLine(reader.ReadLine());
            }
        }

        static void StartServer()
        {
            Task.Factory.StartNew(() =>
            {
                var server = new NamedPipeServerStream("PipesOfPiece");
                server.WaitForConnection();
                StreamReader reader = new StreamReader(server);
                StreamWriter writer = new StreamWriter(server);
                while (true)
                {
                    var line = reader.ReadLine();
                    writer.WriteLine(String.Join("", line.Reverse()));
                    writer.Flush();
                }
            });
        }
    }
}

1
这个例子中使用的Task非常糟糕。它的语法冗长,由于调度可能甚至无法接近1000ms。 - Gusdor
3
@Gusdor 我本可以使用一些同步原语,但那样会让代码更难以理解。我认为对于如何使用命名管道来说,这已经足够了。 - L.B
2
如果您遇到管道在一次读取后关闭的问题,请查看此答案:https://dev59.com/G3NA5IYBdhLWcg3wmfO5#895656 - jgillich
14
如果您正在使用.NET 4.5,您可以用Task.Run替换Task.Factory.StartNew。请参考链接:https://dev59.com/BWEh5IYBdhLWcg3wvFjY#22087211。 - Rudey
2
你必须处理 reader/writer 吗?如果是这样,你只需要处理其中一个吗?我从来没有见过两个都附加在同一个流上的例子。 - JoshVarty
显示剩余3条评论

27

对于初学者来说,如果涉及到IPC和命名管道,我发现以下NuGet包是非常有帮助的。

GitHub: .NET 4.0 命名管道包装器

要使用此包,请首先安装它:

PS> Install-Package NamedPipeWrapper

然后是一个示例服务器(从链接中复制):

var server = new NamedPipeServer<SomeClass>("MyServerPipe");
server.ClientConnected += delegate(NamedPipeConnection<SomeClass> conn)
    {
        Console.WriteLine("Client {0} is now connected!", conn.Id);
        conn.PushMessage(new SomeClass { Text: "Welcome!" });
    };

server.ClientMessage += delegate(NamedPipeConnection<SomeClass> conn, SomeClass message)
    {
        Console.WriteLine("Client {0} says: {1}", conn.Id, message.Text);
    };

server.Start();

示例客户端:

var client = new NamedPipeClient<SomeClass>("MyServerPipe");
client.ServerMessage += delegate(NamedPipeConnection<SomeClass> conn, SomeClass message)
    {
        Console.WriteLine("Server says: {0}", message.Text);
    };

client.Start();

对我来说最好的一点是,与此处被接受的答案不同,它支持多个客户端与单个服务器通信。


11
我不建议在生产环境中使用这个NuGet包。我已经实现了它,但它有一些错误,主要是由于无法真正知道消息何时在另一端的管道中完全接收(导致连接中断,或连接结束太早(如果你不信任我,请检查github上的代码,“WaitForPipeDrain”没有按照预期调用),此外,即使只有一个客户端正在监听,你也会有多个客户端存在,因为...问题太多了)。很遗憾,因为它真的很容易使用。我不得不从头开始重建一个选项更少的版本。 - Micaël Félix
是的,说得好。不幸的是,原来的维护者多年来没有更新这个项目,但幸运的是,存在许多分支,其中大部分修复了你所讨论的问题。 - Martin Laukkanen
2
@MartinLaukkanen:你好,我计划使用NamedPipeWrapper,你知道哪个分支正在修复这个bug吗?谢谢。 - Whiletrue
@MartinLaukkanen,我们可以拿到修复了提到的错误的分支吗? - Ibanez1408
我不记得我具体使用了哪一个,但我建议您查看分叉网络图上的提交记录,以确定哪一个修复了您关注的问题:https://github.com/acdvorak/named-pipe-wrapper/network - Martin Laukkanen
我也会避免使用这个包。它使用BinaryFormatter存在潜在的安全问题。我对其进行了大量重构,但最终原始代码几乎没有留下什么。 - Tony Edgecombe

23
你实际上可以使用命名管道的名称来写入数据到它,顺便提一句。
打开一个管理员权限的命令行窗口可以解决默认的“拒绝访问”错误:
echo Hello > \\.\pipe\PipeName

5

Linux dotnet core 不支持命名管道!

如果部署到 Linux,请尝试 TcpListener。

这个 NamedPipe 客户端/服务器代码通过服务器往返传递一个字节。

  • 客户端写入字节
  • 服务器读取字节
  • 服务器写入字节
  • 客户端读取字节

DotNet Core 2.0 服务器控制台应用程序。

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

namespace Server
{
    class Program
    {
        static void Main(string[] args)
        {
            var server = new NamedPipeServerStream("A", PipeDirection.InOut);
            server.WaitForConnection();

            for (int i =0; i < 10000; i++)
            {
                var b = new byte[1];
                server.Read(b, 0, 1); 
                Console.WriteLine("Read Byte:" + b[0]);
                server.Write(b, 0, 1);
            }
        }
    }
}

DotNet Core 2.0 客户端控制台应用程序

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

namespace Client
{
    class Program
    {
        public static int threadcounter = 1;
        public static NamedPipeClientStream client;

        static void Main(string[] args)
        {
            client = new NamedPipeClientStream(".", "A", PipeDirection.InOut, PipeOptions.Asynchronous);
            client.Connect();

            var t1 = new System.Threading.Thread(StartSend);
            var t2 = new System.Threading.Thread(StartSend);

            t1.Start();
            t2.Start(); 
        }

        public static void StartSend()
        {
            int thisThread = threadcounter;
            threadcounter++;

            StartReadingAsync(client);

            for (int i = 0; i < 10000; i++)
            {
                var buf = new byte[1];
                buf[0] = (byte)i;
                client.WriteAsync(buf, 0, 1);

                Console.WriteLine($@"Thread{thisThread} Wrote: {buf[0]}");
            }
        }

        public static async Task StartReadingAsync(NamedPipeClientStream pipe)
        {
            var bufferLength = 1; 
            byte[] pBuffer = new byte[bufferLength];

            await pipe.ReadAsync(pBuffer, 0, bufferLength).ContinueWith(async c =>
            {
                Console.WriteLine($@"read data {pBuffer[0]}");
                await StartReadingAsync(pipe); // read the next data <-- 
            });
        }
    }
}

使用命名管道来处理两个进程,却出现了“系统未经授权访问异常 - 路径被拒绝”的错误。 - Bercovici Adrian
不确定,也许需要以管理员身份运行? - patrick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接