< Chap 15: Index | Winsock 2 Main | Name Pipes DACL, Threads, Overlapped, Client, Server >


 

 

Named Pipes 15 Part 1

 

 

What do we have in this chapter 15 part 1?

  1. Named Pipe Implementation Details

  2. Named Pipe Naming Conventions

  3. Byte Mode and Message Mode

  4. Compiling Applications

  5. Error Codes

  6. Basic Server and Client

  7. Server Details

 

Named pipes are a simple interprocess communication (IPC) mechanism included in Microsoft Windows NT, and Windows 95, Windows 98, and Windows Me platforms (but not Windows CE). Named pipes provide reliable one-way and two-way data communications among processes on the same computer or among processes on different computers across a network. Developing applications using named pipes is actually quite simple and requires no formal knowledge of underlying network transport protocols (such as TCP/IP or IPX). This is because named pipes use the Microsoft Network Provider (MSNP) redirector to form communication among processes over a network, thus hiding network protocol details from the application. One of the best reasons for using named pipes as a networking communication solution is that they take advantage of security features built into the Windows NT platform.

One possible scenario for using named pipes is developing a data management system that allows only a select group of people to perform transactions. Imagine an office setting in which you have a computer that contains company secrets. You need to have these secrets accessed and maintained by management personnel only. Let's say every employee can see the computer on the network from his or her workstation. However, you do not want regular employees to obtain access to the confidential records. Named pipes work well in this situation because you can develop a server application that, based on requests from clients, safely performs transactions on the company secrets. The server can easily limit client access to management personnel by using security features of the Windows NT platform.

What's important to remember when using named pipes as a network programming solution is that they feature a simple client/server data communication architecture that reliably transmits data. This chapter explains how to develop named pipe client and server applications. We start by explaining named pipe naming conventions, followed by basic pipe types. We'll then show how to implement a basic server application, followed by advanced server programming details. Next we discuss how to develop a basic client application. By the chapter's end, we uncover the known problems and limitations of named pipes.

 

Named Pipe Implementation Details

 

Named pipes are designed around the Windows file system using the Named Pipe File System (NPFS) interface. As a result, client and server applications use standard Windows file system API functions such as ReadFile() and WriteFile() to send and receive data. Using these API functions allows applications to take advantage of Windows file system naming conventions and Windows NT file system security. NPFS relies on the MSNP redirector to send and receive named pipe data over a network. This makes the interface protocol-independent: when developing an application that uses named pipes to form communications among processes across a network, so a programmer does not have to worry about the details of underlying network transport protocols, such as TCP and IPX. Named pipes are identified to NPFS using the Universal Naming Convention.

 

Named Pipe Naming Conventions

 

Named pipes are identified using the following UNC format:

\\server\Pipe\[path]name

This string is divided into three parts: \\server, \Pipe, and \[path]name. The first string part, \\server, represents the server name in which a named pipe is created and the server that listens for incoming connections. The second part, \Pipe, is a hard-coded mandatory string requirement for identifying that this filename belongs to NPFS. The third part, \[path]name, allows applications to uniquely define and identify a named pipe name, and it can have multiple levels of directories. For example, the following name types are legal for identifying a named pipe:

\\myserver\PIPE\mypipe

\\Testserver\pipe\cooldirectory\funtest\jim

\\.\Pipe\Easynamedpipe

The server string portion can be represented as a dot (.) or a server name.

 

Byte Mode and Message Mode

 

Named pipes offer two basic communication modes: byte mode and message mode. In byte mode, messages travel as a continuous stream of bytes between the client and the server. This means that a client application and a server application do not know precisely how many bytes are being read from or written to a pipe at any given moment. Therefore a write on one side will not always result in a same-size read on the other. This allows a client and a server to transfer data without regard to the contents of the data. In message mode, the client and the server send and receive data in discrete units. Every time a message is sent on the pipe, it must be read as a complete message. Figure 20-1 compares the two pipe modes.

 

Winsock - Name pipe: The byte mode and message mode

 

Figure 20-1 Byte mode and message mode

 

Compiling Applications

 

When you build a named pipe client or server application using Microsoft Visual C++, your application must include the WINBASE.H file in your program files. If your application includes WINDOWS.H, as most do, you can omit WINBASE.H. Your application is also responsible for linking with KERNEL32.LIB, which typically is configured with the Visual C++ linker flags.

 

Error Codes

 

