14.1.2 Named Pipes

Unlike anonymous pipes that can only be used by related processes, a named pipe can transfer information between related or unrelated processes. Subject to security checks, a named pipe can be opened and used by any process that knows its name. To use a named pipe, one process (the server process) creates the pipe and connects to one end of it, while another process (the client process) opens the other end of the pipe. The processes can then communicate by writing to and reading from the pipe. Named pipes can be used locally to communicate between processes on the same machine, or they can be used across a network to connect processes on different machines.

A server process uses CreateNamedPipe to create one or more instances of a named pipe (see Section 0.1.2.2, “Creating a Named Pipe”). The server then uses ConnectNamedPipe to try to connect an instance of the pipe to a client process on the other end. The client process uses CreateFile to open its handle to the pipe. If no instances of the pipe are available, the client process can use WaitNamedPipe to wait for one to become available.

Once the client and server have connected, there are several functions that can be used for reading and writing data to the pipe. The behavior of these functions depends on the type of pipe and on the I/O modes that are in effect for the specified pipe handle. The type of a named pipe can be either byte mode or message mode, and message mode pipes can be read in either byte or message readmode (see Section 0.1.2.3, “Pipe Type and Readmode”). The I/O modes that affect pipe operations are blocking vs. non-blocking (see Section 0.1.2.4, “Blocking and Non-blocking Pipe Operations”), synchronous vs. overlapped (see Section 0.1.2.5, “Synchronous and Asynchronous I/O”), and write-through mode (see Section 0.1.2.6, “Write Through to Remote Clients”).

ReadFile and WriteFile, or ReadFileEx and WriteFileEx, can be used to read and write both byte mode and message mode pipes. PeekNamedPipe can be used to read without removing the contents of either a byte mode or a message mode pipe. PeekNamedPipe can also be used to return additional information about the pipe. Message mode pipes can use TransactNamedPipe to write a request message and read a reply message in a single operation. The client end of a message mode pipe can use CallNamedPipe, to combine into a single operation the functions that open a pipe (waiting for an instance to be available, if necessary), write a message, read a message, and close the pipe.

When the client and server have finished using the pipe, the server calls DisconnectNamedPipe to close the connection to the client process. This function will make the client's handle invalid (if it has not already been closed), and any unread data in the pipe is discarded. To ensure that all data written to the pipe has been read by the client, the server can first call FlushFileBuffers. Once the client has been disconnected, the server can call CloseHandle to close the pipe handle, or it can use ConnectNamedPipe to connect the instance to a new client.

