ACE Tutorial 002
Creating a Better Server

Like Tutorial 1, this is also a rather small program. I'm going to add a couple of new ideas along the way but to make up for it I'm also going to simplify the acceptor a great deal.

Kirthika's Abstract:

This is a server example made simpler due to the use of off-the-shelf components and classes from ACE.

Here, the Logging_Acceptor is an ACE_Acceptor class which is associated with the Logging_Handler and the ACE_SOCK_ACCEPTOR. This will now accept connection requests from the clients on being opened with the reactor instance passed to it.

We also implement a signal to capture CTRL-C [ which generates SIGINT ] using ACE_SigAction and ACE_SignalHandler. This signal can now be used to stop the reactor from handling events.

Then, the reactor is allowed to loop infintely until it is shut down using a ^C, after which both the reactor as well as the acceptor are destroyed.

The Logging_Handler derives from the ACE_Svc_Handler instead of the Event_Handler since the Svc_Handler has inbuilt SOCK_Stream and provides all the calls needed by the reactor. The Svc_Handler has the ability to react to events and communicate to remote tasks using the underlying data stream passed to it.

A timer is scheduled in the reactor which does nothing but simply display how it could be used to provide periodic processing when needed. The ACE_TimeValue is used to set the time period.

Also, optimisations have been made in the form of a separate function for destroying the objects used.

Thus a simpler server has now been built which successfully demonstrates how simple a task, writing a server can become on using the various ACE components judiciously.

We begin by looking at the main (server.cpp) portion program:

    // page02.html,v 1.10 2000/03/19 20:09:20 jcej Exp

    /* As before, we need a few ACE objects as well as our Logging_Handler
      declaration.  */
    #include "ace/Acceptor.h"
    #include "ace/SOCK_Acceptor.h"
    #include "ace/Reactor.h"
    #include "handler.h"

    /* We'll still use the global reactor pointer.  There's a snappy way
       around this that shows up in later server tutorials.  */
    ACE_Reactor *g_reactor;

    /* This was hinted at in Tutorial 1.  Remember the hand-coded acceptor
      that we created there?  This template does all of that and more and
      better.  If you find yourself creating code that doesn't feel like a
      part of your application, there's a good chance that ACE has a
      template or framework component to do it for you.  */
    typedef ACE_Acceptor <Logging_Handler, ACE_SOCK_ACCEPTOR> Logging_Acceptor;

    /* One of the new things will be a signal handler so that we can exit
      the application somewhat cleanly.  The 'finished' flag is used
      instead of the previous infninite loop and the 'handler' will set
      that flag in respose to SIGINT (CTRL-C).
      The invocation of ACE_Reactor::notify() will cause the
      handle_events() to return so that we can see the new value of 'finished'.
    */
    static sig_atomic_t finished = 0;
    extern "C" void handler (int)
    {
      finished = 1;
      g_reactor->notify();
    }

    static const u_short PORT = ACE_DEFAULT_SERVER_PORT;

    int
    main (int, char **)
    {
      // Create the reactor we'll register our event handler derivatives with.
      ACE_NEW_RETURN (g_reactor,
                      ACE_Reactor,
                      1);

      // Create the acceptor that will listen for client connetions
      Logging_Acceptor peer_acceptor;

      /* Notice how similar this is to the open() call in Tutorial 1.  I
        read ahead when I created that one so that it would come out this
        way...  */
      if (peer_acceptor.open (ACE_INET_Addr (PORT),
                              g_reactor) == -1)
        ACE_ERROR_RETURN ((LM_ERROR,
                           "%p\n",
                           "open"),
                          -1);

      /* Here's the easiest way to respond to signals in your application.
        Simply construct an ACE_Sig_Action object with a "C" function and
        the signal you want to capture.  As you might expect, there is
        also a way to register signal handlers with a reactor but we take
        the easy-out here.  */
      ACE_Sig_Action sa ((ACE_SignalHandler) handler, SIGINT);

      ACE_DEBUG ((LM_DEBUG,
                  "(%P|%t) starting up server logging daemon\n"));

      // Perform logging service until the signal handler receives SIGINT.
      while (!finished)
        g_reactor->handle_events ();

      // Close the acceptor so that no more clients will be taken in.
      peer_acceptor.close();

      // Free up the memory allocated for the reactor.
      delete g_reactor;

      ACE_DEBUG ((LM_DEBUG,
                  "(%P|%t) shutting down server logging daemon\n"));
      return 0;
    }

    #if defined (ACE_HAS_EXPLICIT_TEMPLATE_INSTANTIATION)
    template class ACE_Acceptor <Logging_Handler, ACE_SOCK_ACCEPTOR>;
    template class ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_NULL_SYNCH>;
    #elif defined (ACE_HAS_TEMPLATE_INSTANTIATION_PRAGMA)
    #pragma instantiate ACE_Acceptor <Logging_Handler, ACE_SOCK_ACCEPTOR>
    #pragma instantiate ACE_Svc_Handler<ACE_SOCK_STREAM, ACE_NULL_SYNCH>
    #endif /* ACE_HAS_EXPLICIT_TEMPLATE_INSTANTIATION */

[Tutorial Index] [Continue This Tutorial]