All Windows API functions (except CreateFile() and CreateNamedPipe()) that are used in developing named pipe client and server applications return the value 0 when they fail. CreateFile() and CreateNamedPipe() return INVALID_HANDLE_VALUE. When either of these functions fails, applications should call the GetLastError() function to retrieve specific information about the failure. For a complete list of error codes, consult the header file WINERROR.H.

 

Basic Server and Client

 

Named pipes feature a simple client/server design architecture in which data can flow in both a unidirectional and a bidirectional manner between a client and server. This is useful because it allows you to send and receive data whether your application is a client or a server. The main difference between a named pipe server and a client application is that a named pipe server is the only process capable of creating a named pipe and accepting pipe client connections. A client application is capable only of connecting to an existing named pipe server. Once a connection is formed between a client application and a server application, both processes are capable of reading and writing data on a pipe using standard Windows functions such as ReadFile() and WriteFile(). Note that a named pipe server application can operate only on the Windows NT platform, Windows 95, Windows 98, and Windows Me systems do not permit applications to create a named pipe. This limitation makes it impossible to form communications directly between two Windows 95, Windows 98, or Windows Me computers. However, Windows 95, Windows 98, and Windows Me clients can form connections to Windows NT–based computers.

 

Server Details

 

Implementing a named pipe server requires developing an application to create one or more named pipe instances, which can be accessed by clients. To a server, a pipe instance is nothing more than a handle used to accept a connection from a local or remote client application. The following steps describe how to write a basic server application:

 

  1. Create a named pipe instance handle using the CreateNamedPipe() API function.
  2. Use the ConnectNamedPipe() API function to listen for a client connection on the named pipe instance.
  3. Receive data from and send data to the client using the ReadFile() and WriteFile() API functions.
  4. Close down the named pipe connection using the DisconnectNamed() Pipe API function.
  5. Close the named pipe instance handle using the CloseHandle() API function.

 

First, your server process needs to create a named pipe instance using the CreateNamedPipe() API call, which is defined as follows:

 

HANDLE CreateNamedPipe(

    LPCTSTR lpName,

    DWORD dwOpenMode,

    DWORD dwPipeMode,

    DWORD nMaxInstances,

    DWORD nOutBufferSize,

    DWORD nInBufferSize,

    DWORD nDefaultTimeOut,

    LPSECURITY_ATTRIBUTES lpSecurityAttributes

);

 

The first parameter, lpName, specifies the name of a named pipe. The name must have the following UNC form:

\\.\Pipe\[path]name

Notice that the server name is represented as a dot, which represents the local machine. You cannot create a named pipe on a remote computer. The [path]name part of the parameter must represent a unique name. This might simply be a filename, or it might be a full directory path followed by a filename.

The dwOpenMode parameter describes the directional, I/O control, and security modes of a pipe when it is created. Table 20-1 describes all the available flags that can be used. A pipe can be created using a combination of these flags by ORing them together.

 

Table 20-1 Named Pipe Open Mode Flags

 

Open Mode

Flags

Description

Directional

PIPE_ACCESS_DUPLEX

The pipe is bidirectional: Both the server and client processes can read from and write data to the pipe.

 

PIPE_ACCESS_OUTBOUND

The flow of data in the pipe goes from server to client only.

 

PIPE_ACCESS_INBOUND

The flow of data in the pipe goes from client to server only.

I/O Control

FILE_FLAG_WRITE_THROUGH

Works only for byte-mode pipes. Functions writing to a named pipe do not return until the data written is transmitted across the network and is in the pipe's buffer on the remote computer.

I/O control

FILE_FLAG_OVERLAPPED

Allows functions that perform read, write, and connect operations to use overlapped I/O.

Security

WRITE_DAC

Allows your application to have write access to the named pipe's DACL.

Security

ACCESS_SYSTEM_SECURITY

Allows your application to have write access to the named pipe's SACL.

 

WRITE_OWNER

Allows your application to have write access to the named pipe's owner and group SID.

 

The PIPE_ACCESS_ flags determine flow direction on a pipe between a client and a server. A pipe can be opened as bidirectional (two-way) using the PIPE_ACCESS_DUPLEX flag: Data can flow in both directions between the client and the server. In addition, you can also control the direction of data flow by opening the pipe as unidirectional (one-way) using the flag PIPE_ACCESS_INBOUND or PIPE_ACCESS_OUTBOUND: data can flow only one way from the client to the server or vice versa. Figure 20-2 describes the flag combinations further and shows the flow of data between a client and a server.

 

-------------------------------------------------------------------

