A C++ class that encapsulates an array of instances of a Win32 named pipe
by
Dr. Thomas Becker
tmbecker@compuserve.com
COPYRIGHT (C) 1997 Thomas BeckerPERMISSION 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.
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.
waitResult | Return values from functions that perform wait operations. |
CNamedPipeArray | Constructs a CNamedPipeArray object. |
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. |
enum waitResult { waitTimeout, // timeout occurred waitExitEvent, // exit event was set waitSuccess // operation completed successfully } ;
template<DWORD dwNumPipeInstances> CNamedPipeArray<dwNumPipeInstances>::CNamedPipeArray( LPTSTR pchPipeName, // = NULL, pipe name HANDLE hExitEvent // = NULL, handle of exit event );
template<DWORD dwNumPipeInstances> CNamedPipeArray<dwNumPipeInstances>::waitResult CNamedPipeArray<dwNumPipeInstances>::Listen( DWORD& refdwIndex, // reference to index DWORD dwMilliseconds // timeout );
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.
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 );
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 );
template<DWORD dwNumPipeInstances> void CNamedPipeArray<dwNumPipeInstances>::Flush( DWORD dwIndex // pipe index );
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.
template<DWORD dwNumPipeInstances> void CNamedPipeArray<dwNumPipeInstances>::Reconnect( DWORD dwIndex // pipe index );
template<DWORD dwNumPipeInstances> void CNamedPipeArray<dwNumPipeInstances>::SetHandleToExitEvent( HANDLE hExitEvent );
template<DWORD dwNumPipeInstances> void CNamedPipeArray<dwNumPipeInstances>::SetPipeName( LPTSTR pchPipeName );
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.
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) ; }