다음을 통해 공유


A Client/Server Application Using Named Pipes


What are named pipes?

Named Pipes is one of few methods to do inter-process communication (IPC) to exchange data between threads or multiple processes in Windows and is also available on other Operating Systems.

Applicability of IPC

  1. Sharing information e.g. Sending message between processes.
  2. Performance e.g. A process that responsible to process some data outside to the program.
  3. Stability e.g. A process that hosts plugins outside to the program itself so if one of the plugins crashes the program can recover without restarting.
  4. Security e.g. A program that gives each process a different level of permissions.
  5. Modularity e.g. A program that deals with video and audio encoding might have one process for video encoding and another process for audio encoding.

How does it work?

A pipe is a section of shared memory that processes use for communication. The process that creates a pipe is the pipe server. A process that connects to a pipe is a pipe client. One process writes information to the pipe, then the other process reads the information from the pipe. -- MSDN

Windows provides few APIs that we can use to create a Named Pipe but we're going to use the .NET wrappers which are just easier to use.

A Client/Server Application

The Client Side

To send messages to the server the client has a Send method that creates a NamedPipeClientStream. the constructor takes few arguments as follow:**
**

  1. The server's name can be either local or a remote server.
  2. The pipe's name.
  3. The direction of the pipe can either be in/out or both.
  4. The pipe options which can either be async, write through or none.

The options I used are marked with a bold text.

The next thing we need to do is connect to the server and we need to do the followings things in order to do it.

  1. Calling the Connect method that responsible to connect to a waiting server.
  2. Check whether the client is connected.

Finally, if the client is connected we need to write to the stream and finally flush the stream.

01.public async Task Send(string content, int timeOut = 30000)
02.        {
03.            try
04.            {
05.                using (NamedPipeClientStream client = new NamedPipeClientStream(".", _pipeName, PipeDirection.Out, PipeOptions.Asynchronous))
06.                {
07.                    client.Connect(timeOut);
08. 
09.                    if (client.IsConnected)
10.                    {
11.                        byte[] output = Encoding.UTF8.GetBytes(content);
12. 
13.                        await client.WriteAsync(output, 0, output.Length);
14. 
15.                        await client.FlushAsync();
16.                    }
17.                }
18.            }
19.            catch (TimeoutException ex)
20.            {
21.                Debug.WriteLine(ex);
22.            }
23.        }

Here is a complete example of a program I wrote that demonstrates how to use this class.

01.namespace NamedPipes.Client
02.{
03.    using System;
04.    using System.Text;
05.    using System.Threading;
06.    using System.Threading.Tasks;
07. 
08.    internal class  Program
09.    {
10.        private static  readonly CancellationToken _cancel;
11. 
12.        private static  readonly CancellationTokenSource _cancelSource;
13. 
14.        private static  readonly PipeClient _pipeClient;
15. 
16.        static Program()
17.        {
18.            _pipeClient = new  PipeClient("7988717B-3119-4413-8636-E6CE325E3957");
19. 
20.            _cancelSource = new  CancellationTokenSource();
21. 
22.            _cancel = _cancelSource.Token;
23.        }
24. 
25.        private static  void Main(string[] args)
26.        {
27.            Console.WriteLine("[thread: {0}] -> Starting client session. ", Thread.CurrentThread.ManagedThreadId);
28. 
29.            Task.Run(async () =>
30.                           {
31.                               StringBuilder input = new  StringBuilder();
32. 
33.                               Console.WriteLine("[thread: {0}] -> waiting for server.", Thread.CurrentThread.ManagedThreadId);
34. 
35.                               while (!_cancelSource.IsCancellationRequested)
36.                               {
37.                                   ConsoleKeyInfo key = Console.ReadKey();
38. 
39.                                   if (key.Key == ConsoleKey.Enter)
40.                                   {
41.                                       string content = input.ToString().TrimEnd();
42. 
43.                                       if (!string.IsNullOrWhiteSpace(content))
44.                                       {
45.                                           await _pipeClient.Send(content);
46.                                       }
47. 
48.                                       if (content.ToLower() == "exit")
49.                                       {
50.                                           _cancelSource.Cancel();
51.                                       }
52. 
53.                                       input.Clear();
54. 
55.                                       Console.WriteLine();
56.                                   }
57.                                   else if  (key.Key == ConsoleKey.Backspace)
58.                                   {
59.                                       if (input.Length > 0)
60.                                       {
61.                                           input = input.Remove(input.Length - 1, 1);
62.                                           Console.Write(" \b"); 
63.                                       }
64.                                   }
65.                                   else
66.                                   {
67.                                       input.Append(key.KeyChar);
68.                                   }
69.                               }
70.                           }, _cancel).Wait();
71. 
72.            Console.WriteLine("\r\n[thread: {0}] -> Client session was ended.\r\n", Thread.CurrentThread.ManagedThreadId);
73.            Console.WriteLine("Press any key to continue ...");
74.            Console.ReadKey();
75.        }
76.    }
77.}