A process can retrieve information about a named pipe by calling GetNamedPipeInfo which returns the type of the pipe, the size of the input and output buffers, and the maximum number of pipe instances that can be created. GetNamedPipeHandleState reports on the readmode and blocking mode of a pipe handle, the current number of pipe instances, and additional information of interest to pipes being used for communication over a network. SetNamedPipeHandleState is used to set the readmode and blocking mode of a pipe handle. For client processes communicating with a remote server, it is also used to control the maximum number of bytes to collect or the maximum time to wait before transmitting a message (assuming the client's handle was not opened with write-through enabled).

14.1.2.1 Pipe Names

Each named pipe must have a unique name that distinguishes it from other named pipes. When specifying the name of a pipe in either CreateNamedPipe, CreateFile, WaitNamedPipe, or CallNamedPipe, the following form must be used:

\\servername\pipe\pipename

The server process cannot create a pipe on a remote machine, so in the CreateNamedPipe function, the servername part of the name must be \\. to indicate the local machine. Client processes using CreateFile to open a named pipe may use a period to specify a local pipe, but must specify the name of a server to open a pipe on a remote machine. The pipename part of the name must conform to the rules for Win32 filenames (refer to Chapter 5, “File I/O”), but no actual file is created for the pipe. The pipename may consist of multiple names separated by \'s, as with directories in a file system pathname. This can help to reduce the chance of duplicating the name used by another application.

14.1.2.2 Creating a Named Pipe

Traditionally a process that creates a named pipe is called the server, and other processes that access the pipe are called clients. But the difference between clients and servers is greatly diminished in Win32 since any Win32 process can act as both a server and a client, allowing peer to peer communication. As used here, server refers to a process that uses CreateNamedPipe to create the named pipe, and client refers to a process that uses CreateFile or CallNamedPipe to open a handle to the pipe.

The first time that the server calls CreateNamedPipe, it specifies the maximum number of instances of the pipe that can exist simultaneously. Limited by this maximum, the server may call CreateNamedPipe repeatedly to create additional instances of the pipe. If the function is successful, each call returns a handle to an instance of the named pipe. This handle can be used for communication between the server and a client process. The server can specify PIPE_UNLIMITED_INSTANCES to allow the number of pipe instances to be limited only by the availability of system resources.

When a named pipe is created, the server must specify the direction that data can flow in the pipe. Named pipes can be created for either bi-directional or uni-directional communications. A bi-directional (duplex) pipe allows both server and client to read and write the pipe. A uni-directional pipe can be either inbound where the client writes to the server, or outbound where the server writes to the client. The read/write access specified by a client using CreateFile to open a named pipe must be compatible with the access mode specified by the server. For example, a client must open an outbound pipe for GENERIC_READ access.

The defaults for named pipes are:

The pipe type is PIPE_TYPE_BYTE

The pipe readmode is PIPE_READMODE_BYTE

Read, write, and connect operations block until complete (PIPE_WAIT)

Overlapped (or asynchronous) I/O is not enabled

Write-through to remote clients is not enabled

The server process may override these defaults by specifying the appropriate flags when the pipe is created. The following sections discuss pipe types and readmodes, blocking and non-blocking I/O, synchronous and asynchronous I/O, and write-through to remote clients.

14.1.2.3 Pipe Type and Readmode

Data can be transmitted through a pipe as either a stream of bytes or as a stream of messages. The type of a pipe determines how data is written to the pipe, and the readmode determines how data is read from the pipe. The default for named pipes is to read and write data as a stream of bytes.

The type of a pipe is specified when the first instance of a pipe is created, and all other instances must be created with the same type. To create a message pipe, PIPE_TYPE_MESSAGE must be specified in the call to CreateNamedPipe. A byte pipe is created by default, or if PIPE_TYPE_BYTE is specified. The type of a pipe cannot be changed. When writing to a message pipe, the block of data from each write operation is tagged by the system with a header containing the size of the message. The header is used internally by the system and is never seen by the server or client processes reading from the pipe.

The default readmode for handles to both byte and message pipes is to read the pipe as a stream of bytes. Byte pipes can only be read in byte readmode, but message pipes can be read in either byte or message readmode. The readmode of the server end of a pipe can be set to read messages by specifying PIPE_READMODE_MESSAGE in the call to CreateNamedPipe or SetNamedPipeHandleState. A named pipe handle returned by CreateFile is always in byte readmode initially, so a client process must use SetNamedPipeHandleState to change to message readmode. The readmode may be different for different instances of a pipe.

In byte readmode, a read operation will complete successfully when all available bytes in the pipe have been read or when the specified number of bytes have been read.

In message readmode, a read operation completes successfully only when the entire message has been read. When a partial message is read, the read operation returns FALSE and GetLastError returns ERROR_MORE_DATA. In this case, additional calls to ReadFile or PeekNamedPipe can be made to read the remainder of the message. If a pipe has multiple unread messages, a message-readmode read operation will return after reading one message, while a byte-readmode operation does not differentiate between the messages and will read all available bytes up to the specified number.

14.1.2.4 Blocking and Non-blocking Pipe Operations

By default (or if PIPE_WAIT was specified when the pipe was created), read, write, and connect operations will block until successful or until an error occurs. Non-blocking operations are enabled for the server end of a pipe by specifying PIPE_NOWAIT when the pipe is created. Blocking or non-blocking operations can be enabled for either a client or server pipe handle by calling SetNamedPipeHandleState.

For a ReadFile operation, the difference between blocking and non-blocking mode occurs when the pipe is empty. In blocking mode, the read will wait until data is available. In non-blocking mode, the read will complete immediately with GetLastError returning ERROR_NO_DATA. If the pipe is not empty, a read in either mode will read all available or the specified number of bytes.

For a WriteFile operation, the difference between blocking and non-blocking mode occurs when there is not enough space in the pipe's buffer to hold all the data to be written. In blocking mode, the write will wait until a process reads from the other end of the pipe to create more room. In non-blocking mode, the behavior differs for byte mode and message mode pipes. For a message mode pipe, the non-blocking write will complete immediately returning TRUE without writing any bytes. For a byte mode pipe, the non-blocking write will return immediately after writing as many bytes as the buffer will hold.

In non-blocking mode, the ConnectNamedPipe function will return FALSE and GetLastError returns ERROR_PIPE_LISTENING if no client process is waiting to connect. In blocking mode, the function will block until a client uses CreateFile to connect to the pipe.

The blocking mode does not effect read or write operations in TransactNamedPipe or CallNamedPipe; and non-blocking operations do not really make sense for overlapped I/O.

14.1.2.5 Synchronous and Asynchronous I/O

The ReadFile, WriteFile, TransactNamedPipe, and ConnectNamedPipe functions on a named pipe may be performed either synchronously or asynchronously. Another set of functions, ReadFileEx and WriteFileEx, may only be performed asynchronously. By default, pipe handles allow only synchronous operations. To enable asynchronous (overlapped) operations, you must specify FILE_FLAG_OVERLAPPED in the CreateNamedPipe call for a server process or in the CreateFile call for a client process.

When I/O operations are performed synchronously, the functions will not return until the operation has completed. If a handle has been opened for asynchronous I/O and an OVERLAPPED structure is specified in the function call, the functions will return immediately even though the operation may not have been completed. This prevents the calling process from blocking on lengthy operations, allowing it to continue with other processing. The process may use any of the wait functions, WaitForSingleObject, WaitForMultipleObjects, WaitForSingleObjectEx or WaitForMultipleObjectsEx, to determine when an overlapped operation has completed. Using overlapped I/O allows a process to perform simultaneous operations on multiple files or pipes, or even to perform multiple operations simultaneously on the same pipe handle. This is particularly useful in allowing a single threaded server process to handle communications with multiple clients.

When a process calls ReadFile, WriteFile, TransactNamedPipe, or ConnectNamedPipe on a handle opened for overlapped I/O, it must specify a pointer to an OVERLAPPED structure. Otherwise, the function will execute synchronously. The OVERLAPPED structure should contain a handle to a manual reset Event object (see Chapter 4, “Synchronization”). This Event object is reset to the Not-Signalled state when the I/O operation begins, and it is set to the Signalled state when the operation is completed. If the OVERLAPPED structure contains a NULL pointer instead of a handle to an Event object, the pipe handle will be signalled on completion of the operation. Any of the wait functions may be used to determine when the Event object (or pipe handle) is Signalled.

It is dangerous to use the pipe handle rather than an Event object if you are performing simultaneous operations on the same handle. Since the handle is Signalled every time an I/O operation on it completes and it is reset to Not-Signalled every time a new I/O operation begins, you would have no way of knowing which operation's completion caused the pipe handle to be Signalled. If you must do simultaneous operations on the same handle, the only safe technique is to use a separate OVERLAPPED structure with its own Event object for each operation.

When ReadFile, WriteFile, TransactNamedPipe, or ConnectNamedPipe are used asynchronously, the function will return FALSE with GetLastError returning ERROR_IO_PENDING if the I/O operation has not completed. In this case, you must use GetOverlappedResult to determine the results of the operation. If the I/O operation completes before the function returns, the function's return value indicates the success or failure of the operation (and for ReadFile, WriteFile and TransactNamedPipe the number of bytes transferred is provided). Note that GetOverlappedResult only reports the results of functions that returned FALSE with ERROR_IO_PENDING.

The ReadFileEx and WriteFileEx functions provide another form of overlapped I/O. These functions specify a completion routine which is enqueued for execution when the I/O operation completes. The completion routine will be executed when the thread that started the operation enters an alertable wait. A thread is in an alertable wait when it is blocked in a call to WaitForSingleObjectEx, WaitForMultipleObjectsEx, or SleepEx with the alertable flag set to TRUE. An alertable wait function will return WAIT_IO_COMPLETION when a completion routine is ready to be executed. ReadFileEx and WriteFileEx can use the hEvent member of an OVERLAPPED structure to pass information to the completion routine, since an Event object is not needed.

14.1.2.6 Write Through to Remote Clients

The default behavior for a write operation to a client or a server on a remote machine is to enhance the efficiency of network operations by buffering data until a minimum number of bytes have accumulated to be written or until a maximum time period has elapsed. This allows multiple writes to be combined into a single network transmission. This also means that a write operation may complete successfully when the data is in the outbound buffer but before it has been transmitted across the network.

On the client end of the pipe, the number of bytes and timeout period before transmission can be controlled using the SetNamedPipeHandleState function.

This performance enhancement can be prevented by specifying FILE_FLAG_WRITE_THROUGH in the CreateNamedPipe call when the pipe is created or in CreateFile when the client opens its end of the pipe. The write-through mode prevents transmission from being delayed and ensures that the write operation will not complete until the data is in the pipe buffer on the remote machine. This “write-through” to the remote client is useful for applications that need synchronization with every write operation.

14.1.2.7 Pipe Security

ImpersonateNamedPipeClient is used by a server process to assume the security token of the client process that is connected to the specified pipe instance. This can be useful in determining whether the request of a client process should be granted. For example, a typical use of a named pipe server would be to provide access to a database or file system to which the server process has privileged access. When a client process makes a request from the server, the client will typically have some lesser level of security access. By assuming the security token of the client, the server can attempt to access the protected database and the system will grant or deny the server's access based on the security level of the client. When finished, the server uses the RevertToSelf function to restore its original security token.