Przykład nazwanych rur

134

Jak napisać prostą - absolutne minimum potrzebne do działania - aplikację testową, która ilustruje, jak używać IPC / Named Pipes?

Na przykład, jak napisać aplikację konsolową, w której Program 1 mówi „Witaj świecie” do Programu 2, a Program 2 odbiera komunikat i odpowiada „Zrozumiano” do Programu 1.

Jordan Trainor
źródło

Odpowiedzi:

174
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();
                }
            });
        }
    }
}
FUNT
źródło
1
@JordanTrainor Przepraszamy, to jest w .Net 4.5. Możesz użyćThread.Sleep
LB
2
@Gusdor Mogłem użyć kilku sync-primiteves. Ale trudniej byłoby to przeczytać. Myślę, że wystarczy dać pomysł, jak używać NamedPipes
LB
2
Jeśli masz problem, że potok zamyka się po jednym odczycie, sprawdź tę odpowiedź: stackoverflow.com/a/895656/941764
jgillich
12
Jeśli używasz .NET 4.5, można wymienić Task.Factory.StartNewzTask.Run .
Rudey
2
Czy musisz pozbyć się reader/ writer? Jeśli tak, czy pozbywasz się tylko jednego z nich? Nigdy nie widziałem przykładu, w którym oba są podłączone do tego samego strumienia.
JoshVarty,
26

Dla kogoś, kto jest nowy w IPC i nazwanych potokach, uważam, że następujący pakiet NuGet jest bardzo pomocny.

GitHub: Named Pipe Wrapper dla .NET 4.0

Aby użyć najpierw zainstaluj pakiet:

PS> Install-Package NamedPipeWrapper

Następnie przykładowy serwer (skopiowany z linku):

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();

Przykładowy klient:

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

client.Start();

Najlepsze w tym jest to, że w przeciwieństwie do przyjętej tutaj odpowiedzi obsługuje wielu klientów rozmawiających z jednym serwerem.

Martin Laukkanen
źródło
7
Nie polecam tego pakietu NuGet do produkcji. Zaimplementowałem to i ma kilka błędów, głównie z powodu braku możliwości sprawdzenia, kiedy wiadomość została w pełni odebrana na drugim końcu potoku (prowadzi do zerwanych połączeń lub zbyt szybkiego zakończenia połączenia (sprawdź kod na github, jeśli mi nie ufasz, „WaitForPipeDrain” nie jest wywoływane, kiedy powinno), a ponadto będziesz mieć wielu klientów, nawet gdy tylko jeden nasłuchuje, ponieważ ... zbyt wiele problemów). To smutne, ponieważ był naprawdę łatwy w użyciu. Musiałem odbudować jeden od podstaw, mając mniej opcji.
Micaël Félix
Tak, słuszna uwaga, niestety ten pierwotny opiekun nie aktualizował projektu od lat, na szczęście chociaż istnieje wiele rozwidleń, z których większość rozwiązuje omówione przez Ciebie problemy.
Martin Laukkanen
2
@MartinLaukkanen: Witam, planuję użyć NamedPipeWrapper, wiesz, który widelec naprawia ten błąd? dzięki
Whiletrue
@MartinLaukkanen Czy możemy dostać widelec, który naprawił wspomniane błędy?
Ibanez1408
Nie pamiętam, którego konkretnie użyłem, ale sugerowałbym przejrzenie zatwierdzeń na wykresie sieci fork, aby określić, który z nich rozwiązuje problemy, których dotyczą: github.com/acdvorak/named-pipe-wrapper/network
Martin Laukkanen
17

Tak przy okazji, możesz pisać do nazwanego potoku, używając jego nazwy.

Otwórz powłokę poleceń jako administrator, aby obejść domyślny błąd „Odmowa dostępu”:

echo Hello > \\.\pipe\PipeName
Michael Blankenship
źródło
4

Linux dotnet core nie obsługuje nazwanych rur!

Wypróbuj TcpListener, jeśli wdrażasz w systemie Linux

Ten kod klienta / serwera NamedPipe w obie strony przesyła bajt do serwera.

  • Klient zapisuje bajt
  • Serwer czyta bajt
  • Serwer zapisuje bajt
  • Klient czyta bajt

DotNet Core 2.0 Server ConsoleApp

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);
            }
        }
    }
}

Konsola klienta 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 <-- 
            });
        }
    }
}
patrick
źródło
Używanie nazwanych potoków tak samo dla 2 procesów renderuje mnieSystem Unauthorized Accesss Exception - path is denied
Bercovici Adrian,
Nie jesteś pewien, czy może działać jako administrator?
patrick