Recipe16.5.Using Named Pipes to Communicate


Recipe 16.5. Using Named Pipes to Communicate

Problem

You need a way to use named pipes to communicate with another application across the network.

Solution

Create a P/Invoke wrapper class for the named-pipe APIs in Kernel32.dll. You can then create a managed client and managed server class to work with named pipes.

Example 16-3 shows the named-pipe interop wrappers in a class called NamedPipeInterop.

Example 16-3. NamedPipeInterop class

 namespace NamedPipes {      /// <summary>      /// Imported named-pipe entry points for P/Invoke into native code      /// </summary>      public class NamedPipeInterop      {         // #defines related to named-pipe processing          public const int PIPE_ACCESS_OUTBOUND = 0x00000002;          public const int PIPE_ACCESS_DUPLEX = 0x00000003;          public const int PIPE_ACCESS_INBOUND = 0x00000001;         public const int PIPE_WAIT = 0x00000000;          public const int PIPE_NOWAIT = 0x00000001;          public const int PIPE_READMODE_BYTE = 0x00000000;          public const int PIPE_READMODE_MESSAGE = 0x00000002;          public const int PIPE_TYPE_BYTE = 0x00000000;          public const int PIPE_TYPE_MESSAGE = 0x00000004;         public const int PIPE_CLIENT_END = 0x00000000;          public const int PIPE_SERVER_END = 0x00000001;         public const int PIPE_UNLIMITED_INSTANCES = 255;         public const uint NMPWAIT_WAIT_FOREVER = 0xffffffff;          public const uint NMPWAIT_NOWAIT = 0x00000001;          public const uint NMPWAIT_USE_DEFAULT_WAIT = 0x00000000;         public const uint GENERIC_READ = (0x80000000);          public const uint GENERIC_WRITE = (0x40000000);          public const uint GENERIC_EXECUTE = (0x20000000);          public const uint GENERIC_ALL = (0x10000000);         public const int CREATE_NEW = 1;         public const int CREATE_ALWAYS = 2;         public const int OPEN_EXISTING = 3;         public const int OPEN_ALWAYS = 4;         public const int TRUNCATE_EXISTING = 5;         public static IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1);         public const int ERROR_PIPE_BUSY = 231;         public const int ERROR_NO_DATA = 232;         public const int ERROR_PIPE_NOT_CONNECTED = 233;         public const int ERROR_MORE_DATA = 234;         public const int ERROR_PIPE_CONNECTED = 535;         public const int ERROR_PIPE_LISTENING = 536;         [DllImport("kernel32.dll", SetLastError = true)]         public static extern bool CallNamedPipe(             string lpNamedPipeName,             byte[] lpInBuffer,             uint nInBufferSize,             byte[] lpOutBuffer,             uint nOutBufferSize,             byte[] lpBytesRead,             uint nTimeOut);         [DllImport("kernel32.dll", SetLastError = true)]          public static extern bool CloseHandle(SafeFileHandle hObject);         [DllImport("kernel32.dll", SetLastError = true)]         public static extern bool ConnectNamedPipe(              SafeFileHandle hNamedPipe,// Handle to named pipe              IntPtr lpOverlapped // Overlapped structure              );         [DllImport("kernel32.dll", SetLastError = true)]         public static extern SafeFileHandle CreateNamedPipe(              String lpName,       // Pipe name              uint dwOpenMode,     // Pipe open mode              uint dwPipeMode,     // Pipe-specific modes              uint nMaxInstances,  // Maximum number of instances              uint nOutBufferSize,     // Output buffer size              uint nInBufferSize,  // Input buffer size              uint nDefaultTimeOut,    // Time-out interval              //SecurityAttributes attr              IntPtr pipeSecurityDescriptor  // Security descriptor              );         [DllImport("kernel32.dll", SetLastError = true)]         public static extern SafeFileHandle CreatePipe(             SafeFileHandle hReadPipe,             SafeFileHandle hWritePipe,             IntPtr lpPipeAttributes,             uint nSize);         [DllImport("kernel32.dll", SetLastError = true)]         public static extern SafeFileHandle CreateFile(              String lpFileName,// File name              uint dwDesiredAccess,// Access mode              uint dwShareMode,// Share mode              IntPtr attr,     // Security descriptor              uint dwCreationDisposition,  // How to create              uint dwFlagsAndAttributes,   // File attributes              uint hTemplateFile);// Handle to template file         [DllImport("kernel32.dll", SetLastError = true)]         public static extern bool DisconnectNamedPipe(SafeFileHandle hNamedPipe);         [DllImport("kernel32.dll", SetLastError = true)]         public static extern bool FlushFileBuffers(SafeFileHandle hFile);         [DllImport("kernel32.dll", SetLastError = true)]         public static extern bool GetNamedPipeHandleState(              SafeFileHandle hNamedPipe,              IntPtr lpState,              IntPtr lpCurInstances,              IntPtr lpMaxCollectionCount,              IntPtr lpCollectDataTimeout,              string lpUserName,              uint nMaxUserNameSize);         [DllImport("KERNEL32.DLL", SetLastError = true)]         public static extern bool GetNamedPipeInfo(              SafeFileHandle hNamedPipe,              out uint lpFlags,              out uint lpOutBufferSize,              out uint lpInBufferSize,              out uint lpMaxInstances);         [DllImport("KERNEL32.DLL", SetLastError = true)]         public static extern bool PeekNamedPipe(              SafeFileHandle hNamedPipe,              byte[] lpBuffer,              uint nBufferSize,              byte[] lpBytesRead,              out uint lpTotalBytesAvail,              out uint lpBytesLeftThisMessage);         [DllImport("KERNEL32.DLL", SetLastError = true)]         public static extern bool SetNamedPipeHandleState(              SafeFileHandle hNamedPipe,              ref int lpMode,              IntPtr lpMaxCollectionCount,              IntPtr lpCollectDataTimeout);         [DllImport("KERNEL32.DLL", SetLastError = true)]         public static extern bool TransactNamedPipe(              SafeFileHandle hNamedPipe,              byte[] lpInBuffer,              uint nInBufferSize,              [Out] byte[] lpOutBuffer,              uint nOutBufferSize,              IntPtr lpBytesRead,              IntPtr lpOverlapped);         [DllImport("kernel32.dll", SetLastError = true)]         public static extern bool WaitNamedPipe(              string name,              uint timeout);         [DllImport("kernel32.dll", SetLastError = true)]         public static extern bool ReadFile(              SafeFileHandle hFile,           // Handle to file              byte[] lpBuffer,        // Data buffer              uint nNumberOfBytesToRead,  // Number of bytes to read              byte[] lpNumberOfBytesRead, // Number of bytes read              uint lpOverlapped       // Overlapped buffer              );         [DllImport("kernel32.dll", SetLastError = true)]         public static extern bool WriteFile(             SafeFileHandle hFile,              // Handle to file             byte[] lpBuffer,           // Data buffer             uint nNumberOfBytesToWrite,    // Number of bytes to write             byte[] lpNumberOfBytesWritten,  // Number of bytes written             uint lpOverlapped         // Overlapped buffer             );     } 

Now, using the interop wrappers, you can create a named-pipe client class named NamedPipeClient, as shown in Example 16-4.

Example 16-4. NamedPipeClient class

 using System;  using System.Collections.Generic;  using System.Text;  using System.Runtime.InteropServices;  using System.Diagnostics;  using System.ComponentModel;  using System.IO;  using System.Threading;  using Microsoft.Win32.SafeHandles; namespace NamedPipes {      /// <summary>      /// NamedPipeClient - An implementation of a synchronous, message-based,      /// named pipe client      ///      /// </summary>      public class NamedPipeClient : IDisposable      {         #region Private Members         /// <summary>         ///    The full name of the pipe being connected to         /// </summary>         private string _pipeName = "";         /// <summary>         /// The pipe handle once connected         /// </summary>         private SafeFileHandle _handle =                 new SafeFileHandle(NamedPipeInterop.INVALID_HANDLE_VALUE,true);         /// <summary>         /// Default response buffer size (1K)         /// </summary>         private int _responseBufferSize = 1024;         /// <summary>         /// Track if dispose has been called         /// </summary>         private bool disposed = false;         /// <summary>         /// Timeout for the retry after first failed connect         /// </summary>         private int _retryTimeout = 20000;         /// <summary>         /// Number of times to retry connecting         /// </summary>         private int _retryConnect = 5;         #endregion         #region Construction / Cleanup         /// <summary>         /// CTOR         /// </summary>         /// <param name="pipeName">name of the pipe</param>         public NamedPipeClient(string pipeName)         {             _pipeName = pipeName;              Trace.WriteLine("NamedPipeClient using pipe name of " + _pipeName);         }         /// <summary>         /// Finalizer         /// </summary>         ~NamedPipeClient()         {             Dispose(false);         }         public void Dispose()         {             Dispose(true);             GC.SuppressFinalize(this);         }         private void Dispose(bool disposing)         {              // Check to see if Dispose has already been called.              if (!this.disposed)              {                 ClosePipe();             }             disposed = true;         }         private void ClosePipe()         {             if (!_handle.IsInvalid)             {                 _handle.Close();             }         }         /// <summary>         /// Close - because it is more intuitive than Dispose…         /// </summary>         public void Close()         {             ClosePipe();         }         #endregion         #region Properties          /// <summary>          /// ResponseBufferSize Property - the size used to create response buffers          /// for messages written using WriteMessage          /// </summary>          public int ResponseBufferSize          {             get             {                 return _responseBufferSize;             }             set             {                  _responseBufferSize = value;             }         }         /// <summary>         /// The number of milliseconds to wait when attempting to retry a connection         /// </summary>         public int RetryConnectCount         {             get             {                 return _retryConnect;             }             set             {                 _retryConnect = value;             }         }         /// <summary>         /// The number of milliseconds to wait when attempting to retry a connection         /// </summary>         public int RetryConnectTimeout         {             get             {                 return _retryTimeout;             }             set             {                 _retryTimeout = value;             }         }         #endregion         #region Public Methods         /// <summary>         /// Connect - connect to an existing pipe         /// </summary>         /// <returns>true if connected</returns>         public void Connect()         {             if (!_handle.IsInvalid)                  throw new InvalidOperationException("Pipe is already connected!");             string errMsg = "";             int errCode = 0;             int retryAttempts = _retryConnect;             // Keep trying to connect             while (retryAttempts > 0)             {                 // Mark off one attempt                 retryAttempts--;                 // Connect to existing pipe                 _handle = NamedPipeInterop.CreateFile(_pipeName,                      NamedPipeInterop.GENERIC_READ |                      NamedPipeInterop.GENERIC_WRITE,                      0,                      IntPtr.Zero,                      NamedPipeInterop.OPEN_EXISTING,                      0,                      0);                 // Check to see if we connected                 if (!_handle.IsInvalid)                     break;                 // The pipe could not be opened as all instances are busy.                  // Any other error we bail for.                 errCode = Marshal.GetLastWin32Error();                  if (errCode !=                      NamedPipeInterop.ERROR_PIPE_BUSY)                  {                      errMsg = string.Format("Could not open pipe {0} with error {1}"  pipeName,errCode);                     Trace.WriteLine(errMsg);                     throw new Win32Exception(errCode, errMsg);                 }                 // If it was busy, see if we can wait it out                 else if (!NamedPipeInterop.WaitNamedPipe(_pipeName, (uint)_retryTimeout))                 {                     errCode = Marshal.GetLastWin32Error();                      errMsg =                          string.Format("Wait for pipe {0} timed out after {1} milliseconds  with error code {2}.",                                    _pipeName, _retryTimeout,errCode);                      Trace.WriteLine(errMsg);                      throw new Win32Exception(errCode, errMsg);                 }             }             // Indicate connection in debug             Trace.WriteLine("Connected to pipe: " + _pipeName);             // The pipe connected; change to message-read mode             bool success = false;             int mode = (int)NamedPipeInterop.PIPE_READMODE_MESSAGE;             // Set to message mode             success = NamedPipeInterop.SetNamedPipeHandleState(                 _handle,    // Pipe handle                 ref mode,  // New pipe mode                 IntPtr.Zero,     // Don't set maximum bytes                 IntPtr.Zero);   // Don't set maximum time             // Currently implemented for just synchronous, message-based pipes             // so bail if we couldn't set the client up properly             if (false == success)             {                 errCode = Marshal.GetLastWin32Error();                  errMsg =                      string.Format("Could not change pipe mode to message with error code                        {0}",                         errCode);                 Trace.WriteLine(errMsg);                  Dispose();                  throw new Win32Exception(errCode, errMsg);             }         }         /// <summary>         /// WriteMessage - write an array of bytes and return the response from the         /// server         /// </summary>         /// <param name="buffer">bytes to write</param>         /// <param name="bytesToWrite">number of bytes to write</param>         /// <returns>true if written successfully</returns>         public MemoryStream WriteMessage(byte[] buffer, // the write buffer         uint bytesToWrite) // Number of bytes in the write buffer         // Message responses         {             // Buffer to get the number of bytes read/written back             byte[] _numReadWritten = new byte[4];             MemoryStream responseStream = null;             bool success = false;             // Write the byte buffer to the pipe             success = NamedPipeInterop.WriteFile(_handle,                 buffer,                 bytesToWrite,                 _numReadWritten,                 0);             if (success)             {                  byte[] responseBuffer = new byte[_responseBufferSize];                  responseStream = new MemoryStream(_responseBufferSize);                  {                     do                     {                          // Read the response from the pipe.                          success = NamedPipeInterop.ReadFile(                             _handle,    // Pipe handle                              responseBuffer,   // Buffer to receive reply                              (uint)_responseBufferSize,// Size of buffer                              _numReadWritten, // Number of bytes read                              0); // Not overlapped                         // Failed, not just more data to come                         if (!success && Marshal.GetLastWin32Error() != NamedPipeInterop                 ERROR_MORE_DATA)                             break;                        // Concat response to stream                        responseStream.Write(responseBuffer,                             0,                            responseBuffer.Length);                    } while (!success);               }           }           return responseStream;         }         #endregion     }  } 

Then you need to create a server class for testing, which you can call NamedPipeServer, as shown in Example 16-5.

Example 16-5. NamedPipeServer class

 using System;  using System.Collections.Generic;  using System.Text;  using System.Runtime.InteropServices;  using System.Diagnostics;  using System.ComponentModel;  using System.IO;  using System.Threading;  using Microsoft.Win32.SafeHandles; namespace NamedPipes {      /// <summary>      /// NamedPipeServer - An implementation of a synchronous, message-based,      /// named-pipe server      ///      /// </summary>      public class NamedPipeServer : IDisposable      {         #region Private Members         /// <summary>         /// The pipe handle         /// </summary>         private SafeFileHandle _handle = new SafeFileHandle(NamedPipeInterop. INVALID_HANDLE_VALUE, true);         /// <summary>         /// The name of the pipe         /// </summary>         private string _pipeName = "";         /// <summary>         /// Default size of message buffer to read         /// </summary>         private int _receiveBufferSize = 1024;         /// <summary>         /// Track if dispose has been called         /// </summary>         private bool disposed = false;         /// <summary>         /// PIPE_SERVER_BUFFER_SIZE set to 8192 by default         /// </summary>         private const int PIPE_SERVER_BUFFER_SIZE = 8192;         #endregion         #region Construction / Cleanup         /// <summary>         /// CTOR         /// </summary>         /// <param name="pipeBaseName">the base name of the pipe</param>         /// <param name="msgReceivedDelegate">delegate to be notified when         /// a message is received</param>         public NamedPipeServer(string pipeBaseName)         {             // Assemble the pipe name             _pipeName = "\\\\.\\PIPE\\" + pipeBaseName;             Trace.WriteLine("NamedPipeServer using pipe name " + _pipeName);         }         /// <summary>         /// Finalizer         /// </summary>         ~NamedPipeServer()         {             Dispose(false);         }         public void Dispose()         {             Dispose(true);             GC.SuppressFinalize(this);         }         private void Dispose(bool disposing)         {             // Check to see if Dispose has already been called.              if (!this.disposed)              {                 ClosePipe();             }             disposed = true;         }         private void ClosePipe()          {              Trace.WriteLine("NamedPipeServer closing pipe");             if (!_handle.IsInvalid)             {                 _handle.Close();             }         }         /// <summary>         /// Close - because it is more intuitive than Dispose…         /// </summary>         public void Close()         {             ClosePipe();         }         #endregion         #region Properties         /// <summary>         /// PipeName         /// </summary>         /// <returns>the composed pipe name</returns>         public string PipeName         {             get             {                 return _pipeName;             }         }         /// <summary>          /// ReceiveBufferSize Property - the size used to create receive buffers          /// for messages received using WaitForMessage          /// </summary>          public int ReceiveBufferSize          {             get             {                 return _receiveBufferSize;             }             set             {                 _receiveBufferSize = value;             }         }         #endregion         #region Public Methods         /// <summary>         /// CreatePipe - create the named pipe         /// </summary>         /// <returns>true is pipe created</returns>         public bool CreatePipe()         {             // Make a named pipe in message mode             _handle = NamedPipeInterop.CreateNamedPipe(_pipeName,                  NamedPipeInterop.PIPE_ACCESS_DUPLEX,                  NamedPipeInterop.PIPE_TYPE_MESSAGE | NamedPipeInterop.PIPE_READMODE_             MESSAGE |                  NamedPipeInterop.PIPE_WAIT,                  NamedPipeInterop.PIPE_UNLIMITED_INSTANCES,                  PIPE_SERVER_BUFFER_SIZE,                  PIPE_SERVER_BUFFER_SIZE,                  NamedPipeInterop.NMPWAIT_WAIT_FOREVER,                  IntPtr.Zero);             // Make sure we got a good one             if (_handle.IsInvalid)             {                 Debug.WriteLine("Could not create the pipe (" +                      _pipeName + ") - os returned " +                     Marshal.GetLastWin32Error());                 return false;             }             return true;         }         /// <summary>         /// WaitForClientConnect - wait for a client to connect to this pipe         /// </summary>         /// <returns>true if connected, false if timed out</returns>         public bool WaitForClientConnect()         {             // Wait for someone to talk to us.              return NamedPipeInterop.ConnectNamedPipe(_handle, IntPtr.Zero);          }         /// <summary>         /// WaitForMessage - have the server wait for a message         /// </summary>         /// <returns>a non-null MessageStream if it got a message, null if timed  out or error          /// </returns>         public MemoryStream WaitForMessage()          {             bool fullyRead = false;             string errMsg = "";             int errCode = 0;             // They want to talk to us, read their messages and write             // replies             MemoryStream receiveStream = new MemoryStream();             byte[] buffer = new byte[_receiveBufferSize];             byte[] _numReadWritten = new byte[4];             // Need to read the whole message and put it in one message             // byte buffer             do             {                 // Read the response from the pipe                 if (!NamedPipeInterop.ReadFile(                     _handle,    // Pipe handle                     buffer,   // Buffer to receive reply                     (uint)_receiveBufferSize,     // Size of buffer                     _numReadWritten,  // Number of bytes read                     0)) // Not overlapped                 {                     // Failed, not just more data to come                     errCode = Marshal.GetLastWin32Error( );                     if (errCode != NamedPipeInterop.ERROR_MORE_DATA)                         break;                     else                     {                         errMsg = string.Format("Could not read from pipe with error {0}", errCode);                         Trace.WriteLine(errMsg);                         throw new Win32Exception(errCode, errMsg);                     }                 }                 else                 {                     // We succeeded and no more data is coming                     fullyRead = true;                 }                 // Concat the message bytes to the stream                 receiveStream.Write(buffer, 0, buffer.Length);             } while (!fullyRead);             if (receiveStream.Length > 0)             {                 // Now set up response with a polite response using the same                 // Unicode string protocol                 string reply = "Thanks for the message!";                 byte[] msgBytes = Encoding.Unicode.GetBytes(reply);                 uint len = (uint)msgBytes.Length;                 // Write the response message provided                 // by the delegate                 if (!NamedPipeInterop.WriteFile(_handle,                     msgBytes,                     len,                     _numReadWritten,                     0))                 {                     errCode = Marshal.GetLastWin32Error( );                     errMsg = string.Format("Could not write response with error {0}", errCode);                     Trace.WriteLine(errMsg);                     throw new Win32Exception(errCode, errMsg);                 }                 // Return the message we received.                 return receiveStream;             }             else // Didn't receive anything                 return null;         }         #endregion     } } 

In order to use the NamedPipeClient class, you need some code like that shown in Example 16-6.

Example 16-6. Using the NamedPipeClient class

 using System;  using System.Collections.Generic;  using System.Text;  using System.IO;  using System.Diagnostics; namespace NamedPipes {      class NamedPipeClientConsole      {         static void Main(string[] args)         {              // Client test code - commented out as it should go in a separate              // console test app             // Create our pipe client             NamedPipeClient _pc =                 new NamedPipeClient("\\\\.\\PIPE\\mypipe");             if (_pc != null)             {                 using (_pc)                 {                     // Connect to the server                     _pc.Connect();                     // Set up a dummy message                     string testString = "This is my message!";                     // Turn it into a byte array                     byte[] writebuffer = Encoding.Unicode.GetBytes(testString);                     uint len = Convert.ToUInt32(writebuffer.Length);                     // Write the message ten times                     for (int i = 0; i < 10; i++)                     {                         MemoryStream response = _pc.WriteMessage(writebuffer, len);                         if (response == null)                         {                             Debug.Assert(false,                                 "Failed to write message!");                         }                         else                         {                             WriteMessageResponse(response);                         }                     }                 }             }             Console.WriteLine("Press Enter to exit…");             Console.ReadLine();         }         static void WriteMessageResponse(MemoryStream responseStream)         {             string response =                 Encoding.Unicode.GetString(responseStream.ToArray());             Console.WriteLine("Received response: {0}", response);         }     } } 

Then, to set up a server for the client to talk to, you use the NamedPipeServer class as shown in Example 16-7.

Example 16-7. Setting up a server for the client

 using System; using System Collections.Generic; using System.Text; using System.IO; using System.ComponentModel; namespace NamedPipes {     class NamedPipeServerConsole     {         static void Main(string[] args)         {             // Server test code - commented out as it should go in a separate             // console test app as shown in the book             // Create pipe server             using (NamedPipeServer _ps =                 new NamedPipeServer("mypipe"))             {                 // Create pipe                 if (_ps.CreatePipe())                 {                     // I get the name of the pipe here just to show you can                     // Normally we would then have to get this name to the client                     // so it knows the name of the pipe to open but hey, I wrote                     // the client to so for now I'm just hardcoding it in the                     // client so we can ignore it                     string pipeName = _ps.PipeName;                     // Wait for clients to connect                     if (_ps.WaitForClientConnect())                     {                         // Process messages until the read fails                         // (client goes away…)                         bool success = true;                         while (success)                         {                             try                             {                                 // Wait for a message from the client                                 MemoryStream messageStream = _ps.WaitForMessage();                                 if (messageStream != null)                                 {                                     // Get the bytes of the message from the stream                                     byte[] msgBytes = messageStream.ToArray();                                     string messageText;                                     // I know in the client I used a Unicode encoding                                     // for the string to turn it into a series of bytes                                     // for transmission so just reverse that                                     messageText = Encoding.Unicode.GetString(msgBytes);                                     // Write out our string message from the client                                     Console.WriteLine(messageText);                               } else                                     success = false;                             }                             catch (Win32Exception)                             {                                 success = false;                             }                         }                     }                 }             }             // Make our server hang around so you can see the messages sent             Console.WriteLine("Press Enter to exit…");             Console.ReadLine();         }     } } 

Discussion

Named pipes are a mechanism to allow interprocess or intermachine communications in Windows. The .NET Framework currently has not provided managed access to named pipes, so the first thing you need to do is to wrap the functions in Kernel32.dll for direct access from managed code in your NamedPipesInterop class.

Once you have this foundation, you can then build a client for using named pipes to talk to a server, as in the NamedPipeClient class. The methods on NamedPipeClient are listed in Table 16-1 with a description for each.

Table 16-1. NamedPipeClient methods

Method

Description

Close

Close method, which calls the Dispose method.

Connect

Connects to a named-pipe server.

Dispose

Dispose method for the named-pipe client so that the pipe handle is not held any longer than necessary.

NamedPipeClient

Constructor for the named-pipe client.

~NamedPipeClient

Finalizer for the named-pipe client. This makes sure the pipe handle is closed.

WriteMessage

Writes a message to the connected server.


You then create the NamedPipeServer class to be able to have something for the NamedPipeClient to connect to. The methods on the NamedPipeServer are listed in Table 16-2 with a description for each as well.

Table 16-2. NamedPipeServer methods

Method

Description

Close

Close method that calls the Dispose method. Many developers use Close, so it is provided for completeness.

CreatePipe

Creates a listener pipe on the server.

Dispose

Dispose method for the named-pipe server so that pipe handles are not held any longer than necessary.

NamedPipeServer

Constructor for the named-pipe server.

~NamedPipeServer

Finalizer for the named-pipe server. This makes sure the pipe handle is closed.

PipeName

Returns the composed pipe name.

WaitForClientConnect

Wait on the pipe handle for a client to talk to.

WaitForMessage

Have the server wait for a message from the client.


Finally, you create some code to use NamedPipeClient and NamedPipeServer. The interaction between these two goes like this:

  1. The server process is started; it fires up a NamedPipeServer, calls CreatePipe to make a pipe, then calls WaitForClientConnect to wait for the NamedPipeClient to connect:

     using (NamedPipeServer _ps =     new NamedPipeServer("mypipe")) {     // Create pipe     if (_ps.CreatePipe())     {         // Wait for clients to connect         if (_ps.WaitForClientConnect())          { 

  2. The client process is created; it fires up a NamedPipeClient, calls Connect, and connects to the server process:

     // Create our pipe client NamedPipeClient _pc =     new NamedPipeClient("\\\\.\\PIPE\\mypipe"); if (_pc != null) {     using (_pc)     {         // Connect to the server         _pc.Connect(); 

  3. The server process sees the connection from the client and then calls WaitForMessage in a loop. WaitForMessage starts reading the pipe, which blocks until a message is written to the pipe by the client.

         // Process messages until the read fails     // (client goes away…)     bool success = true;     while (success)     {         try         {             // Wait for a message from the client             MemoryStream messageStream = _ps.WaitForMessage();             // More processing code in here…         }         catch (Win32Exception)         {             success = false;         }     } 

  4. The client process then writes a number of messages to the server process using WriteMessage:

         // Set up a dummy message     string testString = "This is my message!";     // Turn it into a byte array     byte[] writebuffer = Encoding.Unicode.GetBytes(testString);     uint len = Convert.ToUInt32(writebuffer.Length);     // Write the message ten times     for (int i = 0; i < 10; i++)     {         MemoryStream response = _pc.WriteMessage(writebuffer, len);         if (response == null)         {             Debug.Assert(false,                 "Failed to write message!");         }         else         {             WriteMessageResponse(response);         }     } 

  5. In WaitForMessage, shown in Example 16-8, the server process sees the message, processes it, writes a response to the client, returns a MemoryStream with the received message in it, then goes back to waiting.

    Example 16-8. WaitForMessage method

     public MemoryStream WaitForMessage() {     bool fullyRead = false;     string errMsg = "";     int errCode = 0;     // They want to talk to us, read their messages and write     // replies     MemoryStream receiveStream = new MemoryStream();     byte[] buffer = new byte[_receiveBufferSize];     byte[] _numReadWritten = new byte[4];     // Need to read the whole message and put it in one message     // byte buffer     do     {         // Read the response from the pipe         if (!NamedPipeInterop.ReadFile(             _handle,    // Pipe handle             buffer,    // Buffer to receive reply             (uint)_receiveBufferSize,      // Size of buffer             _numReadWritten,  // Number of bytes read             0)) // Not overlapped         {             // Failed, not just more data to come             errCode = Marshal.GetLastWin32Error();             if (errCode != NamedPipeInterop.ERROR_MORE_DATA)                 break;             else             {                 errMsg = string.Format("Could not read from pipe with error {0}", errCode);                 Trace.WriteLine(errMsg);                 throw new Win32Exception(errCode, errMsg);             }         }         else         {             // We succeeded and no more data is coming             fullyRead = true;         }         // Concat the message bytes to the stream         receiveStream.Write(buffer, 0, buffer.Length);     } while (!fullyRead);     if (receiveStream.Length > 0)     {         // Now set up response with a polite response using the same         // Unicode string protocol         string reply = "Thanks for the message!";         byte[] msgBytes = Encoding.Unicode.GetBytes(reply);         uint len = (uint)msgBytes.Length;         // Write the response message provided         // by the delegate         if (!NamedPipeInterop.WriteFile(_handle,             msgBytes,             len,             _numReadWritten,             0))         {             errCode = Marshal.GetLastWin32Error();             errMsg = string.Format("Could not write response with error {0}",             errCode);             Trace.WriteLine(errMsg);             throw new Win32Exception(errCode, errMsg);         }         // Return the message we received         return receiveStream;     }     else // Didn't receive anything         return null; } 

  6. When the client process receives the response from the server, it returns a MemoryStream with the response for processing. If the message sending is complete, the NamedPipeClient goes out of the scope of the using statement and closes (thereby closing the connection on the client side) and then waits to go away when the user presses Enter.

                     // Write a message and get a response stream                 MemoryStream response = _pc.WriteMessage(writebuffer, len);                 if (response == null)                 {                     Debug.Assert(false,                         "Failed to write message!");                 } else                 {                     // Process response message to console                     WriteMessageResponse(response);                 } static void WriteMessageResponse(MemoryStream responseStream) {     string response =         Encoding.Unicode.GetString(responseStream.ToArray());     Console.WriteLine("Received response: {0}", response); } 

  7. The server process notes that the client has closed the pipe connection via the failed NamedPipesInterop.ReadFile call in WaitForMessage. It calls Close to clean up, then waits for the user to press Enter to terminate the process.

The client output looks like this:

 Received response: Thanks for the message! Received response: Thanks for the message! Received response: Thanks for the message! Received response: Thanks for the message! Received response: Thanks for the message! Received response: Thanks for the message! Received response: Thanks for the message! Received response: Thanks for the message! Received response: Thanks for the message! Received response: Thanks for the message! Press Enter to exit… 

The server output looks like this:

 This is my message! This is my message! This is my message! This is my message! This is my message! This is my message! This is my message! This is my message! This is my message! This is my message! Press Enter to exit… 

See Also

See the "Named Pipes," "DllImport Attribute," "IDisposable Interface," and "GC. SuppressFinalize Method" topics in the MSDN documentation.



C# Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2004
Pages: 424

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net