The client is a simple console application that allows you to write some text and when you press the enter key it sends the message to the server, when you type "exit" it ends the server and finally closes the client application.

The Server Side

The client waits for a server to be available, once it's available we can start sending messages to it.

In our case the server only receives messages so we don't need to have a method to send messages so the only methods that we will implement is a Start method to start the server and a Stop method to stop the server.

To listen to incoming messages from the client we need to create a NamedPipeServerStream. the constructor takes few arguments as follow:

  1. The pipe's name.
  2. The direction of the pipe can either be in/out or both.
  3. The maximum number of server instances allowed in our case it's just 1.
  4. The method to transmit data either in bytes or a message which is basically a sequence of bytes.
  5. The pipe options which can either be async, write through or none.

The options I used are marked with a bold text.

To wait for incoming messages the server needs to wait and finally when a client is connected it reads the data and disconnects and waits again.

  1. Waiting the client is done using pairs of functions BeginWaitForConnection and EndWaitForConnection because we're doing that asynchronously.
  2. Reading the data is done through ReadAsync.
  3. Finally, if the message we sent is "exit", it ends the server.
namespace NamedPipes.Server
{
    using System;
    using System.IO.Pipes;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
 
    public class PipeServer
    {
        private readonly CancellationToken _cancel;
 
        private readonly CancellationTokenSource _cancelSource;
 
        private readonly string _pipeName;
 
        public PipeServer(string pipeName)
        {
            _pipeName = pipeName;
 
            _cancelSource = new CancellationTokenSource();
 
            _cancel = _cancelSource.Token;
        }
 
        public async Task Start()
        {
            Console.WriteLine("[thread: {0}] -> Starting server listener.", Thread.CurrentThread.ManagedThreadId);
 
            while (!_cancel.IsCancellationRequested)
            {
                await Listener();
            }
        }
 
        public void Stop()
        {
            _cancelSource.Cancel();
        }
 
        private async Task Listener()
        {
            using (NamedPipeServerStream server = new NamedPipeServerStream(_pipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous))
            {
                Console.WriteLine("\r\n[thread: {0}] -> Waiting for client.", Thread.CurrentThread.ManagedThreadId);
 
                await Task.Factory.FromAsync(server.BeginWaitForConnection, server.EndWaitForConnection, null);
 
                Console.WriteLine("[thread: {0}] -> Client connected.", Thread.CurrentThread.ManagedThreadId);
 
                await ReadData(server);
 
                if (server.IsConnected)
                {
                    server.Disconnect();
                }
            }
        }
 
        private async Task ReadData(NamedPipeServerStream server)
        {
            Console.WriteLine("[thread: {0}] -> Reading data.", Thread.CurrentThread.ManagedThreadId);
 
            byte[] buffer = new byte[255];
 
            int length = await server.ReadAsync(buffer, 0, buffer.Length, _cancel);
 
            byte[] chunk = new byte[length];
 
            Array.Copy(buffer, chunk, length);
 
            string content = Encoding.UTF8.GetString(chunk);
 
            Console.WriteLine("[thread: {0}] -> {1}: {2}", Thread.CurrentThread.ManagedThreadId, DateTime.Now, content);
 
            if (content == "exit")
            {
                Stop();
            }
        }
    }
}

Here is a complete example of a program I wrote that demonstrates how to use this class.

01.namespace NamedPipes.Server
02.{
03.    using System;
04.    using System.Threading;
05. 
06.    internal class  Program
07.    {
08.        private static  readonly PipeServer _pipeServer;
09.         
10.        static Program()
11.        {
12.            _pipeServer = new  PipeServer("7988717B-3119-4413-8636-E6CE325E3957");
13.        }
14. 
15.        private static  void Main(string[] args)
16.        {
17.            Console.WriteLine("[thread: {0}] -> Starting server.", Thread.CurrentThread.ManagedThreadId);
18. 
19.            _pipeServer.Start().Wait();
20. 
21.            Console.WriteLine("\r\n[thread: {0}] -> Server closed.\r\n", Thread.CurrentThread.ManagedThreadId);
22.            Console.WriteLine("Press any key to continue ...");
23.            Console.ReadKey();
24.        }
25.    }
26.}

The server listens to the client and when it receives a message it prints to the screen.

You can download the project from Github.