CNamedPipeArray

A C++ class that encapsulates an array of instances of a Win32 named pipe
by
Dr. Thomas Becker
tmbecker@compuserve.com


Contents

  1. Copyright and Permission Notice
  2. Overview of CNamedPipeArray
  3. CNamedPipeArray Public Members
  4. CNamedPipeArray Exception Handling
  5. CNamedPipeArray Example

Copyright and Permission Notice

COPYRIGHT (C) 1997 Thomas Becker

PERMISSION NOTICE:

PERMISSION TO USE, COPY, MODIFY, AND DISTRIBUTE THIS SOFTWARE FOR ANY PURPOSE AND WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT ALL COPIES ARE ACCOMPANIED BY THE COMPLETE MACHINE-READABLE SOURCE CODE, ALL MODIFIED FILES CARRY PROMINENT NOTICES AS TO WHEN AND BY WHOM THEY WERE MODIFIED, THE ABOVE COPYRIGHT NOTICE, THIS PERMISSION NOTICE AND THE NO-WARRANTY NOTICE BELOW APPEAR IN ALL SOURCE FILES AND IN ALL SUPPORTING DOCUMENTATION.

NO-WARRANTY NOTICE:

THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY. ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF MERCHANTABILITY ARE HEREBY DISCLAIMED.

Overview of CNamedPipeArray

template<DWORD dwNumPipeInstances>
class CNamedPipeArray

The CNamedPipeArray class template provides an array of instances of a named pipe and functions to read and write the server end of these pipe instances.

The template argument specifies the number of instances of the named pipe. It is easy to change the source code so that this number can be set at runtime: make the template argument a class member that is set by the constructor, find the places where it occurs as the length parameter in array declarations, and replace these array declarations with heap allocations/deallocations in the constructor and destructor.

The constructor takes as its arguments the name of the pipe and the handle to the exit event. The pipe name must be of the form "\\.\pipe\mypipe". Clients must replace the "." with the name of the server machine. Setting the exit event causes all member functions to abandon their respective waits and to return immediately. Both the pipe name and the handle to the exit event can be set later by means of the SetPipeName and SetHandleToExitEvent member functions, respectively. Note that both the pipe name and the handle to the exit event must be set before the Listen member function is called for the first time. The constructor only takes care of the internal setup of the class object; no pipes are created yet.

The pipe instances are actually created and connected when the Listen member function is called for the first time. This function then waits for clients to connect. When a client has connected, the function provides the index of the pipe instance that the client is using. The Listen member function is typically called by a listen thread in an infinite loop.

The index that the Listen member function returns can be used to read, write, flush, and reconnect the pipe instances by means of the Read, Write, Flush, and Reconnect member functions. Internally, these functions use asynchronous read and write operations so they can react immediately to an exit request. To the user of the class, however, the read and write operations appear to be synchronous.

All member functions throw CWin32Exceptions when they encounter an error.

If the destructor encounters an error, it throws a CWin32Exception but does not propagate it.

NOTE: The class CNamedPipeArray will typically be used in a multithreaded server application where the Listen member function will sit in an infinite loop, wait for clients to connect, and pass the pipe indices on to worker threads. For robustness it is important to create the worker threads once and for all at startup. An alternative would be to create a new thread for each client. Unfortunately, Win32 named pipes exhibit strange behavior when a thread that has just finished communicating through a pipe instance is terminated.

CNamedPipeArray Public Members

Enums
waitResult Return values from functions that perform wait operations.
Construction
CNamedPipeArray Constructs a CNamedPipeArray object.
Operations
Listen Listens for clients to connect.
Read Reads from the server end of a pipe instance.
Write Writes to the server end of a pipe instance.
Flush Flushes the server end of a pipe instance.
Reconnect Performs a DisconnectNamedPipe and subsequent asynchronous ConnectNamedPipe on the server end of a pipe instance.
SetHandleToExitEvent Places the handle to the exit event in a private member variable.
SetPipeName Places the pipe name in a private member variable.

CNamedPipeArray::waitResult

Return values from functions that perform wait operations.
enum waitResult
{
  waitTimeout, // timeout occurred
  waitExitEvent, // exit event was set
  waitSuccess  // operation completed successfully
} ;

CNamedPipeArray::CNamedPipeArray

The constructor sets the m_pchPipeName and m_hExitEvent member variables from the arguments. It then initializes all other member variables in a meaningful way.
template<DWORD dwNumPipeInstances>
CNamedPipeArray<dwNumPipeInstances>::CNamedPipeArray(
  LPTSTR pchPipeName, // = NULL, pipe name
  HANDLE hExitEvent // = NULL, handle of exit event
  );