Winsock - Name pipe: Mode flags and flow direction

 

Figure 20-2 Mode flags and flow direction

 

The next set of dwOpenMode flags controls I/O behavior on a named pipe from the server's perspective. The FILE_FLAG_WRITE_THROUGH flag controls the write operations so that functions writing to a named pipe do not return until the data written is transmitted across the network and is in the pipe's buffer on the remote computer. This flag works only for byte-mode named pipes when the client and the server are on different computers. The FILE_FLAG_OVERLAPPED flag allows functions performing read, write, and connect operations to return immediately, even if those functions take significant time to complete. We discuss the details of overlapped I/O when we develop an advanced server later in this chapter.

The last set of dwOpenMode flags described in Table 20-1 controls the server's ability to access the security descriptor that is created by a named pipe. If your application needs to modify or update the pipe's security descriptor after the pipe is created, you should set these flags accordingly to permit access. The WRITE_DAC flag allows your application to update the pipe's DACL, whereas ACCESS_SYSTEM_SECURITY allows access to the pipe's SACL. The WRITE_OWNER flag allows you to change the pipe's owner and group SID. For example, if you want to deny access to a particular user who has access rights to your pipe, you can modify the pipe's DACL using security API functions.

CreateNamedPipe's dwPipeMode parameter specifies the read, write, and wait operating modes of a pipe. Table 20-2 describes all the available mode flags that can be used. The flags can be issued by ORing one flag from each mode category. If a pipe is opened as byte-oriented using the PIPE_READMODE_BYTE | PIPE_TYPE_BYTE mode flags, data can be read and written only as a stream of bytes. This means that when you read and write data to a pipe, you do not have to balance each read and write because your data does not have any message boundaries. For example, if a sender writes 500 bytes to a pipe, a receiver might want to read 100 bytes at a time until it receives all of the data. To establish clear boundaries around messages, place the pipe in message-oriented mode using the flags PIPE_READMODE_MESSAGE | PIPE_TYPE_MESSAGE, meaning each read and write must be balanced. For example, if a sender writes a 500-byte message to a pipe, the receiver must provide the ReadFile() function a 500-byte or larger buffer when reading data. If the receiver fails to do so, ReadFile() will fail with error ERROR_MORE_DATA. You can also combine PIPE_TYPE_MESSAGE with PIPE_READMODE_BYTE, allowing a sender to write messages to a pipe and the receiver to read an arbitrary amount of bytes at a time. The message delimiters will be ignored in the data stream. You cannot mix the PIPE_TYPE_BYTE flag with the PIPE_READMODE_MESSAGE flag. Doing so will cause the CreateNamedPipe() function to fail with the error ERROR_INVALID_PARAMETER because no message delimiters are in the I/O stream when data is written into the pipe as bytes. The PIPE_WAIT or PIPE_NOWAIT flag can also be combined with read and write mode flags. The PIPE_WAIT flag places a pipe in blocking mode and the PIPE_NOWAIT flag places a pipe in nonblocking mode. In blocking mode, I/O operations such as ReadFile() block until the I/O request is complete. This is the default behavior if you do not specify any flags. The nonblocking mode flag PIPE_NOWAIT is designed to allow I/O operations to return immediately. However, it should not be used to achieve asynchronous I/O in Windows applications. It is included to provide backward compatibility with older Microsoft LAN Manager 2.0 applications. The ReadFile() and WriteFile() functions allow applications to accomplish asynchronous I/O using Windows overlapped I/O, which is demonstrated later in this chapter.

 

Table 20-2 Named Pipe Read/Write Mode Flags

 

Mode

Flags

Description

Write

PIPE_TYPE_BYTE

Data is written to the pipe as a stream of bytes.

 

PIPE_TYPE_MESSAGE

Data is written to the pipe as a stream of messages.

Read

PIPE_READMODE_BYTE

Data is read from the pipe as a stream of bytes.

 

PIPE_READMODE_MESSAGE

Data is read from the pipe as a stream of messages.

Wait

PIPE_WAIT

Blocking mode is enabled.

 

PIPE_NOWAIT

Nonblocking mode is enabled.

 

The PIPE_NOWAIT flag is obsolete and should not be used in Windows environments to accomplish asynchronous I/O. It is included in this book to provide backward compatibility with older Microsoft LAN Manager 2.0 software.

