Reliable Transaction Router
C++ Foundation Classes


Previous Contents Index


Chapter 5
Sample Application Tutorial

5.1 Purpose

This tutorial goes through all of the steps needed to set up a simple RTR C++ API-based application for a new developer. The intent is to provide a starting point for learning about RTR, and to simplify the main concepts of RTR; you will be able to cruise through this at a more rapid pace than you normally would with the RTR reference information. At the end of this tutorial, you'll find brief descriptions of some of the more complex features RTR provides, and pointers to the documentation where you can study them in detail.

5.2 Summary

This tutorial walks you through designing, coding and setting up a basic RTR- based client-server application. To do this, you'll use RTR to perform two important services for you:

In the system that you are about to develop, the client application interacts with the user to read and display data. The server application handles requests from the client, and sends replies back to it. When we refer to `client' and `server', we will be referring to the applications. When we refer to the computer nodes on which the client or server is executing, we will call them `frontend' and `backend' nodes, respectively.

In most applications, the server would probably talk to a database in order to retrieve or save data according to what a user had entered in the user- interface. In the interest of simplifying this tutorial, however, this server is only going to tell you whether it received your client's request.

What is different in this system from a non-RTR system is that there will be two servers: one of the servers, also known as the `primary server', almost always talks with the client. In a perfect world, nothing would ever happen to this server; clients would always get the information they asked for, and all changes would be made to the database when the user updated information. Every time anyone attempted to access this server, it would always be there, ready and waiting to `serve', and users could feel secure in the knowledge that the data in the database was changed exactly as they had requested.

But we're all well aware that this is not always the case, and when servers do go down, it's usually at the most inopportune time. So you are going to use RTR to designate a second server as a "standby" server. In this way, if a user is attempting to get some real work done, and the primary server is down, the user will never notice. The standby server will spring into action, and replace the original server by handling the user's requests in just the same way as the primary server had been doing. And, this will be done from the same point at which the primary server had crashed!

Materials List

In order to fully develop this system, you need a client application and frontend node, a server application and two backend nodes, and a router.

Frontend

The frontend node is the system on which your client application is executing. As in any client-server system, the client application interacts with the user, then conveys the user's requests to the server. When developing an RTR-based client-server system, your client will have the following characteristics:

Example code for the client application and the server application can be found in the `examples' subdirectory of your RTR installation directory.

Backend1

Your first backend node will be running the primary server application. It, too, can be on any of the above operating systems, except the Windows system must be NT. It also must have RTR installed on it, and will contain your server application. Your server application will use RTR to listen for requests from the client, receive and handle those requests, and return the result with a message to the client.

Backend2

This machine will run the standby server application. It will probably also be doing any one of a number of other things that have nothing to do with this tutorial, or even with RTR. It most probably will be sitting on one of your co-workers' desks, helping him or her to earn their weekly salary and support their family. Hopefully, you get along with this coworker well enough that they will install RTR on their machine, so that you may complete this tutorial.

Router

Your router is simply RTR software which keeps track of everything that is going on for you when your application is running. The router can execute on a separate machine, on a frontend machine, or on a backend machine. In this tutorial, the router is kept on the same machine as the client.

Install RTR

Your first step, once you have determined the three computers you are going to use for this tutorial, is to be sure RTR is installed and configured on each machine. The RTR installation is well documented and straightforward, although slightly different for each operating system on which the installation is being run. Refer to the section in the RTR Installation Guide for the system on which you are installing RTR. For the purpose of documenting examples, the machine you have decided to use for the:

Remember that the router is on the FE machine. The journal must be accessible to both backend servers. (This requires clusters, NFS or Windows share are not supported)

Start RTR

You need to start RTR on each of the machines on which you have installed it. You may do this from one machine. In order to be able to issue commands to RTR on a remote node, however, you must have an account on that node with the necessary access privileges. The operating system's documentation, or your system manager, will have information on how to set up privileges to enable users to run applications over the network. Use the command interface on your system to interact with RTR. At the command prompt, type in RTR, and press the Return or Enter key. You are then at the RTR> prompt, and can start RTR on all of the nodes. (Start RTR and create facilities independently on separate nodes.) For example, on a UNIX system, it looks like this:


    %  rtr 
    RTR> start rtr/node=(FE,BE1,BE2) 
    RTR> exit 

This command starts `services' or `daemons' on each of the nodes in the list. These are processes that listen for messages being sent by other RTR services or daemons over the network. After executing the command, a `ps', `show process' or Task Manager review of processes executing on your system should now show at least one process named `rtr' or `rtr.exe' on each of the machines. This process is the one that manages the communications between the nodes in the RTR-based application, and handles all transactions and recoveries.

Starting RTR can also be done programmatically.

Create a Recovery Journal

This step holds the key to letting the second server pick up on the work at exactly the right time through a recovery journal. In the case of a failure, the secondary server ensures that no work is lost, and the hot swap to the standby server is automatic. RTR keeps track of the work being done by writing data to the recovery journal. If a failure occurs, all incomplete transactions are being kept track of here, and can be replayed by the standby server when it comes to the rescue. When transactions have been completed, they are removed from this journal. For this example, only your backend nodes need a recovery journal, and you must create the journal before creating your facility; you'll learn more about facilities in the next section. You'll now need to go to each of the backend nodes that you'll be using and create a journal there. Log into each machine and, using the command prompt interface, run RTR and create the journal. When you specify the location of the journal, it should be the disk name or share name where the journal will be located. The journal must be accessible by both of the backend servers.

This is an example of what the command would look like on an OpenVMS system.


    $  RTR 
    RTR> create journal user2 
    RTR> exit 

To allow both servers to access the journal, you have a number of options:

In any case, you should be sure the disk is not on your primary server, since this is the machine that we are protecting, in case of a crash. If the machine goes down, the standby server would not be able to access the disk.

The Database

While we are having this discussion on sharing resources, we should also mention how a database fits into this system, as well. This tutorial and the example code provided with it does not do database transactions. However, there are likely places in the code where you would probably want to access the database in most applications. Because the standby server steps into place when the primary server crashes, each must have access to your database.

This configuration can be supplied using a number of options:

Create a Facility

There can be numerous RTR applications running on any of your computers in your network. The systems or nodes that service one RTR application and the role of each must be clearly defined. This makes the RTR daemons and processes aware of who is talking with whom, and why. The description of a configuration of a group of nodes into frontends, backends and routers is called a facility. To create a facility, use your command prompt utility again and type `RTR'; at the RTR> prompt, create the facility for this example with the following command on a Windows system in the DOS command prompt window:


    C:\>  rtr 
    RTR>  create facility RTRTutor/node=(FE,BE1,BE2) - 
    _RTR> /frontend=FE/router=FE/backend=(BE1,BE2) 
    RTR>  exit 