Parameters

pchPipeName
Points to the name of the pipe.
hExitEvent
Indicates the exit event that causes all member functions to return immediately. This handle can be set later by means of the SetHandleToExitEvent member function. However, the handle must be set to a valid event handle before the Listen member function is called for the first time.

CNamedPipeArray::Listen

The Listen member function waits for a client to connect to one of the pipe instances, then returns the index of the connected pipe.
template<DWORD dwNumPipeInstances>
CNamedPipeArray<dwNumPipeInstances>::waitResult 
CNamedPipeArray<dwNumPipeInstances>::Listen(
  DWORD& refdwIndex, // reference to index
  DWORD dwMilliseconds // timeout
  );

Parameters

refdwIndex
Refers to the DWORD location to receive the index of the connected pipe.
dwMilliseconds
Indicates the timeout value in milliseconds or INFINITE.

Return Value and Exceptions

If the function succeeds, the return value is waitSuccess.
If the function notices that the exit event was set, the return value is waitExitEvent.
If the function fails, a CWin32Exceptions is thrown.

Remarks

The function creates and connects the named pipes when it is called for the first time. In case of an exception, pipes and events are cleaned up.

To terminate the listening, set the exit event. This will also cause all read and write operations to return immediately. Note that there may still be asynchronous read or write operations in progress in this case.

After a timeout has occurred, the Listen function can be called again any time. Clients that have connected during the absence of Listen will not be lost unless they themselves have closed their handles.

When the function is called with a timeout of 0 ms, it is guaranteed that the return value will be waitTimeout. The only effect of the function in this case is to create and connect the named pipes if they do not already exist.


CNamedPipeArray::Read

The Read member function reads from a pipe instance whose index has been obtained from the Listen member function.
template<DWORD dwNumPipeInstances>
CNamedPipeArray<dwNumPipeInstances>::waitResult 
CNamedPipeArray<dwNumPipeInstances>::Read(
  DWORD dwIndex, // index of pipe instance to read 
  LPVOID lpBuffer, // address of buffer that receives data  
  DWORD dwNumberOfBytesToRead, // number of bytes to read 
  LPDWORD lpdwNumberOfBytesRead, // &number of bytes read 
  DWORD dwMilliseconds // timeout value
  );

Parameters

dwIndex
Indicates the index of the pipe instance to read from. The index must have been obtained from the Listen member function.
lpBuffer
As in ReadFile.
dwNumberOfBytesToRead
As in ReadFile.
lpdwNumberOfBytesRead
As in ReadFile.
dwMilliseconds
Indicates the timeout in milliseconds for the wait operation, or INFINITE.

Return Value and Exceptions

If the function succeeds, the return value is waitSuccess.
If the wait operation timed out, the return value is waitTimeout.
If the function notices that the exit event was set, the return value is waitExitEvent.
If the function fails, a CWin32Exception is thrown.

Remarks

When the client closes her end of the pipe, the function throws an exception with error code ERROR_BROKEN_PIPE. In this case, the pipe instance can simply be reconnected by means of the Reconnect member function. All other exceptions should be considered unrecoverable.

CNamedPipeArray::Write

The Write member function writes to a pipe instance whose index has been obtained from the Listen member function.
template<DWORD dwNumPipeInstances>
CNamedPipeArray<dwNumPipeInstances>::waitResult 
CNamedPipeArray<dwNumPipeInstances>::Write(
  DWORD dwIndex, // index of pipe instance to write 
  LPCVOID lpBuffer, // pointer to data to write to file  
  DWORD dwNumberOfBytesToWrite, // number of bytes to write 
  LPDWORD lpdwNumberOfBytesWritten, // number of bytes written 
  DWORD dwMilliseconds // timeout value
  );

Parameters

dwIndex
Indicates the index of the pipe instance to write to. The index must have been obtained from the Listen member function.
lpBuffer
As in WriteFile.
dwNumberOfBytesToRead
As in WriteFile.
lpdwNumberOfBytesRead
As in WriteFile.
dwMilliseconds
Indicates the timeout in milliseconds for the wait operation, or INFINITE.

Return Value and Exceptions

If the function succeeds, the return value is waitSuccess.
If the wait operation timed out, the return value is waitTimeout.
If the function notices that the exit event was set, the return value is waitExitEvent.
If the function fails, a CWin32Exception is thrown.

Remarks

When the client closes her end of the pipe, the function throws an exception with error code ERROR_NO_DATA. In this case, the pipe instance can simply be reconnected by means of the Reconnect member function. All other exceptions should be considered unrecoverable.

CNamedPipeArray::Flush

