CPGuide - Pipes
Reprint Courtesy of International Business Machines Corporation, © International Business Machines Corporation
Communication between processes is valuable in a multitasking operating system to enable concurrent processes to work together. Pipes are one of three forms of interprocess communication (IPC), the other forms of IPC being semaphores and queues.
This chapter describes how to create, manage, and use pipes. Pipes enable two or more processes to communicate as if they were reading from and writing to a file.
The following topics are related to the information in this chapter:
- Memory (shared memory)
- Program execution and control
- Semaphores
- Queues
About Pipes
A pipe is a named or unnamed buffer used to pass data between processes. A process writes to or reads from a pipe as if the pipe were standard input or standard output. A parent process can use pipes to control the input that a child process receives and to receive the output that the child process produces.
There are two types of pipes-named and unnamed.
Unnamed Pipes
An unnamed pipe is a circular buffer in memory. The buffer has in and out pointers that are maintained by the system.
An unnamed pipe can transfer information only between related processes. A child process started by a parent process with DosExecPgm inherits the handles to any unnamed pipes created by its parent. This inheritance enables the parent process and the child process to use the unnamed pipe to communicate with one another. This type of pipe is typically used to redirect the standard input and standard output of a child process.
To do this, a process opens a pipe and duplicates the read and write handles of the pipe as the standard input and standard output files for the child process. Once the handles are duplicated, the parent process can use DosExecPgm to start the child process. When the child process reads and writes to its standard input and standard output handles, it is reading and writing to the pipe. The parent process can also communicate with the child process through the pipe.
Using an unnamed pipe, a text editor could run another program, such as a compiler or assembler, and display the output of the compiler or assembler within the editor.
DosCreatePipe creates an unnamed pipe. This function returns two file handles for the pipe, one for writing to the pipe and another for reading from the pipe. A process can then write to the pipe by using DosWrite and read from the pipe by using DosRead.
A pipe exists until both handles are closed. The order in which the handles are closed is sometimes important. For example, DosWrite might wait for data to be read from the pipe before completing its operation. In this case, the read handle is closed before the write handle is closed, writing to the pipe generates an error.
No control or permission mechanisms or checks are performed on operations to unnamed pipes.
Named Pipes
Named pipes enable related or unrelated processes on either the same computer system or different systems to communicate with each other. Any process that knows the name of a pipe can open and use a named pipe. In addition, named pipe data can be transparently redirected across a network, such as a local area network (LAN). (Unnamed pipes, by contrast, can be used only by related processes that are on the same computer system.)
One process (the server process) creates the pipe and connects to one end of it. Other processes that access the named pipe are called client processes; they connect to the other end of the pipe. The server and client processes can then pass data back and forth by reading from and writing to the pipe. The server process controls access to the named pipe.
The client process can be either local or remote. A local client process is one that runs on the same computer system as the server process. A remote client process runs on a different system and communicates with the server process across a local area network (LAN).
When the server process creates a named pipe with DosCreateNPipe, it must specify the direction that data will flow through the pipe. The process specifies an inbound pipe if it intends to read data from the client process, an outbound pipe if it intends to write data to the client process, or a duplex pipe if it intends to read from and write to the client process.
The server process also specifies whether data passes through the pipe as bytes or messages. A message is a block of data, with a system-supplied header, that is read or written as a single unit. The server and client processes define the size and format of a message.
The server process also specifies whether child processes will inherit the named pipe and how information will be read from and written to the pipe. If the server specifies wait mode, DosRead will be blocked (it will not return to the process) until data is available in the pipe, and DosWrite will be blocked until there is enough room in the pipe to contain the entire data buffer. If the server specifies no-wait mode, reading from an empty pipe or writing to a full pipe immediately returns an error value.
A named pipe consists of two pipe buffers, one for each direction of communication. However, each end of the pipe has only one handle associated with it. The server receives the handle for its end when it creates the pipe with DosCreateNPipe. The client receives the handle for its end when it opens the pipe with DosOpen.
The server and the client use their respective handles both to read from the pipe and to write to it. (This is in contrast to unnamed pipes, for which both the server and the client read from one handle and write to another.) In other words, data that is written by the process at one end of the pipe is read by the process at the other end.
A named pipe can have multiple instances, up to the number specified when the pipe is first created. Pipe instances are actually separate pipes-that is, unique sets of pipe buffers with unique handles-that share the same name. The ability to create multiple pipe instances enables the server to communicate with multiple client processes at the same time.
Server-Client Communications Using Named Pipes
A server process initiates a connection to a client process by using DosConnectNPipe. Once the pipe has been connected by the server process, the client process must open the pipe by using DosOpen to complete the connection. If no client process has opened the pipe when the server process calls DosConnectNPipe, the function either waits until a client opens the pipe or returns ERROR_PIPE_NOT_CONNECTED, depending on whether the server process created the pipe to wait for data.
Each client process requires a separate instance of a pipe. For example, if a server process creates a named pipe and specifies that four instances of the pipe can be created, the process can then create three more instances of the named pipe (for a total of four pipes, including the original). Each instance has the same name, but each has a unique pipe handle. The process can then connect each pipe to the server. (Each instance must be connected by an explicit use of DosConnectNPipe.) In this example, the server must use each function four times to create and connect four separate instances of the named pipe. Each pipe is then available to a separate client process.
If a client process receives the ERROR_PIPE_BUSY return value from DosOpen, no instances of the given pipe are available. The process can use DosWaitNPipe to wait for an instance to become available. The function waits until an instance is free or until the specified time interval elapses. When an instance becomes free, the process can open the pipe. If several processes are waiting for an instance to become available, the system gives the named pipe to the process that has been waiting the longest.
The server process can disconnect a client process from a pipe by using DosDisConnectNPipe. Ideally, the client process closes the pipe by using DosClose before the server process disconnects the pipe. If the client process has not closed the pipe when the server process disconnects it, the server process forces the pipe closed and the client process subsequently receives errors if it attempts to access the pipe. Note that forcing the closure of the pipe might discard data in the pipe before the client process has had an opportunity to read it.
A process can read and write bytes to a named pipe by using DosRead and DosWrite. A process can read or write messages by using DosTransactNPipe. Depending on the access mode, DosTransactNPipe writes a message to the pipe, reads a message from the pipe, or both. If a named pipe contains unread data or is not a message pipe, DosTransactNPipe fails.
Named pipes created with the NP_ACCESS_INBOUND or NP_ACCESS_OUTBOUND access mode cannot use the DosTransactNPipe function. If the named pipe's client uses the DosTransactNPipe function, the function returns error code 5 (ERROR_ACCESS_DENIED).
If it is reading from the pipe, DosTransactNPipe does not return until a complete message is read, even if the server process specified no-wait mode when the pipe was created.
A process can also read data from a named pipe without removing the data from the pipe by using DosPeekNPipe. This function copies the specified number of bytes from the pipe and returns the number of bytes of data left in the pipe and the number of bytes left in the current message, if any.
DosPeekNPipe also returns the state of the pipe. A named pipe can be in one of the following states:
- Named Pipe States
┌───────────────┬─────────────────────────────────────────────┐ │State │Description │ ├───────────────┼─────────────────────────────────────────────┤ │Connected │The pipe has been created and connected by │ │ │the server process and has been opened by a │ │ │client process. Only connected pipes can be │ │ │written to or read from. │ ├───────────────┼─────────────────────────────────────────────┤ │Closing │The pipe has been closed by the client │ │ │process but has not yet been disconnected by │ │ │the server process. │ ├───────────────┼─────────────────────────────────────────────┤ │Disconnected │The pipe has been created by the server │ │ │process but not connected, or has been │ │ │explicitly disconnected and not yet │ │ │reconnected. A disconnected pipe cannot │ │ │accept a DosOpen request. │ ├───────────────┼─────────────────────────────────────────────┤ │Listening │The pipe has been created and connected by │ │ │the server process but has not yet been │ │ │opened by a client process. A listening pipe │ │ │is ready to accept a request to open. If the │ │ │pipe is not in a listening state, DosOpen │ │ │returns ERROR_PIPE_BUSY. │ └───────────────┴─────────────────────────────────────────────┘
DosCallNPipe is used to open, write to, read from, and close a named message-format pipe.
Named pipes created with the NP_ACCESS_INBOUND or NP_ACCESS_OUTBOUND access mode cannot use the DosCallNPipe function. If the named pipe's client uses the DosCallNPipe function, the function returns error code 5 (ERROR_ACCESS_DENIED). This function is equivalent to calling DosOpen, DosTransactNPipe, and DosClose If no instances of the pipe are available, DosCallNPipe waits for an instance or returns without opening the pipe if the specified interval of time elapses.
A process can retrieve information about the handle state of a named pipe by using DosQueryNPHState. The handle state is a combination of the instance count, the access mode, and the pipe type (byte or message). DosQueryNPHState also specifies whether the process owning the handle is a server or client and whether the pipe waits if reading and writing cannot proceed.
A process can modify the handle state of a named pipe by using DosSetNPHState. For example, the process can change the reading mode for the pipe, enabling a process to read bytes from the pipe instead of messages.
Steps in Managing Server-Client Transactions
The following sequence summarizes the typical steps in the management of a named pipe:
1.The server process creates the pipe by calling DosCreateNPipe. DosCreateNPipe returns a handle for the server end of the pipe. (Note that the server uses the same handle to both read from and write to the pipe.) The pipe is now in the disconnected state and cannot be opened by a client process. The server process calls DosConnectNPipe to put the pipe into a listening state.
2.The pipe can now be opened by a client process.
3.A client process supplies the name of the pipe in a call to DosOpen and receives a pipe handle in return. (The client uses the same handle to both read from and write to the pipe.) The pipe is now in the connected state and can be read from or written to by the client.
4.The server and client processes communicate by calling DosRead and DosWrite. DosResetBuffer can be used to synchronize read and write dialogs. A server process that supports a large number of clients for a local named pipe can use DosSetNPipeSem and DosQueryNPipeSemState to coordinate access to the pipe. Server and client processes can also use DosTransactNPipe and DosCallNPipe to facilitate their communication.
5.After completing its transactions, the client process calls DosClose to close its end of the pipe. The pipe is now in the closing state and cannot be accessed by another client.
6.The server process calls DosDisConnectNPipe to acknowledge that the client has closed its end of the pipe. The pipe is now in the disconnected state again.
7.To enable another client process to open the pipe, the server must call DosConnectNPipe again. This puts the pipe back into the listening state. To end its access to the pipe, the server calls DosClose. When all of the handles for both ends of the pipe have been closed, the pipe is deallocated by the system.
Preparing a Named Pipe for a Client
A server process uses DosConnectNPipe to put a newly created named pipe into the listening state. The pipe must be in the listening state in order for a client process to gain access to the pipe by calling DosOpen.
After successfully opening the pipe and finishing its transactions, the client process calls DosClose to end its access to the pipe. The server process must acknowledge the close by calling DosDisConnectNPipe. It can then call DosConnectNPipe again to put the pipe into the listening state for the next client.
Together, DosConnectNPipe and DosDisConnectNPipe enable a server to create a named pipe and to reuse it for communication with different clients. Without these functions, the server would have to delete and re-create the pipe for each client.
- Note
- If multiple instances of a named pipe have been created, then each instance of the pipe must be put into the listening state before it can be opened by a client.
Facilitating Transaction Processing
DosTransactNPipe and DosCallNPipe facilitate the use of named pipes by combining other named pipe functions. Compared to calling the other functions separately, DosTransactNPipe and DosCallNPipe provide significant performance gains for applications that operate in a networked environment. They can also be used by local processes. However, both of these functions can be used only with duplex message pipes.
- DosTransactNPipe performs a transaction (a DosWrite followed by DosRead) on a duplex message pipe.
- DosCallNPipe has the combined effect of DosOpen, DosTransactNPipe, and DosClose, and is referred to as a procedure call. It provides an efficient means of implementing local and remote procedure call interfaces between processes.
Coordinating Access to a Local Named Pipe with Semaphores
When a process writes to a named pipe, the process at the other end (or handle) of the pipe might require notification that data is available to be read. Similarly, when a process reads from a named pipe, the process at the other end might require notification that write space has become available. As long as the communicating processes are on the same computer system, shared event semaphores and muxwait semaphores can be used to provide this notification. Using shared semaphores for this purpose is more efficient than dedicating a thread to periodically poll each pipe, particularly when a server process is communicating with a large number of client processes. Whenever data is available in the pipe, the system posts a semaphore to the server or client (whichever is reading from the pipe). This means that the reading process can use DosWaitEventSem or DosWaitMuxWaitSem to wait for data to arrive, rather than devote a thread to periodically polling the pipe.
A process associates a semaphore with a named pipe by using DosSetNPipeSem. First, create an event semaphore with DosCreateEventSem, specifying the initial state of the semaphore as reset. Then call DosSetNPipeSem to attach the event semaphore to a particular named-pipe handle. Up to two event semaphores can be attached to each named pipe, one for the server process and one for the client process. If there is already a semaphore associated with one end of the pipe, that semaphore is replaced. A process can check the state of the semaphores by using DosQueryNPipeSemState.
The server or client process must then call DosWaitEventSem. The particular thread that calls this function will block until data is either read from or written to the pipe. At that time, the system posts the event semaphore, enabling the blocked thread to resume its execution.
If a process requires notification whenever any one of multiple named pipes has been written to or read from, it can either attach the same event semaphore to multiple pipes, or it can create a muxwait semaphore:
- If the same event semaphore is attached to multiple pipes, then the KeyHandle parameter of DosSetNPipeSem is used to assign a unique value to each pipe. After the event semaphore has been posted, the process calls DosQueryNPipeSemState. This function returns information about each of the named pipes that are attached to the semaphore, including key-handle values. The calling process can use this information to determine which one of the named pipes has either data or write space available.
- To use a muxwait semaphore, a process first creates an event semaphore for each of the pipes that it wants to monitor. Each semaphore must then be attached to a pipe by calling DosSetNPipeSem. Again, a unique key-handle value must be assigned to each pipe.
Next, the process calls DosCreateMuxWaitSem to create the muxwait semaphore, specifying DCMW_WAIT_ANY as one of the flAttr flags. The muxwait semaphore will consist of a linked list of the previously created event semaphores.
The process calls DosWaitMuxWaitSem so that it will be notified the next time data is read from or written to any of the pipes. However, it must call DosQueryNPipeSemState to determine which one of the pipes is ready to be read from or written to.
Using Unnamed Pipes
Unnamed pipes are useful in applications that transfer data between related processes. They are commonly used to control the input and output of child processes by redirecting the standard input and output of the child process to a pipe controlled by the parent process.
- Note
- In the example code fragments that follow, error checking was left out to conserve space. Applications should always check the return code that the functions return. Control Program functions return an APIRET value. A return code of 0 indicates success. If a non-zero value is returned, an error occurred.
Creating Unnamed Pipes
DosCreatePipe creates an unnamed pipe. Two handles are returned: one for read access to the pipe and one for write access. The pipe size specified is advisory; its actual size is dependent on the amount of available memory. If the size parameter is 0, the pipe is created with the default size, which is 512 bytes.
This example creates an unnamed pipe. The current process can use the unnamed pipe for communication between itself and a child process.
#define INCL_BASE /* Queue values */ #include <os2.h> #include <stdio.h> HFILE hfReadHandle; /* Pointer to the read handle */ HFILE hfWriteHandle; /* Pointer to the write handle */ ULONG ulPipeSize; /* Pipe size */ APIRET ulrc; /* Return code */ ulPipeSize = 4096; /* Ask for 4KB of internal storage */ /* for the pipe */ ulrc = DosCreatePipe(&hfReadHandle, &hfWriteHandle, ulPipeSize); if (ulrc != 0) { printf("DosCreatePipe error: return code = %ld", ulrc); return; }
On successful return, the ReadHandle variable contains the read handle for the pipe, and the WriteHandle variable contains the write handle for the pipe.
After a process creates a pipe, any child process started with DosExecPgm inherits the pipe handles. Using shared memory, the parent process can pass one of the pipe handles to the child process; thus, one process can store data in the pipe and the other can retrieve it.
Reading from and Writing to Unnamed Pipes
Applications use the OS/2 file system functions to read from and write to unnamed pipes.
The handles returned by DosCreatePipe are used as file handles to DosRead and DosWrite.
To write (or add data) to an unnamed pipe, call DosWrite, specifying the write handle of the pipe in DosWrite's file handle parameter. DosWrite requests to a pipe are processed in the order in which they are made. Multiple calls to DosWrite can be made before data is read (or removed) from the pipe. When the pipe becomes full, write requests are blocked until space is freed by read requests.
When the pipe is too full to hold the entire contents of the write request, the portion that will fit is written to the pipe before the writer is blocked.
To read from a pipe, call DosRead, specifying the read handle of the pipe in DosRead's file handle parameter. Subsequent calls to DosRead can empty the pipe if no further calls to DosWrite are made in the meantime.
Different threads writing to and reading from a pipe are not synchronized. It is possible for the pipe reader to obtain partial contents of a write request if the pipe becomes full during the write request.
If the process reading the pipe ends, the next DosWrite request for that pipe returns ERROR_BROKEN_PIPE.
Calling DosClose terminates access to an unnamed pipe. However, the pipe is not deleted from memory until all handles to the pipe have been closed, including any handles that were defined with DosDupHandle.
Redirecting Standard I/O for Child Processes
An application can use unnamed pipes to redirect the standard input and the standard output for a child process.
A typical use of an unnamed pipe is to read the output of a child process. An application creates a pipe and then duplicates the standard output handle. When the child process is started, its standard output will be written into the pipe, where the application can read and display it. The following code fragment shows how to do this:
#define INCL_DOSPROCESS #define INCL_DOSNMPIPES #include <os2.h> #define PIPESIZE 256 #define HF_STDOUT 1 /* Standard output handle */ HPIPE hpR, hpW; RESULTCODES resc; ULONG ulRead, ulWritten; CHAR achBuf[PIPESIZE], szFailName[CCHMAXPATH]; HFILE hfSave = -1, hfNew = HF_STDOUT; DosDupHandle(HF_STDOUT, &hfSave); /* Saves standard output handle */ DosCreatePipe(&hpR, &hpW, PIPESIZE); /* Creates pipe */ DosDupHandle(hpW, &hfNew); /* Duplicates standard output handle */ DosExecPgm(szFailName, sizeof(szFailName), /* Starts child process */ EXEC_ASYNC, (PSZ) NULL, (PSZ) NULL, &resc, "DUMMY.EXE"); DosClose(hpW); /* Closes write handle to ensure */ /* Notification at child termination */ DosDupHandle(hfSave, &hfNew); /* Brings stdout back */ /* * Read from the pipe and write to the screen * as long as there are bytes to read. * */ do { DosRead(hpR, achBuf, sizeof(achBuf), &ulRead); DosWrite(HF_STDOUT, achBuf, ulRead, &ulWritten); } while(ulRead);
A parent process can also use unnamed pipes to communicate with a child process by redirecting both the standard input and the standard output for the child process. To do this, the parent process:
1.Uses DosDupHandle to redefine the read handle of one pipe as standard input (0000), and the write handle of the other pipe as standard output (0001). 2.Starts the child process with DosExecPgm. 3.Uses the remaining pipe handles to read and write to the pipes.
The parent process controls the meanings for standard I/O for the child process. Thus, when the child process uses standard I/O handles with DosRead and DosWrite, it reads from and writes to the pipes of its parent instead of reading from the keyboard and writing to the display.
Using Named Pipes
Named pipes are useful in applications that transfer data between processes. The processes using named pipes can be related, unrelated, or even on different computers.
Creating Named Pipes
The server process creates a named pipe by using DosCreateNPipe.
To create a named pipe, specify on the DosCreateNPipe function:
- The name of the pipe
- The access modes
- The type of pipe (byte or message)
- The sizes of the input and output buffers
DosCreateNPipe returns a pipe handle that can be used in subsequent pipe operations.
Each named pipe must have a unique name of the following form:
\PIPE\PipeName
The "\PIPE\" in the name above is required, but need not be uppercase. It is not the name of a subdirectory.
To open a pipe on a remote computer, the client process must specify the name of the server process that opened the pipe as part of the pipe name, as follows:
\\Server\PIPE\PipeName
"\\Server" in the name above is the name of the remote computer; again, "\PIPE\" is required.
The name parameter must conform to the rules for OS/2 file names, but no actual file is created for the pipe.
Named pipes created with certain access modes prevent the named pipes' clients from using certain functions. If the named pipe is created with access mode NP_ACCESS_INBOUND, the named pipe's client cannot use these functions:
- DosCallNPipe
- DosTransactNPipe
- DosPeekNPipe
If the named pipe is created with access mode NP_ACCESS_OUTBOUND, the named pipe's client cannot use these functions:
- DosCallNPipe
- DosTransactNPipe
- DosWrite
If the named pipe's client uses an incorrect function, the function returns error code 5 (ERROR_ACCESS_DENIED).
In the following code fragment, DosCreateNPipe creates a pipe named \pipe\pipe1 and supplies a unique handle identifying the pipe. OpenMode is set to NP_ACCESS_DUPLEX. This activates full duplex access to the named pipe. There will be no inheritance to child process, and no write-through (write-through only affects remote pipes). PipeMode is set to "NP_WMESG | NP_RMESG | 0x01". This specifies that the pipe should be read as a message stream for both reading and writing and an instance count of 1 (only one instance of the named pipe can be created at a time). The pipe will block on Read/Write if no data is available.
#define INCL_DOSNMPIPES /* Named-pipe values */ #include <os2.h> #include <stdio.h> UCHAR ucFileName[40]; /* Pipe name */ HPIPE hpPipeHandle; /* Pipe handle (returned) */ ULONG ulOpenMode; /* Open-mode parameters */ ULONG ulPipeMode; /* Pipe-mode parameters */ ULONG ulOutBufSize; /* Size of the out-buffer */ ULONG ulInBufSize; /* Size of the in-buffer */ ULONG ulTimeOut; /* Default value for */ /* DosWaitNPipe time-out */ /* parameter */ APIRET ulrc; /* Return code */ strcpy(ucFileName, "\\PIPE\\PIPE1"); ulOpenMode = NP_ACCESS_DUPLEX; /* Full duplex, no inheritance, */ /* no write-through */ ulPipeMode = NP_WMESG | NP_RMESG | 0x01; /* Block on read and write, message */ /* stream, instance count of 1 */ ulOutBufSize = 4096; /* The outgoing buffer must be 4KB in size */ ulInBufSize = 2048; /* The incoming buffer must be 2KB in size */ ulTimeOut = 10000; /* Time-out is 10 seconds (units are in milliseconds) */ ulrc = DosCreateNPipe(ucFileName, &hpPipeHandle, ulOpenMode, ulPipeMode, ulOutBufSize, ulInBufSize, ulTimeOut); if (ulrc != 0) { printf("DosCreateNPipe error: return code = %ld", ulrc); return; }
Once the named pipe is created, the application can call DosConnectNPipe to connect a client process to the pipe.
Once a client process connects to the pipe, the process can read from and write to the pipe. The preceding example creates a byte pipe, so the process can use DosRead and DosWrite to read from and write to the pipe.
After the client process finishes using the pipe, the server process can disconnect the pipe by using DosDisConnectNPipe. The server process can either connect again or close the named pipe by using DosClose.
When a server process creates a named pipe, it defines the pipe to the system by specifying the file write-through mode, the inheritance mode, the access and blocking modes, the pipe type, the read mode, the size of the in and out buffers, and the instance count. The following list describes these modes, types, and buffers.
- The file write-through mode has significance only for communication with remote client processes. When the file write-through bit is set, data is sent across the network as soon as it is written; otherwise, OS/2 will in some cases hold data briefly in a local buffer before sending it across the network.
- The inheritance mode specifies whether or not the pipe handle will be inherited by a child process.
- The access mode specifies the direction in which data will flow through the pipe. The server creates an inbound pipe (a pipe with inbound access mode) if it intends to read data from the client process, an outbound pipe if it intends to write data to the client process, or a duplex pipe if it intends to both read from and write to the client process.
- The blocking mode specifies whether or not DosRead and DosWrite will block when no data is available to read, or there is no room to write data.
- The pipe type is the form in which a stream of data is written to the pipe. If the pipe is a byte pipe, the server and client processes write data as an undifferentiated stream of bytes. If the pipe is a message pipe, the processes write data as a stream of messages; messages are blocks of data, each with a system-supplied header, that are written as single units. The server and client processes define the size and format of a message.
- The read mode is the form in which data is read from the pipe. The data in a pipe that was created as a byte pipe can only be read as bytes; therefore, a byte pipe will always be in byte-read mode. The data in a message pipe, however, can be read either as messages or as bytes. (If it is to be read as bytes, DosRead skips over the message headers). Therefore, message pipes can be in either message-read mode or byte-read mode.
- Note
- The terms "byte pipe" and "message pipe" always refer to the pipe type-the form in which data is written to the pipe. When the read mode of a pipe is being referred to, it is always explicitly identified as either message-read mode or byte-read mode.
- The in and out buffers can be up to 64KB in size. If the pipe will be read in message-read mode, and if the message size is known, the server can control how many messages the buffer will hold at one time by specifying the appropriate buffer size.
- The instance count is the maximum number of instances of the named pipe that can be created. A pipe instance is actually a separate pipe-that is, a unique set of pipe buffers with unique handles. However, the term "pipe instance" is used to distinguish pipes that share the same name from pipes with different names. Because a client process uses only the name of the pipe when opening it, the existence of multiple pipe instances is transparent to a client process.
- Creating Multiple Instances of a Named Pipe
Although each named pipe must have a unique name, a server process can create multiple instances of a pipe, all of which have the same name. A pipe instance is actually a separate pipe-that is, a unique set of pipe buffers with unique handles.
The ability to create multiple pipe instances enables the server to communicate with multiple client processes at the same time. Because a client process uses only the name of the pipe when opening it, the existence of multiple pipe instances is transparent to a client process.
The ICount parameter of DosCreateNPipe specifies the maximum number of named pipe instances that can be created. (An unlimited number can also be specified.) This parameter is specified only when the first instance of a named pipe is created; any subsequent attempt to redefine the instance count will be ignored.
If the instance count is greater than 1, the server process can create additional pipe instances by specifying the same pipe name in subsequent calls to DosCreateNPipe. Generally, the attributes of the subsequent pipe instances are defined to be the same as those of the original pipe instance, because a client process that requests the pipe has no way of controlling which pipe instance will be assigned to it.
After an additional pipe instance has been created, it is used in the same manner as the original pipe instance. That is, the same sequence of named pipe functions is used in the control or management of all named pipe instances. (See Steps in Managing Server-Client Transactions for more information.)
- Note
- If all of the instances of a named pipe are in use when a client calls DosOpen, ERROR_PIPE_BUSY is returned. However, the client can wait for an instance of that pipe to become available by calling DosWaitNPipe.
Multiple instances of a named pipe can be created by different processes. That is, multiple server processes can create and use instances of the same named pipe to communicate with their clients.
Opening Named Pipes
A client process can open the client end of a named pipe by using DosOpen. DosOpen opens the client end of a pipe by name and returns a handle. The application must use the appropriate pipe name and access modes to open the pipe for reading, writing, or both. (To open a pipe on a remote computer, the client process must also specify the name of the computer system as part of the pipe name, as follows:
\\ComputerName\PIPE\PipeName.)
If a pipe name includes a remote LAN server name, DosOpen attempts to open the pipe on a remote computer. The server process can then read input from the client process through the pipe.
The following code fragment opens a remote pipe, reads from the standard input (usually the keyboard), and sends the information to the server process through the pipe:
#define INCL_DOSQUEUES /* Queue values */ #include <os2.h> #define PIPESIZE 256 #define SERVER_PIPE_NAME "\\\\myserver\\pipe\\mypipe" #define HF_STDIN 0 /* Standard input handle */ HPIPE hp; BYTE abBuf[PIPESIZE]; ULONG ulAction, ulRead, ulWritten; APIRET ulrc; ulrc = DosOpen(SERVER_PIPE_NAME, &hp, &ulAction, 0, FILE_NORMAL, FILE_OPEN, OPEN_ACCESS_READWRITE | OPEN_SHARE_DENYNONE, (PEAOP) NULL); if (ulrc) DosExit(EXIT_PROCESS, 0); /* Open pipe failed */ do { /* Open pipe succeeded */ DosRead(HF_STDIN, abBuf, sizeof(abBuf), &ulRead); DosWrite(hp, abBuf, ulRead, &ulWritten); /* Writes to the pipe */ } while (ulRead > 2); /* Stop on a blank line */ DosClose(hp);
The client process checks the return value from DosOpen to verify that the pipe was actually opened. If the server process has not yet created the pipe, DosOpen returns an error. When the client process finishes using the pipe, it closes the pipe by using DosClose.
When a named pipe is opened, its initial state is set by the system to block read and write operations (blocking mode), and to read data as a byte stream (byte-read mode). However, the client can change these modes by calling DosSetNPHState. A call to DosOpen fails if all instances of the named pipe are already open.
The open also fails if the pipe has been closed by a client, but the server has not called DosDisConnectNPipe (to acknowledge the client's close), followed by DosConnectNPipe (to prepare the pipe for the next client). In both of these situations, ERROR_PIPE_BUSY is returned.
If all instances of a named pipe are busy, a client process can call DosWaitNPipe to wait for an instance to become available before it calls DosOpen again.
After a pipe instance has been opened by a client, that same instance cannot be opened by another client at the same time. However, the opening process can duplicate the handle as many times as desired by calling DosDupHandle. This enables child processes to share access to a pipe instance with a parent process.
The access-mode and sharing-mode fields that are specified for DosOpen must be the same as those that were specified by the server when it created the pipe with DosCreateNPipe.
Reading from Named Pipes
Both the server and the client processes read data from a pipe by calling DosRead. The server reads from the handle that was returned when it created the pipe with DosCreateNPipe, and the client reads from the handle that was returned to it by DosOpen.
When a pipe is created, the PipeMode parameter is used to specify both the pipe type and the read mode for the server end of the pipe:
- A byte pipe can be read-only in byte-read mode. (DosCreateNPipe and DosSetNPHState return ERROR_INVALID_PARAMETER if message-read mode is specified for a byte pipe.) In byte-read mode, all currently available data is returned, up to the buffer size specified by DosRead.
- A message pipe can be read in either byte-read mode or message-read mode, as follows:
- When a message pipe is read in byte-read mode, the message headers are skipped, and the pipe is read as if it were a byte pipe.
- When a message pipe is read in message-read mode, each message is read either in its entirety, or not at all, depending on the size of the message and the buffer length:
- If the buffer length that was specified for DosRead is larger than the next available message, then only that message is read, and the Bytes-Read parameter indicates the size of the message.
- If the buffer length for DosRead is smaller than the next available message, DosRead returns the number of bytes requested and ERROR_MORE_DATA. Subsequent calls to DosRead are blocked until the rest of the message can be transferred. (DosPeekNPipe can be used to find out how many bytes are left in the message.)
The PipeMode parameter of DosCreateNPipe also specifies the blocking mode for the server end of the pipe:
- If nonblocking mode was specified, DosRead returns immediately with 0 in the Bytes-Read parameter if no data is available.
- If blocking mode was specified, DosRead blocks until data is available; the only time it will return with 0 in the Bytes-Read parameter is if it reaches end-of-file.
DosRead works the same for both ends of the pipe. However, the read mode and blocking mode are not necessarily the same for the client end of the pipe as they are for the server end, because DosOpen always opens the CLIENT end in byte-read mode and blocking mode.
The read mode and blocking mode for either end of the pipe can be changed by calling DosSetNPHState. The pipe type, however, is always the same for both the server and client ends of the pipe, and it cannot be changed.
Writing to Named Pipes
Both the server and the client processes write to a pipe by calling DosWrite. The server writes to the handle that was returned to it by DosCreateNPipe, and the client writes to the handle that was returned to it by DosOpen.
Either bytes or messages can be written, depending on whether the pipe was created as a byte pipe or as a message pipe.
Named pipes created with the NP_ACCESS_OUTBOUND access mode cannot use the DosWrite function. If the named pipe's client uses the DosWrite function, the function returns error code 5 (ERROR_ACCESS_DENIED).
An attempt to write to a pipe whose other end has been closed returns ERROR_BROKEN_PIPE or, if the other end was closed without without reading all pending data, ERROR_DISCARDED.
When a process writes to a message pipe, the buffer-length parameter for DosWrite holds the size of the message that the process is writing. Because DosWrite automatically encodes message lengths in the pipe, applications do not have to encode this information in the data buffers.
The action taken by DosWrite depends on the blocking mode of the pipe, which is not necessarily the same for the server and client ends of the pipe. For the server process, the blocking mode of the pipe is specified when the pipe is created. For a client process, the blocking mode is automatically set to blocking when the pipe is opened. The blocking mode can also be reset by calling DosSetNPHState.
If the end of the message pipe that is being written to is in blocking mode, DosWrite does not return until all of the requested bytes have been written. (It might have to wait for the first part of the message to be read before it can write the rest of the message.)
If the message pipe is in nonblocking mode, DosWrite takes the following action:
- If the message is larger than the pipe buffer, DosWrite blocks until the entire message has been written. (Again, it might have to wait for the first part of the message to be read before it can write the rest of the message.)
- If the message is smaller than the pipe buffer, but there is currently not enough room in the buffer, DosWrite returns with a value of 0 in the Bytes-Written parameter.
If a byte pipe is in nonblocking mode, and if there is more data to be written than will fit in the pipe buffer, then DosWrite writes as many bytes as will fit in the buffer and returns the number of bytes that were actually written.
If a process tries to write to a pipe whose other end is closed, ERROR_BROKEN_PIPE is returned.
Synchronizing Named Pipe Dialogs
Communicating processes can synchronize their named pipe dialogs by calling DosResetBuffer after each call to DosWrite.
When used with external files, DosResetBuffer flushes the buffer cache of the requesting process to disk. When used with named pipes, this function blocks the calling process at one end of the pipe until the data the calling process has written to the pipe has been successfully read at the other end of the pipe.
Determining Pipe Status
DosQueryNPHState and DosQueryNPipeInfo can be used to obtain information about named pipes.
- DosQueryNPHState
A client process can read data from the pipe, write data to the pipe, or both, depending on the access mode specified when the pipe was created. To check the current access mode, the client process can call DosQueryNPHState.
The following code fragment shows how to use DosQueryNPHState to obtain information about a named pipe:
#define INCL_DOSNMPIPES /* Named-pipe values */ #include <os2.h> #include <stdio.h> HPIPE hpHandle; /* Pipe handle */ ULONG ulPipeHandleState; /* Pipe-handle state */ APIRET ulrc; /* Return code */ ulrc = DosQueryNPHState(hpHandle, &ulPipeHandleState); if (ulrc != 0) { printf("DosQueryNPHState error: return code = %ld", ulrc); return; }
On successful return, PipeHandleState will contain information that describes the nature of the named pipe.
DosQueryNPHState returns the following information about the pipe handle and the attributes of the pipe:
- The end of the pipe that the handle is for (server or client end)
- The pipe type (byte pipe or message pipe)
- The instance count
- The blocking mode (blocking or nonblocking)
- The read mode (byte-read mode or message-read mode)
The values for pipe type and instance count cannot be changed, so they are always the same as those that were specified when the pipe was created with DosCreateNPipe. The information returned for blocking mode and read mode, however, can come from different sources:
- If the handle is for the server end of the pipe, then the blocking mode and the read mode were set with DosCreateNPipe, but could have been reset with DosSetNPHState.
- If the handle is for the client end of the pipe, then the blocking mode and the read mode were set to "blocking" and "byte-read" by the system when the client called DosOpen. However, again, they could have been reset with DosSetNPHState.
The pipe attributes are described in more detail in Creating Named Pipes.
An application can use DosSetNPHState to change the wait mode and the read mode. The pipe cannot be changed to no-wait mode when another thread is blocked on a read or write operation to the same end of the pipe.
- DosQueryNPipeInfo
More detailed information about a named pipe can be obtained by using DosQueryNPipeInfo. This function returns information in a PIPEINFO data structure that includes the name of the pipe, the current and maximum instance counts (the current number of pipes and the maximum number of times the pipe can be created), the size of the input and output buffers for the pipe, and the pipe identifier of the client process.
The following code fragment shows how to use DosQueryNPipeInfo:
#define INCL_DOSNMPIPES /* Named-pipe values */ #include <os2.h> #include <stdio.h> HPIPE hpHandle; /* Pipe handle */ ULONG ulInfoLevel; /* Pipe data required */ PIPEINFO pipInfoBuf; /* Pipe information data structure */ ULONG ulInfoBufSize; /* Pipe data-buffer size */ APIRET ulrc; /* Return code */ ulInfoLevel = 1; /* Ask for standard level of pipe info */ ulInfoBufSize = sizeof(PIPEINFO); /* Length of pipe info data structure */ ulrc = DosQueryNPipeInfo(hpHandle, ulInfoLevel, &pipInfoBuf, ulInfoBufSize); if (ulrc != 0) { printf("DosQueryNPipeInfo error: return code = %ld", ulrc); return; }
On successful return, the pipe information data structure contains a set of information describing the nature and the current state of the named pipe.
DosQueryNPipeInfo returns level 1 or level 2 file information for the pipe. Level 1 information includes the following:
- The actual sizes of the in-buffer and out-buffer
- The maximum number of pipe instances permitted
- The current number of pipe instances
- The length of the pipe name
- The ASCIIZ name of the pipe, including \\ComputerName if the pipe is in a remote computer system.
Level 2 information consists of a unique 2-byte identifier for each of the pipe's client processes.
Examining the Contents of Named Pipes
DosPeekNPipe examines the current contents of a named pipe.
Named pipes created with the NP_ACCESS_INBOUND access mode cannot use the DosPeekNPipe function. If the named pipe's client uses the DosPeekNPipe function, the function returns error code 5 (ERROR_ACCESS_DENIED).
It is similar to DosRead, except that DosPeekNPipe does not remove data from the pipe. In addition, DosPeekNPipe never blocks, even if the pipe is in blocking mode; if the pipe cannot be accessed immediately, ERROR_PIPE_BUSY is returned.
Because DosPeekNPipe does not block, it returns only what is currently in the pipe. Thus, if a message pipe is being examined, only a portion of a message might be returned, even though the specified buffer length could accommodate the entire message.
DosPeekNPipe also returns the state of the pipe. A named pipe can be in any of the following states: Connected, Disconnected, Listening, Closing.
The following code fragment shows how to use DosPeekNPipe:
#define INCL_DOSNMPIPES /* Named-pipe values */ #include <os2.h> #include <stdio.h> HPIPE hpHandle; /* Pipe handle */ UCHAR ucBuffer[200]; /* Address of user buffer */ ULONG ulBufferLen; /* Buffer length */ ULONG ulBytesRead; /* Bytes read (returned) */ struct _AVAILDATA BytesAvail; /* Bytes available (returned) */ ULONG ulPipeState; /* Pipe state (returned) */ APIRET ulrc; /* Return code */ ulBufferLen = 200; /* Length of the read buffer */ ulrc = DosPeekNPipe(hpHandle, ucBuffer, ulBufferLen, &ulBytesRead, &BytesAvail, &ulPipeState); if (ulrc != 0) { printf("DosPeekNPipe error: return code = %ld", ulrc); return; }
On successful return, the input buffer Buffer will contain up to the first 200 bytes from the named pipe, BytesRead will contain the number of bytes read into Buffer, BytesAvail will contain the total number of bytes that were available in the pipe, and PipeState will contain a value indicating the state of the named pipe.
Closing Named Pipes
DosClose closes the specified pipe handle. When all of the handles that access one end of a pipe have been closed, the pipe is referred to as a broken pipe.
If the client end of the pipe closes, no other process can reopen the pipe until the server calls DosDisConnectNPipe (to acknowledge the client's close) followed by DosConnectNPipe (to prepare the pipe for a new client). Until it calls DosDisConnectNPipe, the server will receive ERROR_EOF if it tries to read from the pipe, and ERROR_BROKEN_PIPE if it tries to write to it. Clients that attempt to open the pipe receive ERROR_PIPE_BUSY.
If the server end closes when the client end is already closed, the pipe is deallocated immediately; otherwise, the pipe is not deallocated until the last client handle is closed.
The following code fragment shows how to close a named pipe. Assume that a previous call to DosOpen provided the named pipe handle that is contained in Handle.
#define INCL_DOSNMPIPES /* Named-pipe values */ #include <os2.h> #include <stdio.h> HPIPE hpHandle; /* Pipe handle */ APIRET ulrc; /* Return code */ ulrc = DosDisConnectNPipe(hpHandle); if (ulrc != 0) { printf("DosDisConnectNPipe error: return code = %ld", ulrc); return; }