February 16, 2019
Hot Topics:

Developing Efficient Network and Distributed Applications with ACE, Part 2

  • November 10, 2004
  • By Mugdha Vairagade
  • Send Email »
  • More Articles »
The first installment of this article series introduced and took an overview of Adaptive Communication Environment (ACE) framework. Now that we are familiar with the structure of ACE framework and its major components, we can move towards understanding how ACE works under the hood and how it can be implemented to develop efficient communication software.

This article explores the ACE framework components necessary for application development and how they work in tandem to provide flexible, efficient, reliable, and portable communication solutions for application. This article also contains an example of a simple distributed application developed using ACE, comprised of a blog server and a desktop-based blogging client, to help explain the implementation and working of ACE components, along with the communication taking place between them.

Note: The example application, which is GUI-based, presented in this article was built and tested using MS VC++ and MFC on a Windows 2000 platform. The GUI, being OS-dependent and in this particular case slightly tight-coupled with the application layer, is not portable; yet the underlying implementation of ACE is portable. Readers can model their own GUI-based applications on the example for different platforms while using the ACE implementation code as it is, thanks to the high portability that ACE provides.

How ACE Works

Before we take a look at the example application, we need to understand how communication between components of an ACE-based distributed application takes place.

Communication in a distributed application that implements ACE follows the usual client/server model (refined into a new Acceptor-Connector pattern by ACE). It involves an active client that initiates a communication and requests for a connection for data exchange; and a passive server that waits for a needy client to initiate communication and request for a connection to arrive. Upon arrival of such a request(s), the server accepts the request by establishing a connection and exchanges data with the client. Here, both the client and the server are components of the distributed application, between whom the communication takes place.

Note: ACE allows the reversal of client and server roles anytime during communication, once a connection between the two is established. Therefore, the server can become a client and request information from the client-turned-server and vise-versa.

The ACE framework contains two useful components, namely Acceptor and Connector, that carry out the communication for the client and server. ACE also provides other useful components, such as Dispatcher and Service Handler, to aid different aspects of this communication.

Connector and Acceptor both are factories. Connector resides on the client side (of the application), while Acceptor lies on the server side, and there is one singleton Dispatcher each for both client and server.

Connector initializes a connection by actively sending out connection requests to a passive Acceptor, which is listening on a designated port for a connection request to arrive. Connector can initialize connections both synchronously and asynchronously, depending on the application's requirement.

Both Connector and Acceptor are registered with Dispatchers on their own sides. Depending on the type of connection establishment used by the Connector, the appropriate type of Dispatcher is used:

  • Reactor for synchronously established connections
  • Proactor for asynchronously established connections

Here, we'll explore Reactor because our example application requires a simple synchronous connection establishment.

The Reactor watches out for relevant events occurring; in other words, incoming connection requests and confirmation of connection establishment. When the former event occurs, Reactor informs Acceptor. Acceptor in turn accepts the requests, establishes a connection with Connector, and creates a Service Handler to handle the processing of data exchanged over the connection.

Meanwhile, the Reactor at the client side informs the Connector of confirmation that the connection was established. Now, Connector also initializes the appropriate Service Handler on its own side. Then, the Service Handlers take over and exchange data with their corresponding remote peer Service Handler. When the data exchange is finished on a connection, the peer Service Handlers are shut down and any resources involved in the data exchange or processing are released. Figure 1 explains how ACE components work.

Figure 1: Working of ACE components

ACE at Work: Example Application

To build applications, ACE can be downloaded for free from the ACE homepage. This application uses version 5.4 of ACE. After downloading the compressed source of ACE for the appropriate platform (here, ACE-5.4.zip), the source can be uncompressed, compiled, and thus installed on the desired machine.

As described earlier, the example application consists of a blog server and a desktop-based blog client (analogous an to an e-mail client such as MS Outlook Express), both of which may or may not reside on different machines. Both the client and the server are GUI based. The complete source code of the application and its executable files are available for download.

Figure 2: The blog server's GUI

First, executing blogserver.exe starts the server's GUI. In the resulting dialog box, the Start button is clicked to start the server. When started, the server creates two Acceptor objects of type CNewUserAcceptor and CServiceAcceptor respectively and registers them with the singleton Reactor on its own side. These Acceptor objects are assigned to and listen at two different ports.

Code: Source code from file BlogServer.h

#define NEWUSER_PORT   10001
#define SERVICE_PORT   10002

Code: Source code from file BlogServer.cpp

   static const char *const SERVER_HOST = ACE_DEFAULT_SERVER_HOST;
... ... ....
CNewUserAcceptor newUserAcceptor;
CServiceAcceptor srvAcceptor;
if (newUserAcceptor.open (ACE_INET_Addr(NEWUSER_PORT),
    ACE_Reactor::instance()) == -1)
   AfxMessageBox("Can not start new user service");
   return -1;
if (srvAcceptor.open (ACE_INET_Addr(SERVICE_PORT),
    ACE_Reactor::instance()) == -1)
   AfxMessageBox("Can not start blog service");
   return -1;
... ... ... ...

Now, the Reactor constantly watches out for a relevant connection request (sent by Connectors on the client side) events to occur and notifies the appropriate Acceptor object about it.

Code: Source code from file BlogServer.cpp

//The application calls the handle_events() function to
//continuously watch the events in a loop.
      ACE_Reactor::instance()->handle_events ();

Now, the blogclient.exe is executed to start the client application, which in turn starts the event-handling loop of the client-side Reactor.

Code: Source code from file BlogClient.cpp

//The application calls the handle_events() function to
//continuously watch the events in a loop.

The blog client's GUI appears and accepts input. First is a Login dialog box that asks for the username and password and also provides an option for new user registration.

Figure 3: The blog client GUI

Page 1 of 2

Comment and Contribute


(Maximum characters: 1200). You have characters left.



Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Thanks for your registration, follow us on our social networks to keep up-to-date