(You can also repeat this command separately on all three nodes rather than using remote commands.)

With this command, you have now:

You can create a facility programmatically as follows:


    rtr_sStatus_t sStatus; 
    RTRFacilityManager FacilityManager; 
    char nodename[ABCMAX_STRING_LEN] = "kenmare"; 
    // gethostname(&nodename[0],ABCMAX_STRING_LEN); 
    sStatus = FacilityManager.CreateFacility(ABCFacility, 
                                             nodename, 
                                             nodename, 
                                             nodename, 
                                             true, 
                                             false); 
    print_sStatus_on_failure(sStatus); 
    return sStatus; 

Take a Break

At this point you have accomplished a lot; you've configured RTR to protect a multi-tiered application by providing failover capability, and to handle communications between your client and your server. Next, you write the application for your client to talk to RTR, and your server to talk to RTR. RTR delivers the messages between the client and server and, if the server crashes, brings in the standby server to handle your client's requests. The client never knows that the server has been switched, and no data or requests to retrieve or modify data is lost.

Sample Application Code

The C++ modules and header files for this sample application are located in the `examples' subdirectory of the directory into which you installed RTR. They consist of the following files:

Although you won't have much typing to do, this tutorial explains what the code in each file is doing. Copy all of these files into a working directory of your own. For convenience, you may also wish to copy rtrapi.h from the RTR installation directory into your working directory as well.

The example code you'll run must reference the facility you created earlier, so edit the example file headerfilename.h and change the FACILITY value to "RTRTutor".

The sample application code supplied with RTR has a lot going on inside of it, but can be broken down into a few general and very simple concepts that will give you an idea of the power of RTR, and how to make it work for you. As you see, you have code for the client application and the server application. Each application talks only to RTR. RTR moves the messages and data between the client and sample applications. This frees you from the worrying about:

Aren't you relieved? Maybe you should take another break to celebrate!

Client Application

The files shipped with the RTR kit used in the client application for this tutorial are ABCOrderTaker.h and ABCOrderTaker.cpp. and all of the common files. All applications that wish to talk to RTR through its C++ API need to include `rtrapi.h' as a header file. This file lives in the directory into which RTR was installed, and contains the definitions for RTR classes and values that you'll need to reference in your application. Please do not modify this file. Always create your own application header file to include, as we did in the sample (ABCCommon.h) whenever you need additional definitions for your application.


    #include "ABCCommon.h" 
    #include "rtrapi.h" 

The client application design follows this outline:

  1. Initialize RTR
  2. Send a message to the server
  3. Send a second message to the server
  4. Get a response from the server
  5. Decide what to do with the response

