The use of named pipes involves two types of processes: the server process and the client process. The following sections will discuss the considerations for writing server and client processes.
A server process uses the CreateNamedPipe function to create one or more instances of a named pipe. The server specifies the name of the pipe, along with the type, access modes, and other attributes associated with the pipe.
The simplest server process would create a single instance of a pipe, connect to a single client, communicate with the client, disconnect the pipe, close the pipe handle, and terminate. Typically, however, a server process will be created to communicate with multiple client processes. This could be done with a single pipe instance connecting to and disconnecting from each client in sequence. To handle multiple clients simultaneously, you would need to create multiple pipe instances.
The Win32 API supports three basic strategies for servicing multiple pipe instances. The server process could:
Create multiple threads with a separate thread for each instance of the pipe
Use overlapped operations by specifying an OVERLAPPED structure in the ReadFile, WriteFile, and ConnectNamedPipe functions, in which case a single server thread waits for one of multiple event objects to be Signalled.
Use overlapped operations by using the ReadFileEx and WriteFileEx functions which specify a completion routine to be executed when the operation completes.
The multi-threaded server strategy is simple and easy to code, since the instance thread deals with communications for only a single client. The system handles the allocation of processor time to each thread as needed. But each thread uses system resources, which is a potential disadvantage for a server that handles a large number of clients. Other complications occur if the actions of one client necessitate communications with other clients (as with a network game program where a move by one player must be communicated to the other players).
With a single-threaded server, it is easier to coordinate operations that affect multiple clients and it is easier to protect shared resources (for example, a database file) from simultaneous access by multiple clients. The challenge of a single-threaded server is that it requires the coordination of overlapped (asynchronous) operations in order to allocate processor time for handling the simultaneous needs of the clients.
The following examples show how to create each of these server types.
Example: A Multi-threaded Server
This example of a multi-threaded server process has a main thread that loops continuously creating a pipe instance and waiting for a client to connect. When client connects, a thread is created to handle that instance and the loop starts over. Each instance thread closes its handle and terminates when its client has finished using the pipe.
VOID InstanceThread(LPVOID);
VOID GetAnswerToClientRequest(LPTSTR, LPTSTR, LPDWORD);
DWORD main(void)
{
BOOL fConnected;
DWORD dwThreadId;
HANDLE hPipe, hThread;
/*
* Main loop creates an instance of the named pipe, and then waits
* for a client to connect to it. When client connects, a thread is
* created to handle communications with that client, and the loop is
* repeated to wait for the next client.
*/
for (;;) {
hPipe = CreateNamedPipe("\\\\.\\pipe\\mynamedpipe", /* pipename */
PIPE_ACCESS_DUPLEX, /* read/write access */
0, /* use defaults: type=byte, readmode=byte, blocking I/O */
PIPE_UNLIMITED_INSTANCES, /* maximum simultaneous instances */
BUFSIZE, /* output buffer size */
BUFSIZE, /* input buffer size */
PIPE_TIMEOUT, /* default timeout for WaitNamedPipe */
NULL); /* no security attr, not inheritable */
if (hPipe == INVALID_HANDLE_VALUE)
ErrExit("CreatePipe");
fConnected = ConnectNamedPipe(hPipe, NULL);
if (fConnected) {
hThread = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)InstanceThread, (LPVOID) hPipe,
0, &dwThreadId);
if (hThread == INVALID_HANDLE_VALUE)
ErrExit("CreateThread");
}
else /* could not connect, so close pipe */
CloseHandle(hPipe);
}
return 1;
}
VOID InstanceThread(LPVOID lpvParam)
{
CHAR chRequest[BUFSIZE];
CHAR chReply[BUFSIZE];
DWORD cbBytesRead, cbReplyBytes, cbWritten;
BOOL fSuccess;
HANDLE hPipe;
/*
* Read client requests from pipe and write answers to pipe until
* client closes pipe or an error occurs.
*/
hPipe = (HANDLE) lpvParam;
while (1) {
fSuccess = ReadFile(hPipe, chRequest, BUFSIZE, &cbBytesRead, NULL);
if (! fSuccess || cbBytesRead == 0)
break;
GetAnswerToClientRequest(chRequest, chReply, &cbReplyBytes);
fSuccess = WriteFile(hPipe, chReply, cbReplyBytes, &cbWritten, NULL);
if (! fSuccess || cbReplyBytes != cbWritten) break;
}
/*
* Flush pipe to allow client to read pipe contents before disconnect,
* the disconnect pipe and close handle to pipe instance.
*/
FlushFileBuffers(hPipe);
DisconnectNamedPipe(hPipe);
CloseHandle(hPipe);
}
Example: A Server Using Overlapped I/O
This example of overlapped operations uses an Event object associated with each pipe instance to signal the completion of a read, write, or connect operation. Because a single Event object is used for all three types of operations, the program is careful to avoid simultaneous overlapped operations on a single pipe instance. It also maintains a state variable for each instance to keep track of which operation should be performed next. Each pipe instance has its own OVERLAPPED structure whose hEvent member is the Event object for that instance. This OVERLAPPED structure is used as a parameter in each overlapped operation on that instance. The Event handles for all pipe instances are also stored in an array that is used in WaitForMultipleObjects to wait for one of the Events to be Signalled.
The server sends a message packet when it writes to the pipe. The first four bytes of the packet are a code that signals whether the packet contains data. In byte mode, the client could not be sure which bytes where at the beginning of a message, so the server creates the pipe in message mode. The client must use SetNamedPipeHandleState to set its handle to message readmode.
When ReadFile and WriteFile are executed asynchronously (overlappedly), the operation may complete before the function returns. In this case, the results of the operation (success/failure, number of bytes transferred) are returned by the function. Otherwise, the function returns FALSE and the GetLastError function returns ERROR_IO_PENDING. GetOverlappedResult returns the results of the last operation that returned FALSE with ERROR_IO_PENDING on a specified instance using a specified OVERLAPPED structure. GetOverlappedResult does not report the results of operations that completed before the ReadFile or WriteFile returned.
Before disconnecting from a client, the multi-threaded server example in the previous section used FlushFileBuffers to ensure that the client had read everything written to the pipe. This would defeat the purpose of overlapped I/O since the flush would block until the client got around to emptying the pipe. Consequently, it is necessary to wait for a signal from the client that it has finished before disconnecting. In this example, the signal is the error generated by trying to read from the pipe after the client has closed its handle.
#define COMPLETED_READ_STATE 1
#define WRITING_REPLY_STATE 2
#define READ_REQUEST_STATE 3
typedef struct {
DWORD dwCode;
CHAR chBuf[BUFSIZE];
} PACKET, *LPPACKET;
typedef struct {
HANDLE hPipeInst;
DWORD dwState;
LPPACKET lpPacket;
DWORD cbBytes;
DWORD cbBytesToWrite;
HANDLE hFile;
LPOVERLAPPED lpO;
BOOL fPendingIO;
} PIPEINST, *LPPIPEINST;
VOID DisconnectPipe(DWORD);
PIPEINST Pipe[INSTANCES];
HANDLE hEvents[INSTANCES];
DWORD main(void)
{
DWORD i, dwWait, cbRead;
BOOL fSuccess, fConnected;
/*
* Initial loop creates n instances of pipe, along with an Event
* object and data structures for each instance. An overlapped
* ConnectNamedPipe operation is started for each instance. Then
* drop through to the wait loop that waits for a connection or
* some overlapped operation on a connected pipe.
*/
for (i = 0; i < INSTANCES; i++) {
Pipe[i].hPipeInst = CreateNamedPipe("\\\\.\\pipe\\mynamedpipe",
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
PIPE_WAIT | PIPE_TYPE_MESSAGE,
INSTANCES, /* maximum simultaneous instances */
BUFSIZE, /* output buffer size */
BUFSIZE, /* input buffer size */
PIPE_TIMEOUT, /* default timeout used by WaitNamedPipe */
NULL); /* no security attributes (not inheritable) */
if (Pipe[i].hPipeInst == INVALID_HANDLE_VALUE)
ErrExit("CreatePipe");
/* CreateEvent: No SecAttr, ManualReset, Not Signalled, No Name */
hEvents[i] = CreateEvent(NULL, TRUE, FALSE, NULL);
if (hEvents[i] == NULL)
ErrExit("CreateEvent");
/* allocate OVERLAPPED struct and buffer for this pipe instance */
Pipe[i].lpO = (LPOVERLAPPED) GlobalAlloc(GPTR, sizeof(OVERLAPPED));
if (Pipe[i].lpO == NULL)
ErrExit("GlobalAlloc OVERLAPPED");
Pipe[i].lpPacket = (LPPACKET) GlobalAlloc(GPTR, sizeof(PACKET));
if (Pipe[i].lpPacket == NULL)
ErrExit("GlobalAlloc lpPacket");
Pipe[i].dwState = READ_REQUEST_STATE;
Pipe[i].fPendingIO = TRUE;
Pipe[i].lpO->hEvent = hEvents[i];
/*
* Start overlapped ConnectNamedPipe for this instance of pipe.
* Overlapped ConnectNamedPipe should always return FALSE.
*/
fConnected = ConnectNamedPipe(Pipe[i].hPipeInst, Pipe[i].lpO);
if (fConnected || (GetLastError() != ERROR_IO_PENDING))
ErrExit("ConnectNamedPipe");
}
/*
* Wait for one of the event objects to be signalled, indicating
* completion of an overlapped operation -- read, write, or connect.
* Return value of WaitForMultipleObjects shows which pipe had
* completed operation; and the data structure for that pipe shows
* whether there was pending I/O (Pipe[i].fPendingIO), and the
* next operation to be performed (Pipe[i].dwState).
*/
while (1) {
dwWait = WaitForMultipleObjects(INSTANCES, hEvents, FALSE, INFINITE);
i = dwWait - WAIT_OBJECT_0; /* determine which pipe */
/* get result if operation was pending; disconnect if error occurred */
if (Pipe[i].fPendingIO) {
fSuccess = GetOverlappedResult(Pipe[i].hPipeInst,
Pipe[i].lpO, &Pipe[i].cbBytes, FALSE);
if (! fSuccess && Pipe[i].dwState == READ_REQUEST_STATE)
ErrExit("ConnectNamedPipe");
if (! fSuccess ||
((Pipe[i].dwState == COMPLETED_READ_STATE) &&
(Pipe[i].cbBytes == 0)) ||
((Pipe[i].dwState == WRITING_REPLY_STATE) &&
(Pipe[i].cbBytes != Pipe[i].cbBytesToWrite))) {
DisconnectPipe(i);
continue;
}
}
/* pipe state determines what operation to do next */
switch (Pipe[i].dwState) {
/*
* COMPLETED_READ_STATE:
* Finished reading request from client. In this program, the
* request is the name of a file. The server tries to open it
* to write the contents back to the client. If the file cannot
* be opened, the server sends an error code to client.
*/
case COMPLETED_READ_STATE:
Pipe[i].hFile = CreateFile(Pipe[i].lpPacket->chBuf, GENERIC_READ,
0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
Pipe[i].lpPacket->dwCode =
(Pipe[i].hFile == INVALID_HANDLE_VALUE) ? GetLastError() : 0;
/* fall through */
/*
* WRITING_REPLY_STATE:
* If no error, read a page from the file. Send packet to client.
* If still no error, packet code = 0 and packet contains data;
* otherwise, packet code = error code.
*/
case WRITING_REPLY_STATE:
if (Pipe[i].lpPacket->dwCode == 0) {
fSuccess = ReadFile(Pipe[i].hFile, Pipe[i].lpPacket->chBuf,
BUFSIZE, &cbRead, NULL);
if (! fSuccess || cbRead == 0) {
Pipe[i].lpPacket->dwCode = fSuccess ? EOF : GetLastError();
CloseHandle(Pipe[i].hFile);
}
}
if (Pipe[i].lpPacket->dwCode == 0) {
Pipe[i].dwState = WRITING_REPLY_STATE;
Pipe[i].cbBytesToWrite = sizeof(DWORD) + cbRead;
}
else {
Pipe[i].dwState = READ_REQUEST_STATE;
Pipe[i].cbBytesToWrite = sizeof(DWORD);
}
fSuccess = WriteFile(Pipe[i].hPipeInst, Pipe[i].lpPacket,
Pipe[i].cbBytesToWrite, &Pipe[i].cbBytes, Pipe[i].lpO);
Pipe[i].fPendingIO = ! fSuccess;
if ((! fSuccess && (GetLastError() != ERROR_IO_PENDING)) ||
(fSuccess && (Pipe[i].cbBytes != Pipe[i].cbBytesToWrite)))
DisconnectPipe(i);
break;
/*
* READ_REQUEST_STATE:
* Pipe is connected, ready to read request from client
* If error other than ERROR_IO_PENDING, disconnect pipe.
* If ReadFile returns TRUE, the read has completed and
* Pipe[i].cbBytes has # of bytes read; otherwise, the
* read is still pending and GetOverlappedResult must be
* called when it completes.
*/
case READ_REQUEST_STATE:
Pipe[i].dwState = COMPLETED_READ_STATE;
fSuccess = ReadFile(Pipe[i].hPipeInst, Pipe[i].lpPacket->chBuf,
BUFSIZE, &Pipe[i].cbBytes, Pipe[i].lpO);
Pipe[i].fPendingIO = ! fSuccess;
if ((! fSuccess && (GetLastError() != ERROR_IO_PENDING)) ||
(fSuccess && (Pipe[i].cbBytes == 0)))
DisconnectPipe(i);
break;
default:
ErrExit("ERROR: got default case");
}
}
return 0;
}
/*
* DisconnectPipe()
* Called when error occurs or when client closes its handle to
* the pipe. The server disconnects and then does another
* overlapped ConnectNamedPipe to wait for another client to connect.
*/
VOID DisconnectPipe(DWORD i)
{
BOOL fDisconnected, fConnected;
fDisconnected = DisconnectNamedPipe(Pipe[i].hPipeInst);
if (! fDisconnected)
ErrExit("DisconnectNamedPipe");
fConnected = ConnectNamedPipe(Pipe[i].hPipeInst, Pipe[i].lpO);
if (! fConnected && GetLastError() != ERROR_IO_PENDING)
ErrExit("Re-ConnectNamedPipe");
Pipe[i].dwState = READ_REQUEST_STATE;
Pipe[i].fPendingIO = TRUE;
}
Example: A Server Using Completion Routines
Like the previous example, this server creates a message mode pipe and uses overlapped operations. This server differs in its use of ReadFileEx and WriteFileEx to perform overlapped I/O. Unlike the overlapped ReadFile and WriteFile which signal an Event object on completion, the extended functions specify a completion routine which is enqueued for execution when the operation completes. The server uses one of the alertable wait functions, WaitForSingleObjectEx, which returns when a completion routine is ready to execute. The wait function also returns when an Event object is Signalled indicating that the overlapped ConnectNamedPipe operation has completed (a new client has connected).
When a client connects, the server uses ReadFileEx to launch a sequence of I/O operations to handle communications with the client. Each operation specifies a completion routine that performs the next operation in the sequence. The sequence terminates when the client is disconnected and the pipe instance closed. ReadFileEx and WriteFileEx specify a parameter that points to an OVERLAPPED structure. Since these functions do not need to use an Event object, the hEvent member of this structure can be used to pass information to the completion routine.
typedef struct {
DWORD dwCode;
CHAR chBuf[BUFSIZE];
} PACKET, *LPPACKET;
typedef struct {
HANDLE hPipeInst;
LPPACKET lpPacket;
HANDLE hFile;
LPOVERLAPPED lpO;
} PIPEINST, *LPPIPEINST;
VOID DisconnectPipe(LPPIPEINST);
VOID ReadRequestRoutine(DWORD, DWORD, LPOVERLAPPED);
VOID CompletedReadRoutine(DWORD, DWORD, LPOVERLAPPED);
VOID WritingReplyRoutine(DWORD, DWORD, LPOVERLAPPED);
DWORD main(void)
{
HANDLE hPipe, hConnectEvent;
OVERLAPPED ConnectOverLapped;
LPPIPEINST lpPipeInst;
DWORD dwWait;
BOOL fConnected, fCreateInstance = TRUE;
/* Create manual reset event for overlapped ConnectNamedPipe */
hConnectEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (hConnectEvent == NULL)
ErrExit("CreateEvent");
ConnectOverLapped.hEvent = hConnectEvent;
while (1) {
/*
* Create a pipe instance and start an overlapped ConnectNamedPipe
* for it. Do this the first time through the loop, and
* each time a new client connects to the pipe create another
* instance for the next client.
*/
if (fCreateInstance) {
hPipe = CreateNamedPipe("\\\\.\\pipe\\mynamedpipe",
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
PIPE_WAIT | PIPE_TYPE_MESSAGE,
PIPE_UNLIMITED_INSTANCES, /* unlimited pipe instances */
BUFSIZE, /* output buffer size */
BUFSIZE, /* input buffer size */
PIPE_TIMEOUT, /* default timeout for WaitNamedPipe */
NULL); /* no security attr, not inheritable */
if (hPipe == INVALID_HANDLE_VALUE)
ErrExit("CreatePipe");
fConnected = ConnectNamedPipe(hPipe, &ConnectOverLapped);
if (fConnected || (GetLastError() != ERROR_IO_PENDING))
ErrExit("ConnectNamedPipe");
fCreateInstance = FALSE;
}
/*
* Do an alertable wait which is satisfied by hConnectEvent
* being signalled when a client connects, or by the completion
* of a ReadFileEx or WriteFileEx operation on any instance of
* the pipe, in which case WaitForSingleObjectEx returns
* WAIT_IO_COMPLETION and the completion routine is executed.
*/
dwWait = WaitForSingleObjectEx(hConnectEvent, INFINITE, TRUE);
switch (dwWait) {
/*
* If dwWait = 0, the wait was satisfied by the hConnectEvent
* being signalled. This only happens when a new client
* connects, so allocate data structures for this instance
* of pipe and then call ReadRequestRoutine() to start a
* series of ReadFileEx and WriteFileEx calls for this client.
*
* fCreateInstance is set to TRUE to trigger creation of new pipe
* instance and connect operation at top of loop.
*/
case 0:
fCreateInstance = TRUE;
lpPipeInst = (LPPIPEINST) GlobalAlloc(GPTR, sizeof(PIPEINST));
if (lpPipeInst == NULL)
ErrExit("GlobalAlloc lpPipeInst");
lpPipeInst->lpO = (LPOVERLAPPED) GlobalAlloc(GPTR, sizeof(OVERLAPPED));
if (lpPipeInst->lpO == NULL)
ErrExit("GlobalAlloc lpOverLapped");
lpPipeInst->lpPacket = (LPPACKET) GlobalAlloc(GPTR, sizeof(PACKET));
if (lpPipeInst->lpPacket == NULL)
ErrExit("GlobalAlloc lpPacket");
lpPipeInst->hPipeInst = hPipe;
lpPipeInst->lpO->hEvent = lpPipeInst;
ReadRequestRoutine(0, 0, lpPipeInst->lpO);
case WAIT_IO_COMPLETION:
break;
default: /* If the wait failed, error out */
ErrExit("WaitForSingleObjectEx");
}
}
return 0;
}
/*
* ReadRequestRoutine()
* Read client request from pipe. Called when a pipe instance
* has just connected to a new client, or as an I/O completion
* routine after done responding to previous request from client.
*/
VOID ReadRequestRoutine(DWORD dwErr, DWORD cbBytesRead,
LPOVERLAPPED lpOverLap)
{
LPPIPEINST lpPipeInst;
BOOL fRead;
lpPipeInst = (LPPIPEINST) lpOverLap->hEvent;
if (dwErr == 0)
fRead = ReadFileEx(lpPipeInst->hPipeInst, lpPipeInst->lpPacket->chBuf,
BUFSIZE, lpPipeInst->lpO, CompletedReadRoutine);
if (dwErr != 0 || ! fRead)
DisconnectPipe(lpPipeInst);
UNREFERENCED_PARAMETER(cbBytesRead);
}
/*
* CompletedReadRoutine()
* Read operation has completed. If client closed its end of pipe,
* dwErr will indicate an error, so disconnect. Otherwise, lpPacket->chBuf
* contains a filename. If cannot open file, write error message
* to client; else call WritingReplyRoutine to send contents of file.
*/
VOID CompletedReadRoutine(DWORD dwErr, DWORD cbBytesRead,
LPOVERLAPPED lpOverLap)
{
LPPIPEINST lpPipeInst;
lpPipeInst = (LPPIPEINST) lpOverLap->hEvent;
if (dwErr != 0 || cbBytesRead == 0) {
DisconnectPipe(lpPipeInst);
}
else {
lpPipeInst->hFile = CreateFile(lpPipeInst->lpPacket->chBuf, GENERIC_READ,
0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
dwErr = (lpPipeInst->hFile == INVALID_HANDLE_VALUE) ? GetLastError() : 0;
WritingReplyRoutine(dwErr, 0, lpOverLap);
}
}
/*
* WritingReplyRoutine()
* Read from file and write contents to pipe.
*/
VOID WritingReplyRoutine(DWORD dwErr, DWORD cbBytesWritten,
LPOVERLAPPED lpOverLap)
{
LPPIPEINST lpPipeInst;
BOOL fRead, fWrite;
DWORD cbRead, cbBytesToWrite = sizeof(DWORD);
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine = ReadRequestRoutine;
lpPipeInst = (LPPIPEINST) lpOverLap->hEvent;
if (dwErr == 0) {
fRead = ReadFile(lpPipeInst->hFile, lpPipeInst->lpPacket->chBuf,
BUFSIZE, &cbRead, NULL);
if (! fRead || cbRead == 0) {
dwErr = fRead ? 0xffffffff: GetLastError();
CloseHandle(lpPipeInst->hFile);
}
}
lpPipeInst->lpPacket->dwCode = dwErr;
if (dwErr == 0) {
cbBytesToWrite += cbRead;
lpCompletionRoutine = WritingReplyRoutine;
}
fWrite = WriteFileEx(lpPipeInst->hPipeInst, lpPipeInst->lpPacket,
cbBytesToWrite, lpPipeInst->lpO, lpCompletionRoutine);
if (! fWrite)
DisconnectPipe(lpPipeInst);
UNREFERENCED_PARAMETER(cbBytesWritten);
}
/*
* DisconnectPipe()
* Called when an error occurs or a client closes its handle to
* the pipe. The server disconnects, closes the pipe handle,
* and releases the memory associated with the pipe.
*/
VOID DisconnectPipe(LPPIPEINST lpPipeInst)
{
BOOL fDisconnected;
fDisconnected = DisconnectNamedPipe(lpPipeInst->hPipeInst);
if (! fDisconnected)
ErrExit("DisconnectNamedPipe");
CloseHandle(lpPipeInst->hPipeInst);
if (lpPipeInst->lpO != NULL)
GlobalFree(lpPipeInst->lpO);
if (lpPipeInst->lpPacket != NULL)
GlobalFree(lpPipeInst->lpPacket);
if (lpPipeInst != NULL)
GlobalFree(lpPipeInst);
}
A client process uses the CreateFile function to open a handle to a named pipe. If the pipe exists but it is busy, CreateFile will returns FALSE and GetLastError returns ERROR_PIPE_BUSY. The WaitNamedPipe function can then be used to wait for an instance of the pipe to be available. The access specified in the CreateFile call must be compatible with the access specified when the server created the pipe.
The handle returned by CreateFile defaults to byte readmode, synchronous I/O, and blocking I/O. Asynchronous (overlapped) I/O can be enabled by specifying FILE_FLAG_OVERLAPPED in the CreateFile call. Non-blocking I/O and message readmode can be enabled by specifying PIPE_NOWAIT and PIPE_READMODE_MESSAGE in a call to SetNamedPipeHandleState.
The following example shows a client process that opens a named pipe, sets the pipe handle to message readmode, uses WriteFile to send a request to the server, and uses ReadFile to read the server's reply. This client could be used with either of the message mode server examples in the sections above (“Example: A Server Using Overlapped I/O” or “Example: A Server Using Completion Routines”), but would fail with a byte mode server. Because the pipe is being read in message readmode, the process needs to check for additional data if ReadFile returns FALSE.
typedef struct {
DWORD dwCode;
CHAR chBuf[BUFSIZE];
} PACKET, *LPPACKET;
DWORD main(int argc, char *argv[])
{
HANDLE hStdout, hPipe;
PACKET ReadPacket;
LPVOID lpvPipeRequest;
BOOL fSuccess;
DWORD cbRead, cbWritten, dwMode;
LPTSTR lpszPipename = "\\\\.\\pipe\\mynamedpipe";
if (argc < 2) {
printf("usage: client <PipeRequest>\n");
return 0;
}
lpvPipeRequest = argv[1];
/* Try to open named pipe, and wait for it, if necessary */
while (1) {
hPipe = CreateFile(lpszPipename,
GENERIC_READ | GENERIC_WRITE,
0, /* no sharing of this pipe instance */
NULL, /* no security attributes */
OPEN_EXISTING,
0,
NULL);
if (hPipe != INVALID_HANDLE_VALUE) break;
if ((GetLastError() != ERROR_PIPE_BUSY) ||
(! WaitNamedPipe(lpszPipename, 20000)))
ErrExit("Could not open pipe");
}
dwMode = PIPE_READMODE_MESSAGE;
fSuccess = SetNamedPipeHandleState(hPipe, &dwMode, NULL, NULL);
if (! fSuccess) ErrExit("SetNamedPipeHandleState error");
/* Send message (the name of a file) to pipe server */
fSuccess = WriteFile(hPipe, lpvPipeRequest, strlen(lpvPipeRequest)+1,
&cbWritten, NULL);
if (! fSuccess)
ErrExit("WriteFile error");
/*
* Read message packets from pipe. A packet code of 0 indicates the
* packet contains data which is written to StdOut; otherwise, it is
* an error and the handle is closed.
*/
hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
while (fSuccess) {
fSuccess = ReadFile(hPipe, &ReadPacket, sizeof(PACKET), &cbRead, NULL);
if ((ReadPacket.dwCode != 0) ||
(! fSuccess && GetLastError() != ERROR_MORE_DATA))
break;
if (! WriteFile(hStdout, ReadPacket.chBuf, cbRead-sizeof(DWORD),
&cbWritten, NULL))
break;
while (! fSuccess) { /* must be more data in message */
fSuccess = ReadFile(hPipe, &ReadPacket.chBuf, BUFSIZE, &cbRead, NULL);
if (! fSuccess && GetLastError() != ERROR_MORE_DATA)
break;
if (! WriteFile(hStdout, ReadPacket.chBuf, cbRead, &cbWritten, NULL))
break;
}
}
CloseHandle(hPipe);
return 0;
}
There are two functions, TransactNamedPipe and CallNamedPipe, that support client-server transactions for duplex message mode pipes. A transaction combines a write operation and a read operation into a single network operation. This improves the performance of communications between a client and a remote server.
To use the TransactNamedPipe function, the pipe handle must be opened by CreateFile for GENERIC_READ | GENERIC_WRITE access, and it must be set to message readmode by calling SetNamedPipeHandleState. The function can be called as an overlapped operation. If the message contains more than the specified number of bytes, the remainder of the message can be read by calling ReadFile, ReadFileEx, or PeekNamedPipe. TransactNamedPipe is typically called by client processes, but may be used by a server process as well.
A client process uses the CallNamedPipe function to combine the CreateFile, WaitNamedPipe (if necessary), TransactNamedPipe, and CloseHandle operations into a single function. Because the pipe handle is closed before the function returns, any additional bytes in the message are lost if the message contains more than the specified number of bytes.