The nMaxInstances parameter specifies how many instances or pipe handles can be created for a named pipe. A pipe instance is a connection from a local or remote client application to a server application that created the named pipe. Acceptable values are in the range 1 through PIPE_UNLIMITED_INSTANCES. For example, if you want to develop a server that can service only five client connections at a time, set this parameter to 5. If you set this parameter to PIPE_UNLIMITED_INSTANCES, the number of pipe instances that can be created is limited only by the availability of system resources.

CreateNamedPipe()'s nOutBufferSize and nInBufferSize parameters represent the number of bytes to reserve for internal input and output buffer sizes. These sizes are advisory in that every time a named pipe instance is created, the system sets up inbound and/or outbound buffers using the nonpaged pool (the physical memory used by the operating system). The buffer size specified should be reasonable (not too large) so that your system will not run out of nonpaged pool memory, but it should also be large enough to accommodate typical I/O requests. If an application attempts to write data that is larger than the buffer sizes specified, the system tries to automatically expand the buffers to accommodate the data using nonpaged pool memory. For practical purposes, applications should size these internal buffers to match the size of the application's send and receive buffers used when calling ReadFile() and WriteFile().

The nDefaultTimeOut parameter specifies the default timeout value (how long a client will wait to connect to a named pipe) in milliseconds. This affects only client applications that use the WaitNamedPipe() function to determine when an instance of a named pipe is available to accept connections. We discuss this concept in greater detail later in this chapter, when we develop a named pipe client application.

The lpSecurityAttributes parameter allows the application to specify a security descriptor for a named pipe and determines whether a child process can inherit the newly created handle. If this parameter is specified as NULL, the named pipe gets a default security descriptor and the handle cannot be inherited. A default security descriptor grants the named pipe the same security limits and access controls as the process that created it following the Windows NT platform security. An application can apply access control restrictions to a pipe by setting access privileges for particular users and groups in a SECURITY_DESCRIPTOR structure using security API functions. If a server wants to open access to any client, you should assign a null DACL to the SECURITY_DESCRIPTOR structure.

After you successfully receive a handle from CreateNamedPipe(), which is known as a pipe instance, you have to wait for a connection from a named pipe client. This connection can be made through the ConnectNamedPipe() API function, which is defined as follows:

BOOL ConnectNamedPipe(HANDLE hNamedPipe, LPOVERLAPPED lpOverlapped);

The hNamedPipe parameter represents the pipe instance handle returned from CreateNamedPipe(). The lpOverlapped parameter allows this API function to operate asynchronously, or in nonblocking mode, if the pipe was created using the FILE_FLAG_OVERLAPPED flag, which is known as Windows overlapped I/O. If this parameter is specified as NULL, ConnectNamedPipe() blocks until a client forms a connection to the server. We discuss overlapped I/O in greater detail when you learn to create a more advanced named pipe server later in this chapter.

Once a named pipe client successfully connects to your server, the ConnectNamedPipe() API call completes. The server is then free to send data to a client using the WriteFile() API function and to receive data from the client using ReadFile(). Once the server has finished communicating with a client, it should call DisconnectNamedPipe() to close the communication session. The following sample demonstrates how to write a simple server application that can communicate with one client.

 

// Server sample

#include <windows.h>

#include <stdio.h>

 

void main(void)

{

    HANDLE PipeHandle;

    DWORD BytesRead;

    CHAR buffer[256];

    if ((PipeHandle = CreateNamedPipe("\\\\.\\Pipe\\Jim",

        PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE │ PIPE_READMODE_BYTE, 1,e0, 0, 1000, NULL)) == INVALID_HANDLE_VALUE)

    {

        printf("CreateNamedPipe failed with error %d\n", GetLastError());

        return;

    }

 

    printf("Server is now running\n");

 

    if (ConnectNamedPipe(PipeHandle, NULL) == 0)

    {

        printf("ConnectNamedPipe failed with error %d\n", GetLastError());

        CloseHandle(PipeHandle);

        return;

    }

 

    if (ReadFile(PipeHandle, buffer, sizeof(buffer), &BytesRead,  NULL) <= 0)

    {

        printf("ReadFile failed with error %d\n", GetLastError());

        CloseHandle(PipeHandle);

        return;

    }

 

    printf("%.*s\n", BytesRead, buffer);

 

    if (DisconnectNamedPipe(PipeHandle) == 0)

    {

        printf("DisconnectNamedPipe failed with error %d\n", GetLastError());

        return;

    }

    CloseHandle(PipeHandle);

}

 

 

 


< Chap 15: Index | Winsock 2 Main | Name Pipes DACL, Threads, Overlapped, Client, Server >