The ACE::select() Methods





The ACE::select() Methods

Motivation

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 fd_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() 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 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.

2. Architecture of a Reactive Logging Server

graphics/07fig02.gif

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 <select>.
  ACE_Handle_Set active_read_handles_;

  // Other methods shown below...
};

Sidebar 15: The ACE Container Classes

ACE provides a suite of containers classes, Including

  • Singly and doubly linked lists

  • Sets and multisets

  • Stacks and queues

  • Dynamic arrays

  • String manipulation classes

Where possible, these classes are modeled after the C++ standard library classes so that it's easy to switch between them as C++ compilers mature,

The ACE_Hash_Map_Manager defines a set abstraction that associates keys to values efficiently. We use this class instead of the "standard" std::map [Aus98] for several reasons:

  1. The std::map isn't so standard—not all compilers that ACE works with Implement it, and those that do don't all implement its interface the same way.

  2. The ACE_Hash_Map_Manager performs efficient lookups based on hashing, something std::map doesn't yet support.

More coverage of the ACE container classes appears in [HJS].

The open() hook method simply performs the steps necessary to initialize the reactive server.

virtual int open (u_short port) {
  Logging_Server::open (port);
  master_handle_set_.set_bit (acceptor ().get_handle ());
  acceptor ().enable (ACE_NONBLOCK);
  return 0;
}

The wait_for_multiple_events() hook method in this reactive server is similar to the one in Section 7.2. In this method, though, we call ACE::select(), which is a static wrapper method in ACE that provides default arguments for the less frequently used parameters to the select() function.

virtual int wait_for_multiple_events () {
  active_read_handles_ = master_handle_set_;
  int width = (int) active_read_handles_.max_set () + 1;

  return ACE::select (width, active_read_handles_);
}

The handle_connections() hook method implementation is similar to the one in Reactive_Logging_Server. We accept new connections and update the log_map_ and master_handle_set_.

virtual int handle_connections () {
  ACE_SOCK_Stream logging_peer;

  while (acceptor ().accept (logging_peer) != -1) {
    master_handle_set_.set_bit (logging_peer.get_handle ());

    ACE_FILE_IO *log_file = new ACE_FILE_IO;

    // Use the client' s hostname as the logfile name.
    make_log_file (*log_file, &logging_peer);

    // Add the new <logging_peer>'s handle to the map and
    // to the set of handles we <select> for input.
    log_map_.bind (logging_peer.get_handle (), log_file);
    master_handle_set_.set_bit (logging_peer.get_handle ());
  }
  return 0;
}

Note that we use the make_log_file() method (see page 85) inherited from the Logging_Server base class described in Section 4.4.1.

The handle_data() hook method iterates over only the active connections, receives a log record from each, and writes the record to the log file associated with the client connection.

virtual int handle_data (ACE_SOCK_Stream *) {
  ACE_Handle_Set_Iterator peer_iterator (active_read_handles_);

  for (ACE_HANDLE handle;
       (handle = peer_iterator ()) != ACE_INVALID_HANDLE;
       ) {
    ACE_FILE_IO *log_file;

    log_map_.find (handle, log_file);
    Logging_Handler logging_handler (*log_file, handle);
    if (logging_handler.log_record () == -1) {
      logging_handler.close ();
      master_handle_set_.clr_bit (handle);
      log_map_.unbind (handle);
      log_file->close ();
      delete log file; }
    }
  }
  return 0; }
}

When a client closes its connection, we close the corresponding Logging_ Handler, clear the handle from the master_handle_set_, remove the handle-to-file association from the log_map_, and delete the dynamically allocated ACE_FILE_IO object. Although we don't show much error handling code in this example, a production implementation should take appropriate corrective action if failures occur.

Finally, we show the main() program, which is essentially identical to the ones we showed earlier, except that this time we define an instance of Reactive_Logging_Server_Ex.

int main (int argc, char *argv[])
{
  Reactive_Logging_Server_Ex server;

  if (server.run (argc, argv) == -1)
    ACE_ERROR_RETURN ((LM_ERROR, "%p\n", "server.run()"), 1);
  return 0;
}

     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows