C++ Network Programming(part 13):: The ACE::select() Methods
The select () function is available on most OS platforms. Yet even this
common function has subtleties that make it harder to use than necessary.
For example, consider how select ( ) is used on page 144. Even though the
most common use case for select () in a reactive server is to wait indefinitely
for input on a set of socket handles, programmers must provide NULL
pointers for the write and exception f d_sets and for the timeout pointer.
Moreover, programmers must remember to call the sync ( ) method on
active_handles_ to reflect changes made by select ( ) . Addressing these
issues in each application can be tedious and error prone, which is why
ACE provides the ACE: : select () wrapper methods.
Method Capabilities
ACE defines overloaded static wrapper methods for the native select ( )
function that simplify its use for the most common cases. These methods
are defined in the utility class ACE as follows:
class ACE {
public:
static int select (int width,
ACE_Handle_Set &rfds,
const ACE Time Value *tv = 0);
static int select (int width,
ACE_Handle_Set *rfds,
ACE_Handle_Set *wfds = 0,
ACE_Handle_Set *efds = 0,
const ACE_Time_Value *tv = 0) ;
// . . . Other methods omitted . . .
The first overloaded select 0 method in class ACE omits certain parameters
and specifies a default value of no time-out value, that is, wait
indefinitely. The second method supplies default values of 0 for the infrequently
used write and exception ACE_Handle_Sets. They both automatically
call ACE_Handle_Set : : sync ( ) when the underlying select ( )
method returns to reset the handle count and size-related values in the
handle set to reflect any changes made by select ( ) .
We devised these wrapper functions by paying attention to design details
and common usages to simplify programming effort and reduce the
chance for errors in application code. The design was motivated by the
following factors:
• Simplify for the common case. As mentioned above, the most common
use case for select ( ) in a reactive server is to wait indefinitely
for input on a set of socket handles. The ACE : : select ( ) methods
simplify this common use case. We discuss this design principle further
in Section A. 3.
• Encapsulate platform variation. All versions of select () accept a
timeout argument; however, only Linux's version modifies the timeout
value on return to reflect how much time was left in the time-out
period when one of the handles was selected. The ACE: : select ( )
wrapper functions declare the timeout const to unambiguously state
its behavior, and include internal code to work around the nonstandard
time-out-modifying extensions on Linux. We discuss this design
principle further in Section A. 5.
• Provide type portability. The ACE_Time_Value class is used instead
of the native timer type for the platform since timer types aren't consistent
across all platforms.
Example
The more useful and portable we make our logging .server, the more client
applications will want to use it and the greater its load will become. We
therefore want to think ahead and design our subsequent logging servers
to avoid becoming a bottleneck. In the next few chapters, we'll explore OS
concurrency mechanisms and their associated ACE wrapper facades. As
our use of concurrency expands, however, the single log record file we've
been using thus far will become a bottleneck since all log records converge
to that file.
In preparation for adding different forms of concurrency therefore, we
extend our latest reactive server example to write log records from different
clients to different log files, one for each connected client. Figure 7.2 illustrates
the potentially more scalable reactive logging server architecture
that builds upon and enhances the two earlier examples in this chapter.
As shown in the figure, this reactive server implementation maintains a
map container that allows a logging server to keep separate log files for
each of its clients. The figure also shows how we use the ACE : : select ( )
wrapper method and the ACE_Handle_Set class to service multiple clients
via a reactive server model.
Our implementation starts by including several new header files that
provide various new capabilities we'll use in our logging server.
include "ace/ACE.h"
include "ace/Handle_Set.h"
include "ace/Hash_Map_Manager.h"
include "ace/Synch.h"
include "Logging_Server.h"
include "Logging_Handler.h"
We next define a type definition of the ACE_Hash_Map_Manager template,
which is explained in Sidebar 15.
typedef ACE_Hash_Map_Manager<ACE_HANDLE,
ACE_FILE_IO *,
ACE_Null_Mutex> LOG_MAP;
We'll use an instance of this template to map an active ACE_HANDLE socket
connection efficiently onto the ACE_FILE_IO object that corresponds to its
log file. By using ACE_HANDLE as the map key, we address an important
portability issue: socket handles on UNIX are small unsigned integers,
whereas on Win32 they are pointers.
We create a new header file named Reactive_Logging_Server_Ex.h
that contains a subclass called Reactive_Logging_Server_Ex, which inherits
from Logging_Server. The main difference between this implementation
and the one in Section 7.2 is that we construct a log_map to
associate active handles to their corresponding ACE_FILE_IO pointers efficiently.
To prevent any doubt that an active handle is a stream socket the
ACE_SOCK_Acceptor isn't added to the log_map.
class Reactive_Logging_Server_Ex : public Logging_Server
{
protected:
// Associate an active handle to an ACE_FILE_IO pointer.
LOG_MAP log_map_;
// Keep track of acceptor socket and all the connected
// stream socket handles.
ACE_Handle_Set master_handle_set_;
// Keep track of read handles marked as active by
ACE_Handle_Set active_read_handles_;
// Other methods shown below...