Chapter 6 showed how to create and manage processes, and Chapters 7 to 10 showed how to manage and synchronize threads within processes. So far, however, we have not been able to perform direct process-to-process communication other than through shared memory (Chapter 5).
The next step is to provide sequential interprocess communication (IPC) between processes1 using filelike objects. Two primary Windows mechanisms for IPC are the anonymous pipe and the named pipe, both of which are accessed with the familiar ReadFile
and WriteFile
functions. Simple anonymous pipes are character-based and half-duplex. As such, they are well suited for redirecting the output of one program to the input of another, as is common with communicating Linux and UNIX programs. The first example shows how to do this with Windows anonymous pipes.
1 The Windows system services also allow processes to communicate through mapped files, as demonstrated in the semaphore exercise in Chapter 10 (Exercise 10–10). Additional mechanisms for IPC include files, sockets, remote procedure calls, COM, and message posting. Chapter 12 describes sockets.
Named pipes are much more powerful than anonymous pipes. They are full-duplex and message-oriented, and they allow networked communication. Furthermore, there can be multiple open handles on the same pipe. These capabilities, coupled with convenient transaction-oriented named pipe functions, make named pipes appropriate for creating client/server systems. This capability is shown in this chapter’s second example, a multithreaded client/server command processor, modeled after Figure 7-1, which was used to introduce threads. Each server thread manages communication with a different client, and each thread/client pair uses a distinct handle, or named pipe instance. Mailslots, which allow for one-to-many message broadcasting and are also filelike, are used to help clients locate servers.
Windows anonymous pipes allow one-way (half-duplex), byte-based IPC. Each pipe has two handles: a read handle and a write handle. The CreatePipe
function is:
The pipe handles are often inheritable; the next example shows the reasons. cbPipe
, the pipe byte size, is only a suggestion, and 0
specifies the default value.
In order to use the pipe for IPC, there must be another process, and that process requires one of the pipe handles. Assume that the parent process, which calls CreatePipe
, wishes to write data for a child to use. The problem, then, is to communicate the read handle (phRead
) to the child. The parent achieves this by setting the child procedure’s input handle in the start-up structure to *phRead
(see Chapter 6 for process management and the start-up structure).
Reading a pipe read handle will block if the pipe is empty. Otherwise, the read will accept as many bytes as are in the pipe, up to the number specified in the ReadFile
call. A write operation to a full pipe, which is implemented in a memory buffer, will also block.
Finally, anonymous pipes are one-way. Two pipes are required for bidirectional communication.
Program 11-1 shows a parent process, Redirect
, that creates two processes from the command line and pipes them together. The parent process sets up the pipe and redirects standard input and output. Notice how the anonymous pipe handles are inheritable and how standard I/O is redirected to the two child processes; these techniques were described in Chapter 6.
The location of WriteFile
in Program2
on the right side of Figure 11-1 assumes that the program reads a large amount of data, processes it, and then writes out results. Alternatively, the write could be inside the loop, putting out results after each read.
Close the pipe and thread handles at the earliest possible point. Figure 11-1 does not show the handle closings, but Program 11-1 does. The parent should close the standard output handle immediately after creating the first child process so that the second process will be able to recognize an end of file when the first process terminates. If there were still an open handle, the second process might not terminate because the application would not indicate an end of file.
Program 11-1 uses an unusual syntax; the =
sign is the pipe symbol separating the two commands. The vertical bar ( |
) would conflict with the command processor. Figure 11-1 schematically shows the execution of the command:
$ Redirect Program1 arguments = Program2 arguments
In UNIX or at the Windows command prompt, the corresponding command would be:
$ Program1 arguments | Program2 arguments
Run 11-1 shows output from grepMT
, Chapter 7’s multithreaded pattern search program piped to FIND
, which is a similar Windows command. While this may seem a bit artificial, cat
is the book’s only sample program that accepts standard input, and it also shows that Redirect
works with third-party programs that accept standard input.
These examples search the presidents and monarchs files, first used in Chapter 6, for individuals named “James” and “George” who lived in any part of the eighteenth century (the search is not entirely accurate) or “William” who lived in any part of the nineteenth century. The file names were shortened to decrease the horizontal space.
Named pipes have several features that make them an appropriate general-purpose mechanism for implementing IPC-based applications, including networked file access and client/server systems,2 although anonymous pipes remain a good choice for simple byte-stream IPC, such as the preceding example, where communication is within a single computer. Named pipe features (some are optional) include the following.
2 This statement requires a major qualification. Windows Sockets (Chapter 12) is the preferred API for most networking applications and higher-level protocols (http, ftp, and so on), especially where TCP/IP-based interoperability with non-Windows systems is required. Many developers prefer to limit named pipe usage to IPC within a single computer or to communication within Windows networks.
• Named pipes are message-oriented, so the reading process can read varying-length messages precisely as sent by the writing process.
• Named pipes are bidirectional, so two processes can exchange messages over the same pipe.
• There can be multiple, independent instances of pipes with the same name. For example, several clients can communicate concurrently with a single server using distinct instances of a named pipe. Each client can have its own named pipe instance, and the server can respond to a client using the same instance.
• Networked clients can access the pipe by name. Named pipe communication is the same whether the two processes are on the same machine or on different machines.
• Several convenience and connection functions simplify named pipe request/response interaction and client/server connection.
Named pipes are generally preferable to anonymous pipes, although Program 11-1 and Figure 11-1 did illustrate a situation in which anonymous pipes are useful. Use named pipes any time your communication channel needs to be bidirectional, message-oriented, networked, or available to multiple client processes. The upcoming examples could not be implemented using anonymous pipes.
CreateNamedPipe
creates the first instance of a named pipe and returns a handle. The function also specifies the pipe’s maximum number of instances and, hence, the number of clients that can be supported simultaneously.
Normally, the creating process is regarded as the server. Client processes, possibly on other systems, open the pipe with CreateFile
.
Figure 11-2 shows an illustrative client/server relationship, and the pseudocode shows one scheme for using named pipes. Notice that the server creates multiple instances of the same pipe, each of which can support a client. The server also creates a thread for each named pipe instance, so that each client has a dedicated thread and named pipe instance. Figure 11-2, then, shows how to implement the multithreaded server model of Figure 7-1.
Here is the specification of the CreateNamedPipe
function.
lpName
indicates the pipe name, which must be of the form:
\.pipepipename
The period (.
) stands for the local machine; thus, you cannot create a pipe on a remote machine. The pipename is case-insensitive, can be up to 256 characters long, and can contain any character other than backslash.
dwOpenMode
specifies several flags; the important ones for our purposes are:
• One of three mutually exclusive data flow description flags—PIPE_ACCESS_DUPLEX
, PIPE_ACCESS_INBOUND
, or PIPE_ACCESS_OUTBOUND
. The value determines the combination of GENERIC_READ
and GENERIC_WRITE
from the server’s perspective. Thus, PIPE_ACCESS_INBOUND
gives the server GENERIC_READ
access, and the client must use GENERIC_WRITE
when connecting with CreateFile
. If the access is PIPE_ACCESS_DUPLEX
, data flows bidirectionally, and the client can specify GENERIC_READ
, GENERIC_WRITE
, or both.
• FILE_FLAG_OVERLAPPED
enables asynchronous I/O (Chapter 14).
The mode can also specify FILE_FLAG_WRITE_THROUGH
(not used with message pipes), FILE_FLAG_FIRST_PIPE_INSTANCE
, and more (see MSDN).
dwPipeMode
has three mutually exclusive flag pairs. They indicate whether writing is message-oriented or byte-oriented, whether reading is by messages or blocks, and whether read operations block.
• PIPE_TYPE_BYTE
and PIPE_TYPE_MESSAGE
indicate whether data is written to the pipe as a stream of bytes or messages. Use the same type value for all pipe instances with the same name.
• PIPE_READMODE_BYTE
and PIPE_READMODE_MESSAGE
indicate whether data is read as a stream of bytes or messages. PIPE_READMODE_MESSAGE
requires PIPE_TYPE_MESSAGE
.
• PIPE_WAIT
and PIPE_NOWAIT
determine whether ReadFile
will block. Use PIPE_WAIT
because there are better ways to achieve asynchronous I/O.
nMaxInstances
determines the maximum number of pipe instances. As Figure 11-2 shows, use this same value for every CreateNamedPipe
call for a given pipe. Use the value PIPE_UNLIMITED_INSTANCES
to have Windows base the number on available computer resources.
nOutBufferSize
and nInBufferSize
give the sizes, in bytes, of the input and output buffers used for the named pipes. Specify 0
to get default values.
nDefaultTimeOut
is a default time-out period (in milliseconds) for the WaitNamedPipe
function, which is discussed in an upcoming section. This situation, in which the create function specifies a time-out for a related function, is unique.
The error return value is INVALID_HANDLE_VALUE
because pipe handles are similar to file handles.
lpSecurityAttributes
operates as in all the other create functions.
The first CreateNamedPipe
call actually creates the named pipe and an instance. Closing the last handle to an instance will delete the instance (usually, there is only one handle per instance). Closing the last instance of a named pipe will delete the pipe, making the pipe name available for reuse.
Figure 11-2 shows that a client connects to a named pipe using CreateFile
with the pipe name. In many cases, the client and server are on the same machine, and the name would take this form:
\.pipe[path]pipename
If the server is on a different machine, the name would take this form:
\servernamepipepipename
Using the name period (.
) when the server is local—rather than using the local machine name—delivers significantly better connection-time performance.
There are seven functions to interrogate pipe status information, and an eighth sets state information. They are mentioned briefly, and Program 11-3 demonstrates several of the functions.
• GetNamedPipeHandleState
returns information, given an open handle, on whether the pipe is in blocking or nonblocking mode, whether it is message-oriented or byte-oriented, the number of pipe instances, and so on.
• SetNamedPipeHandleState
allows the program to set the same state attributes. The mode and other values are passed by address rather than by value, which is necessary so that a NULL
value specifies that the mode should not be changed. See the full Examples code of Program 11-2 for an example.
• GetNamedPipeInfo
determines whether the handle is for a client or server instance, the buffer sizes, and so on.
• Five functions get information about the client name and the client and server session ID and process ID. Representative names are GetNamedPipeClientSessionId
and GetNamedPipeServerProcessId
.
The server, after creating a named pipe instance, can wait for a client connection (CreateFile
or CallNamedPipe
, described in a subsequent function) using ConnectNamedPipe
.
With lpOverlapped
set to NULL
, ConnectNamedPipe
will return as soon as there is a client connection. Normally, the return value is TRUE
. However, it would be FALSE
if the client connected between the server’s CreateNamedPipe
call and the ConnectNamedPipe
call. In this case, GetLastError
returns ERROR_PIPE_CONNECTED
, and the connection is valid despite the FALSE
return value.
Following the return from ConnectNamedPipe
, the server can read requests using ReadFile
and write responses using WriteFile
. Finally, the server should call DisconnectNamedPipe
to free the handle (pipe instance) for connection with another client.
WaitNamedPipe
, the final function, is for use by the client to synchronize connections to the server. The call will return successfully as soon as the server has a pending ConnectNamedPipe
call. By using WaitNamedPipe
, the client can be certain that the server is ready for a connection and the client can then call CreateFile
. Nonetheless, the client’s CreateFile
call could fail if some other client opens the named pipe using CreateFile
or if the server closes the instance handle; that is, there is a race involving the server and the clients. The server’s ConnectNamedPip
e call will not fail. Notice that there is a time-out period for WaitNamedPipe
that, if specified, will override the time-out period specified with the server’s CreateNamedPipe
call.
The proper connection sequences for the client and server are as follows. First is the server sequence, in which the server makes a client connection, communicates with the client until the client disconnects (causing ReadFile
to return FALSE
), disconnects the server-side connection, and then connects to another client.
The client connection sequence is as follows, where the client terminates after it finishes, allowing another client to connect on the same named pipe instance. As shown, the client can connect to a networked server if it knows the server name.
Notice the race conditions between the client and the server. First, the client’s WaitNamedPipe
call will fail if the server has not yet created the named pipe; the failure test is omitted for brevity but is included in the sample programs in the Examples file. Next, the client may, in rare circumstances, complete its CreateFile
call before the server calls ConnectNamedPipe
. In that case, ConnectNamedPipe
will return FALSE
to the server, but the named pipe communication will still function properly.
The named pipe instance is a global resource, so once the client disconnects, another client can connect with the server.
Figure 11-2 shows a typical client configuration in which the client does the following:
• Opens an instance of the pipe, creating a long-lived connection to the server and consuming a pipe instance
• Repetitively sends requests and waits for responses
• Closes the connection
The common WriteFile
, ReadFile
sequence could be regarded as a single client transaction, and Windows provides such a function for message pipes.
The parameter usage is clear because this function combines WriteFile
and ReadFile
on the named pipe handle. Both the output and input buffers are specified, and *lpcbRead
receives the message length. Overlapped operations (Chapter 14) are possible. More typically, the function waits for the response.
TransactNamedPipe
is convenient, but, as in Figure 11-2, it requires a permanent connection, which limits the number of clients.3
3 Note that TransactNamedPipe
is more than a mere convenience compared with WriteFile
and ReadFile
and can provide some performance advantages. One experiment shows throughput enhancements ranging from 57% (small messages) to 24% (large messages).
CallNamedPipe
is the second client convenience function:
CallNamedPipe
does not require a permanent connection; instead, it makes a temporary connection by combining the following complete sequence:
into a single function. The benefit is that clients do not have long-lived connections, and the server can service more clients at the cost of per-request connection overhead.
The parameter usage is similar to that of TransactNamedPipe
except that a pipe name, rather than a handle, specifies the pipe. CallNamedPipe
is synchronous (there is no overlapped structure). It specifies a time-out period, in milliseconds, for the connection but not for the transaction. There are three special values for dwTimeOut
:
• NMPWAIT_NOWAIT
• NMPWAIT_WAIT_FOREVER
• NMPWAIT_USE_DEFAULT_WAIT
, which uses the default time-out period specified by CreateNamedPipe
In addition to reading a named pipe using ReadFile
, you can also determine whether there is actually a message to read using PeekNamedPipe
. This is useful to poll the named pipe (an inefficient operation), determine the message length so as to allocate a buffer before reading, or look at the incoming data so as to prioritize its processing.
PeekNamedPipe
nondestructively reads any bytes or messages in the pipe, but it does not block; it returns immediately.
Test *lpcbAvail
to determine whether there is data in the pipe; if there is, *lpcbAvail
will be greater than 0
. lpBuffer
and lpcbRead
can be NULL
, but if you need to look at the data, call PeekNamedPipe
a second time with a buffer and count large enough to receive the data (based on the *lpcbAvail
value). If a buffer is specified with lpBuffer
and cbBuffer
, then *lpcbMessage
will tell whether there are leftover message bytes that could not fit into the buffer, allowing you to allocate a large buffer before reading from the named pipe. This value is 0
for a byte mode pipe.
Again, PeekNamedPipe
reads nondestructively, so a subsequent ReadFile
is required to remove messages or bytes from the pipe.
Everything required to build a request/response client/server system is now available. This example is a command line server that executes a command on behalf of the client. Features of the system include:
• Multiple clients can interact with the server.
• The clients can be on different systems on the network, although the clients can also be on the server machine.
• The server is multithreaded, with a thread dedicated to each named pipe instance. That is, there is a thread pool of worker threads4 ready for use by connecting clients. Worker threads are allocated to a client on the basis of the named pipe instance that the system allocates to the client.
4 This application-managed thread pool is different from the NT6 thread pool (see Chapter 10).
• The individual server threads process a single request at a time, simplifying concurrency control. Each thread handles its own requests independently. Nonetheless, exercise the normal precautions if different server threads are accessing the same file or other resource.
Program 11-2 shows the single-threaded client, and its server is Program 11-3. The server corresponds to the model in Figures 7-1 and 11-2. The client request is simply the command line. The server response is the resulting output, which is sent in several messages. The programs also use the include file ClientServer.h
, which is included in the Examples file, and defines the request and response data structures as well as the client and server pipe names.
The client in Program 11-2 also calls a function, LocateServer
, which finds a server pipe by name. LocateServer
uses a mailslot, described in a later section and shown in Program 11-5.
The defined records have DWORD32
length fields; this is done to emphasize the field size.
Program 11-3 is the server program, including the server thread function, that processes the requests from Program 11-2. The server also creates a “server broadcast” thread (see Program 11-4) to broadcast its pipe name on a mailslot to clients that want to connect. Program 11-2 calls the LocateServer
function, shown in Program 11-5, which reads the information sent by this process. Mailslots are described later in this chapter.
While the code is omitted in Program 11-4, the server (in the Examples file) optionally secures its named pipe to prevent access by unauthorized clients. Chapter 15 describes object security and how to use this option. Also, see the example for the server process shutdown logic.
This solution includes a number of features as well as limitations that will be addressed in later chapters.
• Multiple client processes can connect with the server and perform concurrent requests; each client has a dedicated server (or worker) thread allocated from the thread pool.
• The server and clients can run from separate command prompts or can run under control of JobShell
(Program 6-3).
• If all the named pipe instances are in use when a client attempts to connect, the new client will wait until a different client disconnects on receiving a $Quit
command, making a pipe instance available for another client. Several new clients may be attempting to connect concurrently and will race to open the available instance; threads that lose this race will need to wait again.
• Each server thread performs synchronous I/O, but some server threads can be processing requests while others are waiting for connections or client requests.
• Extension to networked clients is straightforward, subject to the limitations of named pipes discussed earlier in this chapter. Simply change the pipe names in the header file or add a client command line parameter for the server name.
• Each server worker thread creates a simple connection thread, which calls ConnectNamedPipe
and terminates as soon as a client connects. This allows a worker thread to wait, with a time-out, on the connection thread handle and test the global shutdown flag periodically. If the worker threads blocked on ConnectNamedPipe
, they could not test the flag and the server could not shut down. For this reason, the server thread performs a CreateFile
on the named pipe in order to force the connection thread to resume and shut down. Asynchronous I/O (Chapter 14) is an alternative, so that an event could be associated with the ConnectNamedPipe
call. The comments in the Examples file source provide additional alternatives and information. Without this solution, connection threads might never terminate by themselves, resulting in resource leaks. Chapter 12 discusses this subject.
• There are a number of opportunities to enhance the system. For example, there could be an option to execute an in-process server by using a DLL that implements some of the commands. This enhancement is added in Chapter 12.
• The number of server threads is limited by the WaitForMultipleObjects
call in the main thread. While this limitation is easily overcome, the system here is not truly scalable; too many threads will impair performance, as we saw in Chapter 10. Chapter 14 uses asynchronous I/O ports to address this issue.
The details of how clients locate servers are explained in the next section (“Mailslots”). However, we can now show the programs in operation. Run 11-3 shows the server, Program 11-3, which was started using JobShell
from Chapter 6. The server accepts connections from three client processes, reporting the connections and the commands.
Run 11-4 shows one of the clients in operation; this is the client represented by process ID 15872 in Run 11-3. The commands are familiar from previous chapters.
A Windows mailslot, like a named pipe, has a name that unrelated processes can use for communication. Mailslots are a broadcast mechanism, similar to datagrams (see Chapter 12), and behave differently from named pipes, making them useful in some important but limited situations. Here are the significant mailslot characteristics:
• A mailslot is one-directional.
• A mailslot can have multiple writers and multiple readers, but frequently it will be one-to-many of one form or the other.
• A writer (client) does not know for certain that all, some, or any readers (servers) actually received the message.
• Mailslots can be located over a network domain.
• Message lengths are limited.
Using a mailslot requires the following operations.
• Each server creates a mailslot handle with CreateMailslot
.
• The server then waits to receive a mailslot message with a ReadFile
call.
• A write-only client should open the mailslot with CreateFile
and write messages with WriteFile
. The open will fail (name not found) if there are no waiting readers.
A client’s message can be read by all servers; all of them receive the same message.
There is one further possibility. The client, in performing the CreateFile
, can specify a name of this form:
\*mailslotmailslotname
In this way, the *
acts as a wildcard, and the client can locate every server in the domain, a networked group of systems assigned a common name by the network administrator. The client can then connect to one of the servers, assuming that they all provide the same basic functionality, although the server responses could contain information (current load, performance, etc.) that would influence the client’s choice.
The preceding client/server command processor suggests several ways that mailslots might be useful. Here is one scenario that will solve the server location problem in the preceding client/server system (Programs 11-2 and 11-3).
The application server, acting as a mailslot client, periodically broadcasts its name and a named pipe name. Any application client that wants to find a server can receive this name by being a mailslot server. In a similar manner, the command line server can periodically broadcast its status, including information such as utilization, to the clients. This situation could be described as a single writer (the mailslot client) and multiple readers (the mailslot servers). If there were multiple mailslot clients (that is, multiple application servers), there would be a many-to-many situation.
Alternatively, a single reader could receive messages from numerous writers, perhaps giving their status—that is, there would be multiple writers and a single reader. This usage, for example, in a bulletin board application, justifies the term mailslot. These first two uses—name and status broadcast—can be combined so that a client can select the most appropriate server.
The inversion of the terms client and server is confusing in this context, but notice that both named pipe and mailslot servers perform the CreateNamedPipe
(or CreateMailSlot)
calls, while the client (named pipe or mailslot) connects using CreateFile
. Also, in both cases, the client performs the first WriteFile
and the server performs the first ReadFile
.
Figure 11-3 shows the use of mailslots for the first approach.
The mailslot servers (readers) use CreateMailslot
to create a mailslot and to get a handle for use with ReadFile
. There can be only one mailslot of a given name on a specific machine, but several systems in a network can use the same name to take advantage of mailslots in a multireader situation.
lpName
points to a mailslot name of this form:
\.mailslot[path]name
The name must be unique. The period (.
) indicates that the mailslot is created on the current machine. The path, if any, represents a pseudo directory, and path components are separated by backslash characters.
cbMaxMsg
is the maximum size (in bytes) for messages that a client can write. A value of 0
means no limit.
dwReadTimeout
is the number of milliseconds that a read operation will wait. A value of 0
causes an immediate return, and MAILSLOT_WAIT_FOREVER
is an infinite wait (no time-out).
The client (writer), when opening a mailslot with CreateFile
, can use the following name forms.
\.mailslot[path]name specifies a local mailslot.
\computernamemailslot[path]name specifies a mailslot on a specified machine.
\domainnamemailslot[path]name specifies all mailslots on machines in the domain. In this case, the maximum message length is 424 bytes.
\*mailslot[path]name specifies all mailslots on machines in the domain. In this case, the maximum message length is also 424 bytes.
Finally, the client must specify the FILE_SHARE_READ
flag.
The functions GetMailslotInfo
and SetMailslotInfo
are similar to their named pipe counterparts.
Table 11-1 summarizes the valid pipe names that can be used by application clients and servers. It also summarizes the functions to create and connect with named pipes.
Table 11-2 gives similar information for mailslots. Recall that the mailslot client (or server) may not be the same process or even on the same computer as the application client (or server).
Program 11-4 shows the thread function that the command line server (Program 11-3), acting as a mailslot client, uses to broadcast its pipe name to waiting clients. There can be multiple servers with different characteristics and pipe names, and the clients obtain their names from the well-known mailslot name. Program 11-3 starts this function as a thread.
Note: In practice, many client/server systems invert the location logic used here. The alternative is to have the application client also act as the mailslot client and broadcast a message requesting a server to respond on a specified named pipe; the client determines the pipe name and includes that name in the message. The application server, acting as a mailslot server, then reads the request and creates a connection on the specified named pipe.
Program 11-4’s inverted logic solution has advantages, although it consumes a mailslot name:
• The latency time to discover a server decreases because there is no need to wait for a server to broadcast its name.
• Network bandwidth and CPU cycles are used only as required when a client needs to discover a server.
Program 11-5 shows the LocSrver
function called by the client (see Program 11-2) so that it can locate the server.
Windows pipes and mailslots, which are accessed with file I/O operations, provide stream-oriented interprocess and networked communication. The examples show how to pipe data from one process to another and a simple, multithreaded client/server system. Pipes also provide another thread synchronization method because a reading thread blocks until another thread writes to the pipe.
Chapter 12 shows how to use industry-standard, rather than Windows proprietary, interprocess and networking communication. The same client/server system, with some server enhancements, will be rewritten to use the standard methods.
11–1. Carry out experiments to determine the accuracy of the performance advantages cited for TransactNamedPipe
. You will need to make some changes to the server code as given. Also compare the results with the current implementation.
11–2. Use the JobShell
program from Chapter 6 to start the server and several clients, where each client is created using the “detached” option. Eventually, shut down the server by sending a console control event through the kill
command. Can you suggest any improvements to the serverNP
shutdown logic so that a connected server thread can test the shutdown flag while blocked waiting for a client request? Hint: Create a read thread similar to the connection thread.
11–3. Enhance the server so that the name of its named pipe is an argument on the command line. Bring up multiple server processes with different pipe names using the job management programs in Chapter 6. Verify that multiple clients simultaneously access this multiprocess server system.
11–4. Run the client and server on different systems to confirm correct network operation. Modify SrvrBcst
(Program 11-4) so that it includes the server machine name in the named pipe. Also, modify the mailslot name, currently hard-coded in Program 11-4, so that the name is taken from the mailslot response from the application server.
11–5. Modify the server so that you measure the server’s utilization. (In other words, what percentage of elapsed time is spent in the server?) Maintain performance information and report this information to the client on request. Consider using the Request.Command
field to hold the information.
11–6. Enhance the server location programs so that the client will find the server with the lowest utilization rate.
11–7. serverNP
is designed to run indefinitely as a server, allowing clients to connect, obtain services, and disconnect. When a client disconnects, it is important for the server to free all associated resources, such as memory, file handles, and thread handles. Any remaining resource leaks will ultimately exhaust computer resources, causing the server to fail, and before failure there will probably be significant performance degradation. Carefully examine serverNP
to ensure that there are no resource leaks, and, if you find any, fix them. (Also, please inform the author using the e-mail address in the preface.) Note: Resource leaks are a common and serious defect in many production systems. No “industry-strength” quality assurance effort is complete if it has not addressed this issue.
11–8. Extended exercise: Synchronization objects can synchronize threads in different processes on the same machine, but they cannot synchronize threads running in processes on different machines. Use named pipes and mailslots to create emulated mutexes, events, and semaphores to overcome this limitation.