The messages the client sends are for book orders and magazine orders. These orders are implemented as ABCBookOrder and ABCMagazineOrder data objects.

Initialize RTR Client Application

This is the first thing that every RTR client application needs to do: tell RTR that it wants to get a facility up and running, and to talk with the server. You find this happening in the (RegisterFacility method in the RTRClientTransactionController class. In the sample application, the implementation for this is in private methods, Initialize and Register, of the ABCOrderTaker class, which derives from RTRClientTransactionController. You remember from the `Start RTR' step in this tutorial that there are RTR daemons or processes executing on the nodes in a facility, listening for communications from other RTR components and applications. Your client application is going to request that all processes associated with the RTRTutor facility "listen up." To do this, you create a client transaction controller and then register a facility in order to enable communication between the client transaction controller and the RTR router. Remember that the RTR router has been described as "keeping track of everything" that goes on in an RTR application.

Create an RTRClientTransactionController object:


    ABCOrderTaker::ABCOrderTaker():m_bRegistered(false) 
    { 
    } 

First, register with RTR if the client hasn't already done so:


    rtr_sStatus_t ABCOrderTaker::Register() 
    { 
    rtr_sStatus_t sStatus = RTR_STS_OK; 
    if(false == m_bRegistered) 
      { 
        // If RTR is not already started then start it now. 
           sStatus = StartRTR();    
        // Create a Facility if not already created. 
           sStatus = CreateFacility(); 

Register the facility with RTR:


    sStatus = RegisterFacility(ABCFacility); 
    print_sStatus_on_failure(sStatus); 
    if (RTR_STS_OK == sStatus) 
      { 
        m_bRegistered = true; 

The transaction controller represents the means of communication from the client to the rest of the components in this system. There is a lot going on here to make the communication work, but it's all being done by RTR so you won't have to worry about all of the problems inherent in communicating over a network.

Let's examine what the RegisterFacility method does. First, the RTRFacilityName parameter we sent to it is ABCFacility. This tells RTR the name of the facility we created earlier. Suddenly, RTR has a whole lot more information about your application: where to find the server, the standby server, and the router. You will see later in this tutorial that the server also declares itself and supplies the same facility name.

The RegisterFacility method tells RTR that this application is acting as a client. So now RTR knows that if the server goes down, it certainly doesn't want to force this application to come to the rescue as the standby server! And there will be other things that RTR will be handling that are appropriate only to clients or only to servers. This information helps it to keep track of all the players.

The second parameter, szRecipientName, designates the facility member that has the backend role. The default value is the wildcard "*", meaning that there is no specific recipient name specified.

The third parameter, *pszAccess, is a pointer to the null-terminated string containing the access parameter. This is a security key for authorizing access to a facility by clients and servers. The default value is RTR_NO_ACCESS, when there is no specified access parameter.

At this point, RTR has all of the information it needs to put the pieces together into one system; you're ready to start sending messages to the server, and to get messages back from it.

RTR Return Status

Your facility may have more than just one client talking to your server. In fact, your neighbor who so generously allowed you to run your standby server on his or her machine might want to get in on this RTR thing, too. That's all right: just add a machine to the RTRTutor facility definition that will also run a copy of the client. But not yet; we're only telling you this to illustrate the point that there can be more than one client in an RTR-based application. Because of this, after the RTR router hands off your client's request to your server, it must then be able to do the same for other clients.

Servers can also decide they want to talk to your client, and the RTR router may need to handle their requests at any time, as well. If RTR were to wait for the server to do its processing and then return the answer each time, there would be an awful bottleneck.

But RTR doesn't wait. This means that the sStatus that you get back from each call means only, "I passed your message on to the server", not that the server successfully handled it and here is the result. So how does your client actually get the result of the request it made on the server? It will need to explicitly "receive" a message, as you'll see later in this tutorial.

Checking RTR Status

Throughout this code example, you'll see a line of code that looks like:


    assert(RTR_STS_OK == sStatus ); 

or


    if(RTR_STS_OK == sStatus) 

This is good because, as you know from your Programming 101 course, you should always check your return sStatus. But it's also good that your program knows when something has gone wrong and can tell the user, or behave accordingly. The assert function is not part of RTR, but is something you will probably want to do in your application.

To check RTR's return sStatus, compare it to RTR_STS_OK. If it's the same, everything is fine, and you can go on to the next call. But if it is something else, you'll probably to print a message to the user. To get the text string that goes with this sStatus, call `rtr_error_text' which returns a null terminated ASCII string containing the message in human readable format.

Receiving Messages

As explained earlier, RTR does not hold your client up while it processes your request, or even a request from another client. You must first wait for the client transaction controller RegisterFacility call to let you know that everything is ready to go for the client to start sending messages to a server application.

With the C++ API, your client application receives messages through data class objects on the Receive method of the transaction controller class. The RTRClassFactory class creates the appropriate data object based on the type of data that the transaction controller is about to receive. All C++ API data objects derive from the RTRData class.

The Receive call waits to receive a message or event from RTR:


    sStatus = Receive(*pRTRData); 

The *pRTRData is a pointer to a data object and RTR_NO_TIMEOUTMS is the default for the tTimeout parameter.

Remember Programming 101 - check your sStatus every time!


    assert(RTR_STS_OK == sStatus); 

In the client sample application ABCOrderTaker, the client derived receive method is DetermineOutcome. This method receives a message from RTR to determine whether the book or magazine order was processed successfully or not.


    eOrderStatus ABCOrderTaker::DetermineOutcome() 
    { 
        RTRData  *pResult = NULL; 
        rtr_sStatus_t sStatus; 
        eOrderStatus eTxnResult = OrderBeingProcessed; 
        rtr_msg_type_t mtMessageType; 

This code illustrates how an application can retrieve and use the message from an RTRData derived object.


        while (OrderBeingProcessed == eTxnResult) 
        { 
          sStatus = Receive(&pResult); 
          print_sStatus_on_failure(sStatus); 
          if ( true == pResult->IsRTRMessage()) 
        { 

Check to see if we have a sStatus for the transaction. If the transaction sStatus is:


    sStatus = ((RTRMessage*)pResult)->GetMessageType(mtMessageType); 
    print_sStatus_on_failure(sStatus); 
    if (rtr_mt_accepted == mtMessageType) return eTxnResult = OrderSucceeded; 
    if (rtr_mt_rejected == mtMessageType) return eTxnResult = OrderFailed; 
    } 
    } 
    return eTxnResult; 
    } 

Information about whether RTR or your server has successfully handled your client's request is returned in the data object. It is received by the transaction controller from RTR in the RTRData object on the Receive call.

The implementation for the handler methods OnAccepted and OnRejected in ABCSHandlers.cpp is:


    void ABCSHandlers::OnAccepted( RTRMessage *pRTRMessage, 
    RTRServerTransactionController *pController ) 
    { 
        pController->AcknowledgeTransactionOutcome(); 
        return; 
    } 
    void ABCSHandlers::OnRejected( RTRMessage *pRTRMessage, 
    RTRServerTransactionController *pController ) 
    { 
        pController->AcknowledgeTransactionOutcome(); 
        return; 
    } 

Send Messages

With the C++ API, the start of a transaction is implicit, with the sending of the first message to a server application. Once the client transaction controller has registered a facility and its message and event handlers, the rest of the client application is simply a `send/receive' message loop. It continues to send messages to the server, then listen for the server's response. It is important to remember that, although the client is sending these messages to the server, it is doing so through RTR. Because of this, the client can receive, asynchronously, different types of messages and events, including:

With the C++ API, there are four types of data you can receive:

The RTRClassFactory creates these data objects when a Receive method is called for a transaction controller. The class factory takes the incoming RTRData object and creates the appropriate data object based on the type of incoming data.

In addition to clients and servers sending and receiving messages, RTR may send the client messages under certain conditions. So the client application must be prepared to accept any of these messages, and not necessarily in a particular sequence.

That's certainly a tall order! How should you handle this? Well, there are a number of ways, but you typically implement these possibilities in the client message and event handlers. (The implementation details of handling messages and events on a Receive are implemented in the sample server application ABCOrderProcessor.) In this tutorial we will explain how to run a "message loop" that both sends and receives messages.

The client sample application ABCOrderTaker has a derived SendOrder method for sending RTRApplicationMessage objects to the server application. These objects can be either book orders or magazine orders. (ABCOrderTaker derives from RTRClientTransactionController and thus inherits the Register and SendApplicationMessage methods.)


    bool ABCOrderTaker::SendOrder(ABCOrder *pOrder) 
    { 
        rtr_sStatus_t sStatus; 
        eOrderStatus eTxnResult = OrderBeingProcessed; 
    // Register with RTR if we havn't already done so. 
    // This will make sure we are ready to start sending data. 
        sStatus = Register(); 
        if (RTR_STS_OK != sStatus) return false; 
    // If we can't register with RTR then exit 
    // Send this Book Order object to a server capable 
    // of processing it. 
        sStatus = SendApplicationMessage(pOrder); 
        print_sStatus_on_failure(sStatus); 
    //  Let RTR know that this is the object  being sent and 
    //  that we are done with our work. 
        sStatus = AcceptTransaction(); 
        print_sStatus_on_failure(sStatus); 
    //  Determine if the server successfully processed the request 
        eTxnResult = DetermineOutcome(); 
        return true; 
    } 


Previous Next Contents Index