The Flush member function calls the FlushFileBuffers API on the pipe instance with the specified index.
template<DWORD dwNumPipeInstances>
void CNamedPipeArray<dwNumPipeInstances>::Flush(
  DWORD dwIndex // pipe index
  );

Parameters

dwIndex
Indicates the index of the pipe instance to be flushed.

Return Value and Exceptions

If the function fails, a CWin32Exception is thrown.

Remarks

The function must be called to prevent loss of data that the client has not yet read when the server writes to a pipe and then disconnects that pipe.

When the client closes her end of the pipe, the function throws an exception with error code ERROR_BROKEN_PIPE. In this case, the pipe instance can simply be reconnected by means of the Reconnect member function. All other exceptions should be considered unrecoverable.

Note: there is no timeout or simultaneous wait for an exit event. A client who refuses to read can hang the server here. It is recommended to use a protocol that requires the client to send a Roger at the end of a transaction. This avoids calls to the Flush function altogether.


CNamedPipeArray::Reconnect

The Reconnect member function disconnects and reconnects a pipe instance after a client has been served on the pipe instance. This function must only be called for indices that have been obtained from the Listen member functions.
template<DWORD dwNumPipeInstances>
void CNamedPipeArray<dwNumPipeInstances>::Reconnect(
  DWORD dwIndex // pipe index
  );

Parameters

dwIndex
Indicates the index of the pipe to be reconnected.

Return Value and Exceptions

If the function fails, a CWin32Exception is thrown.

CNamedPipeArray::SetHandleToExitEvent

Places the handle to the exit event in a private member variable.
template<DWORD dwNumPipeInstances>
void CNamedPipeArray<dwNumPipeInstances>::SetHandleToExitEvent(
  HANDLE hExitEvent 
  );

CNamedPipeArray::SetPipeName

Places the pipe name in a private member variable.
template<DWORD dwNumPipeInstances>
void CNamedPipeArray<dwNumPipeInstances>::SetPipeName(
  LPTSTR pchPipeName 
  );

CNamedPipeArray Exception Handling

The implementation of CNamedPipeArray uses Win32 APIs. All errors are thus identified by the error code that is returned by the GetLastError() API. When such an error occurs, the CNamedPipeArray functions throw an exception of type CWin32Exception. CWin32Exception is a simple class that stores the error code as well as the name of the source file and the number of the line where the error occurred. The declaration of CWin32Exception can be found in the file Win32Exception.h.

Win32Exception.h also contains the declaration of a class named CSehException, which can be used to catch operating system exceptions (SEH-exceptions) such as access violations via the C++ exception handling mechanism. See the documentation of the _set_se_translator() function and the article "Structured exception handling" in the C++ Language Reference for a detailed explanation.

CNamedPipeArray Example

A complete example of an application of the CNamedPipeArray class can be found in the file MTServerV1.cpp. The following code fragments are excerpts from that file.

The client side is not affected when the CNamedPipeArray class is used instead of Win32 named pipe APIs. An example can be found in the file MTClient.cpp.

The program creates an instance of the CNamedPipeArray class as a global variable:

#define NUM_PIPE_INSTANCES 5
#define PIPE_NAME "\\\\.\\pipe\\MTServerPipe"
CNamedPipeArray<NUM_PIPE_INSTANCES> cThePipeArray(PIPE_NAME) ;
The handle to the exit event is set later on, after the event has been created:
cThePipeArray.SetHandleToExitEvent(hExitEvent) ;
A separate thread calls the Listen member function in an infinite loop. Everytime a client has connected, the index of the pipe instance that the client is using is placed into a queue:
DWORD dwPipeIndex ;
CNamedPipeArray<NUM_PIPE_INSTANCES>::waitResult enListenRetVal ;

while ( 1 )
{
  enListenRetVal = cThePipeArray.Listen(
    dwPipeIndex,
    INFINITE
    ) ;

  if ( CNamedPipeArray<NUM_PIPE_INSTANCES>::waitExitEvent 
         == enListenRetVal ) break ;
      
  //
  // Place index dwIndex of connected pipe in the queue.
  //

}
The indices of connected pipe instances are retrieved from the queue by worker threads. These threads perform read and write operations on the pipe instance and then reconnect it:
DWORD dwPipeIndex ;

while(1)
{
      
  //
  // Retrieve a pipe index from the queue and place it in the variable 
  // dwPipeIndex.
  //
      
  // Call the Read, Write, and Flush member functions on the index
  // dwPipeIndex. Return value false means exit event has been set.
  //
  if ( ! ServeClient(dwPipeIndex) ) break ;

  // Disconnect named pipe and reconnect asynchronously.
  //
  cThePipeArray.Reconnect(dwPipeIndex) ;

}