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.
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:
NFS and Windows shares are not supported for journal disks.
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:
These files include:
These files include:
These files include:
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:
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;
}
A Word about RTR Data types
You may have noticed that your client, server and router can be on any one of many different operating systems. And you’ve probably written code for more than one operating system and noticed that each has a number of data types that the other doesn’t have. If you send data between a Solaris UNIX machine and an OpenVMS or Windows NT machine, you’ll also have to worry about the order different operating system stores bytes in their data types (called "endian" order). And what happens to the data when you send it from a 16 bit Intel 486 Windows machine to a 64 bit Alpha UNIX machine?
Thanks to RTR, you don’t need to worry about it. RTR will handle everything for you. Just write standard C++ code that will compile on the machines you choose, and the run-time problems won’t complicate your design. When you do this, you need to use RTR data types to describe your data. RTR translates everything necessary when your data gets to a new machine by converting the data to the native data types on the operating system with which it happens to be communicating at the time.
To illustrate this, the example code evaluates your input parameters and places them into an RTRData-derived RTRApplicationMessage object, ABCOrder. One sample application data class is ABCBook, which derives from ABCOrder. This subclass defines the data that is passed from client to server for a book order. This data class is defined in ABCBook.h and implemented in ABCBook.cpp
You’ll notice that the data types which make up this object aren’t your standard data types - they are RTR data types. And they are generic enough to be able to be used on any operating system: 8 bit unsigned, 32 bit unsigned, and a string.
UINT m_uiPrice;
UINT m_uiISBN;
CString m_csTitle;
CString m_csAuthor;)
unsigned int m_uiISBN;
unsigned int m_uiPrice;
char m_szTitle[ABCMAX_STRING_LEN];
char m_szAuthor[ABCMAX_STRING_LEN];
Send/Receive Message Loop
As mentioned earlier, an RTR client application typically contains a message loop that sends messages to the server via the RTR router, and handles messages that come from the server via the router, or from RTR itself.
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 txn.
// rtr_mt_accepted = Server successfully processed our request.
// rtr_mt_rejected = Server could not process our request.
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;
}
sStatus = SendApplicationMessage( *pRTRApplicationMessage,
bReadonly = false,
bReturnToSender = false,
mfMessageFormat=RTR_NO_MSGFMT);
}
assert(RTR_STS_OK == sStatus);
The first message is sent to the server in the first parameter of the SendApplicationMessage call. As you will see, this is part of the flexibility and power of RTR. The parameter pRTRApplicationMessage is a pointer to a block of memory containing your data. RTR doesn’t know what it’s a pointer to, but it doesn’t need to know this. You, as the programmer, are the only one who cares what it is. It’s your own data object that carries any and all of the information your server will need in order to do your bidding. We’ll see this in detail when we look at the server code.
You do not need to tell RTR how big the piece of memory being pointed to pRTRApplicationMessage is. The data object automatically lets RTR know how many bytes to move from your client machine to your server machine, so that your server application has access to the data being sent by the client.
And now, the client waits for a response from the server.
The client receives the server's reply or an rtr_mt_rejected and calls the client message handler method, OnRejected
sStatus = Receive( *pRTRData);
assert(RTR_STS_OK == sStatus);
Again you see the pRTRData parameter is a pointer to a data object created by you as the programmer, and can carry any information you need your server to be able to communicate back to the your client.
The RTRData object contains a code that tells you what kind of a message you are now receiving on your transaction controller. If the RTR message type contains the value rtr_mt_reply, then you are receiving a reply to a message you already sent, and your receive message object has been written to with information from your server.
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;
If GetMessageType contains the value rtr_mt_rejected, then something has happened that caused your transaction to fail after you sent it to the router. You can find out what that ‘something’ is by looking at the sStatus returned by the Receive call. You will recall that making the rtr_error_text call and passing the sStatus value will return a human readable null terminated ASCII string containing the error message.
This is where you’ll need to make a decision about what to do with this transaction. You can abort and exit the application, issue an error message and go onto the next message, or resend the message to the server. This code re-sends a rejected transaction to the server.
When your client application receives an rtr_mt_reply message, your message has come full circle. The client has made a request of the server on behalf of the user; the server has responded to this request. If you’re satisfied that the transaction has completed successfully, you must notify RTR so that it can do its own housekeeping. To this point, this transaction has been considered "in progress", and its sStatus kept track of at all times. If all parties interested in this transaction (this includes the client AND the server) notify RTR that the transaction has been completed, RTR will stop tracking it, and confirm to all parties that it has been completed. This is called ‘voting’.
if (msgsb.msgtype == rtr_mt_reply)
{
sStatus = AcceptTransaction(RTR_NO_REASON)
assert (RTR_STS_OK == sStatus);
And now the client waits to find out what the result of the voting is.
sStatus = Receive( *pRTRData, RTR_NO_TIMOUTMS);
assert(RTR_STS_OK == sStatus);
If everyone voted to ‘accept’ the transaction, the client can move on to the next one. But if one of the voters rejected the transaction, then another decision must be made regarding what to do about this transaction. This code attempts to send the transaction to the server again.
sStatus = ((RTRMessage*)pResult)->GetMessageType(mtMessageType);
print_status_on_failure(sStatus);
if (rtr_mt_accepted == mtMessageType) return eTxnResult =
OrderSucceeded;
if (rtr_mt_rejected == mtMessageType) return eTxnResult =
OrderFailed;
All of the requested messages, or transactions, have been sent to the server, and responded to. The only RTR cleanup we need to do before we exit the client is to close the transaction controller. This is similar to signing off, and RTR releases all of the resources it was holding for the client application.
Now, that wasn’t so bad, was it? Of course not. And what has happened so far? The client application has sent a message to the server application. The server has responded. RTR has acted as the messenger by carrying the client’s message and the server’s response between them.
Next, let’s see how the server gets these messages, and sends a response back to the client.
Server Application
The files shipped with the RTR kit used in the server application for this tutorial are the ABCOrderProcessor, ABCSHandlers and ABCSClassFactory files, in addition to the common files. These common files, including ABCCommon, ABCBook, and ABCMagazine are used in both client and the server applications. This is for a number of reasons, but most importantly that both the client and the server use the same definitions for the data objects they pass back and forth as messages. With the exception of only two items, there will be nothing in this server that you haven’t already seen in the client. It’s doing much the same things as the client application is doing. It creates a server transaction controller object for connecting to the router, telling the router that it is a server application; and then registers a partition. It waits to hear that the RegisterPartition request has been successfully executed; runs a loop that receives messages from the client; carries out the client’s orders; sends the response back to the client. And the server gets to vote, too, on whether each message/response loop is completed.
One of the differences between the client andserver is the types of messages a server can receive from RTR; we’ll go through some of them in this section of the tutorial about the server application.
The other difference is the RegisterPartition call which is sent to RTR. We mentioned partitions while discussing the client application, but said we’d discuss them later. Well, it’s later...
Initialize RTR
The server creates a transaction controller and registers a partition. In addition, the server registers message and event handlers and a class factory, causing RTR to initialize a number of resources for use by the server, as well as to gather information about the server. In the Register methods in the server application, ABCOrderProcessor.cpp, you’ll find the example server calling RegisterPartition. You see that the RegisterPartition method creates a single RTR data partition for each time it is called. In the server code, there are two partitions, ABCPartition1 and ABCPartition2.
sStatus = RegisterPartition(ABCPartition1);
print_sStatus_on_failure(sStatus);
sStatus = RegisterPartition(ABCPartition2);
print_sStatus_on_failure(sStatus);
In order to call RegisterPartition, the sample application includes a CreateRTREnvironment method that is first called in the ABCOrderProcessor::Register method.
Data Partitions
What is data partitioning, and why would you wish to take advantage of it? It is possible to run a server application on each of multiple backend machines, and to run multiple server applications on any backend machine. When a server registers a partition to begin communicating with the RTR router, it uses the KeySegment information to tell RTR that it is available to handle certain key segments. A key segment can be "all last names that start with A to K" and "all last names that start with L to Z", or "all user identification numbers from zero to 1000" and "all user identification numbers from 1001 to 2000".
In the sample application, the implementation is as follows:
void ABCOrderProcessor::CreateRTREnvironment()
{
rtr_sStatus_t sStatus;
// If RTR is not already started then start it now.
StartRTR();
// Create a Facility if not already created.
CreateFacility();
// Create a partition that processes ISBN numbers in
// the range 0 - 99
unsigned int low = 0;
unsigned int max = 99;
RTRKeySegment KeyZeroTo99(rtr_keyseg_unsigned,
sizeof(int),
0,
&low,
&max );
RTRPartitionManager PartitionManager;
sStatus = PartitionManager.CreateBackendPartition(ABCPartition1,
ABCFacility,
KeyZeroTo99,
false,
true,
false);
print_sStatus_on_failure(sStatus);
// Create a partition that processes ISBN numbers in
// the range 100 - 199
low = 100;
max = 199;
RTRKeySegment Key100To199( rtr_keyseg_unsigned,
sizeof(int),
0,
&low,
&max );
sStatus = PartitionManager.CreateBackendPartition(ABCPartition2,
ABCFacility,
Key100To199,
false,
true,
false);
print_sStatus_on_failure(sStatus);
}
Each key segment describes a data partition. Data partitions allow you to use multiple servers to handle the transactions all of your clients are attempting to perform; in this way, they don’t all have to wait in line to use the same server. They can get more done in less time. Data partitions can be specified through a command line interface or programmatically through the RTRPartitionManager class.
The RTR Application Design Guide goes into more detail about data partitioning.
Again, we use the RTR data object that RTR will place information in, and the user-defined data object, ABCOrder, that the client’s data will be copied into. But at this point, the server is talking with RTR only, not the client, so it is expecting an answer from RTR. All the server really wants to know is that the transaction controller is ready to receive a client request. If it isn’t, the server application will write out an error message and exit with a failure sStatus. The implementation of the sample server application's Register function:
The sStatus is checked after each of these calls. If they are all successful, the server is ready to receive incoming requests from the client application.
void ABCOrderProcessor::Register()
{
rtr_sStatus_t sStatus;
// Create an environment that our server can run in.
CreateRTREnvironment();
// Register with RTR the following objects
sStatus = RegisterFacility(ABCFacility);
print_sStatus_on_failure(sStatus);
// ABC Partition
sStatus = RegisterPartition(ABCPartition1);
print_sStatus_on_failure(sStatus);
sStatus = RegisterPartition(ABCPartition2);
print_sStatus_on_failure(sStatus);
// ABC Class Factory
sStatus = RegisterClassFactory(&m_ClassFactory);
print_sStatus_on_failure(sStatus);
// ABC Handlers
sStatus = RegisterHandlers(&m_rtrHandlers,&m_rtrHandlers);
print_sStatus_on_failure(sStatus);
return;
The RegisterHandlers method takes two parameters; the first parameter is a pointer to an RTRServerMessageHandler object and the second parameter is a pointer to an RTRSeverEventHandler object. ABCHandlers multiply derives from both of these foundation classes.
The server message handler specifies all messages generated by RTR or the RTR application that a server application may receive. The server event handler specifies all events generated by RTR or the RTR application that a server application may receive.
Now that the transaction controller has been established, the server waits to receive messages from the client application and the RTR router.
Receive/Reply Message Loop
The server sits in a message loop receiving messages from the router, or from the client application via the router. Like the client, it must be prepared to receive various types of messages in any order and then handle and reply to each appropriately. But the list of possible messages the server can receive is different than that of the client. This example includes some of those. First, the server waits to receive a message from RTR.
The implementation in the ProcessIncomingOrders method of the ABCOrderProcessor class:
// Start processing orders
abc_sStatus sStatus;
RTRData *pOrder = NULL;
while (true)
{
// Receive an Order
sStatus = Receive(&pOrder);
print_sStatus_on_failure(sStatus);
// If we can't get an Order then stop processing.
if(ABC_STS_SUCCESS != sStatus) break;
// Dispatch the Order to be processed
sStatus = pOrder->Dispatch();
print_sStatus_on_failure(sStatus);
// Check to see if there were any problems processing the order.
// If so, let the handler know to reject this txn when asked to
// vote.
CheckOrderStatus(sStatus);
Upon receiving the message the server checks the RTRData object's message type field to see what kind of message it is. Some are messages directly from RTR and others are from the client. In any event, the class factory creates the appropriate data object for the server application to handle the incoming data. When the message is from the client, your application will read the data object you constructed to pass between your client and server and, based on what it contains, do the work it was written to do. In many cases, this will involve storing and retrieving information using your database.
But when the message is from RTR, how should you respond? Let’s look at some of the types of messages a server gets from RTR, and what should be done about them.
The following implementation from the ABCSHandlers.cpp file is for an rtr_mt_msg1_uncertain RTR message:
void ABCSHandlers::OnUncertainTransaction( RTRApplicationMessage *pRTRData, RTRServerTransactionController *pController )
{
return;
}
The rtr_mt_msg1 and rtr_mt_msg1_uncertain messages identify the beginning of a new transaction. The rtr_mt_msg1 message says that this is a message from the client, and it’s the first in a transaction. When you receive this message type, you will find the client data in the object pointed to by pRTRData parameter of this call. The client and server have agreed on a common data object that the client will send to the server whenever it makes a request: this is the ABCOrder object we looked at in the client section of this tutorial. RTR has copied the data from the client’s data object into the one whose memory has been supplied by the server. The server’s responsibility when receiving this message is to process it. On receiving an rtr_mt_msg1, the server application calls the OnInitialize and OnApplicationMessage server message handler methods.
On receiving an RTR message rtr_mt_msg1, the server application calls the handler methods OnInitialize and OnApplicationMessage by default. Business logic processing can be done within the OnApplicationData method.
The sample server application implements the OnInitialize method and overloads the Dispatch method with an implementation in ABCOrder that deserializes the ABCOrder data object, rather than having the default Dispatch method invoking OnApplicationMessage.
From the ABCSHandlers class:
void ABCSHandlers::OnInitialize( RTRApplicationMessage *pRTRData, RTRServerTransactionController *pController )
{
m_bVoteToAccept = true;
return;
}
The overloaded Dispatch method in the ABCOrder data object:
rtr_sStatus_t ABCOrder::Dispatch()
{
// Populate the derived object
ReadObject();
// Process the purchase that the derived object represents
bool bStatus = Process();
if (true == bStatus)
{
return ABC_STS_SUCCESS;
}
else
{
return ABC_STS_ORDERNOTPROCESSED;
}
}
Recovered Transactions
The rtr_mt_msg1_uncertain message type tells the server that this is the first message in a recovered transaction. In this instance, the original server the application was communicating with failed, possibly leaving some of its work incomplete, and now the client is talking to the standby server. What happens to that incomplete work left by the original server? Looking back at the client you will recall that everyone got to vote as to whether the transaction was accepted or rejected, and then the client waited to see what the outcome of the vote was. While the client was waiting for the results of this vote, the original server failed, and the standby server took over. RTR uses the information it kept storing to the recovery journal, which you also created earlier, to replay to the standby server so that it can recover the incomplete work of the original server.
When a server receives the ‘uncertain’ message, it knows that it is stepping in for a defunct server that had, to this point, been processing client requests. But it doesn’t know how much of the current transaction has been processed by that server, and how much has not, even though it receives the replayed transactions from RTR. The standby server will need to check in the database or files to see if the work represented by this transaction is there and, if not, then process it. If it has already been done, the server can forget about it.
If the received message contains rtr_mt_msg1_uncertain:
replay = RTR_TRUE;
else
replay = RTR_FALSE;
if ( replay == TRUE )
// The server should use this opportunity to
// clean up the original attempt, and prepare
// to process this request again.
else
// Process the request.
The server then replies to the client indicating that it has received this message and handled it.
The server typically uses the SendApplicationMessage call to answer the request the client has made. In some cases, this may mean that data needs to be returned. This will be done in the data object that has been agreed upon by both the client and the server.
Prepare Transaction
The rtr_mt_prepare message tells the server to prepare to commit the transaction. All messages from the client that make up this transaction have been received, and it is now almost time to commit the transaction in the database. This message type will never be sent to a server that has not requested an explicit prepare.
After determining whether it is possible to complete the transaction based on what has occurred to this point, the server can either call RejectTransaction to reject the transaction, or set all of the required locks on the database before calling AcceptTransaction to accept the transaction.
void ABCSHandlers::OnPrepareTransaction( RTRMessage *pRTRMessage, RTRServerTransactionController *pController )
{
// Check to see if anything has gone wrong. If so, reject
// the transaction, otherwise accept it.
if (true == m_bVoteToAccept)
{
pController->AcceptTransaction();
}
else
{
pController->RejectTransaction();
}
return;
}
Because this example code is not dealing with a database, nor is it bundling multiple messages into a transaction, the code here immediately votes to accept the transaction.
Transaction Rejected
The rtr_mt_rejected message is from RTR, telling the server application that a participant in the transaction voted to reject it. If one participant rejects the transaction, it fails for all. The transaction will only be successful if all participants vote to accept it. When it receives this message, the server application should take this opportunity to roll back the current transaction if it is processing database transactions.
The sample server application includes the following code to check the order if it was not processed. If the order was not processed properly, then the handler method OnABCOrderNotProcessed is called and m_bVoteToAccept is set to false. This causes OnPrepareTransaction to reject the transaction.
void ABCOrderProcessor::CheckOrderStatus (abc_sStatus sStatus)
{
// Check to see if there were any problems
// processing the order. If so, let the handler know
// to reject this txn when asked to vote.
if (sStatus == ABC_STS_ORDERNOTPROCESSED)
{
// Let the handler know that the current txn should be rejected
GetHandler()->OnABCOrderNotProcessed();
};
};
void ABCSHandlers::OnABCOrderNotProcessed()
{
m_bVoteToAccept = false;
return;
}
Finally, explicitly end the transaction on a reject, the handler method OnRejected is called:
void ABCSHandlers::OnRejected( RTRMessage *pRTRMessage, RTRServerTransactionController *pController )
{
pController->AcknowledgeTransactionOutcome();
return;
}
Transaction Accepted
RTR is telling the server that all participants in this transaction have voted to accept it. If database transactions are being done by the server, this is the place at which the server will want to commit the transaction to the database, and release any locks it may have taken on the database.
void ABCSHandlers::OnAccepted( RTRMessage *pRTRMessage, RTRServerTransactionController *pController )
{
pController->AcknowledgeTransactionOutcome();
return;
}
Note the AcknowledgeTransactionOutcome call in the server. This is an explicit method for completing a transaction.
That’s it. You now know how to write a client and server application using RTR as your network communications, availability and reliability infrastructure. Congratulations!
Build and Run the Servers
Compile the ABC server and ABC shared files on the operating system that will run your server applications. If you are using two different operating systems, then compile it on each of them. To build on UNIX, issue the command:
cxx -o server server.c shared.c /usr/shlib/librtr.so -DUNIX
You should start the servers before you start your clients. They will register with the RTR router so that the router will know where to send client requests. Start your primary server with the appropriate ‘run’ command for its operating system along with the two parameters ‘1’ and ‘h’. To run on UNIX:
% ./server 1 h
Start your standby server with the parameters ‘2’ and ‘h’.
% ./server 2 h
Build and Run the Client: Compile the ABC CLIENT and ABC SHARED modules on the operating system which will run your client application. To build on UNIX:
% cxx -o client client.c shared.c /usr/shlib/librtr.so -DUNIX
Run the client with the following command:
% ./client 1 h 10
or
C:\RtrTutor\> client.exe 1 h 10
In many ways, this tutorial has only scratched the surface of RTR. There is a great deal more that RTR gives you to make your distributed application reliable, available, and perform better. The following sections of this document highlight some of the capabilities you have at your service. For more details on each item, and information on what additional features will help you to enhance your application, look first through the RTR Application Design Guide. Then, earlier sections of this (C++ Foundation Classes) manual will tell you in detail how to implement each capability.
Compaq Computer Corporation also offers training classes for RTR, and if you’d like to attend any of them, contact your Compaq representative.
Callout Server
RTR supports the concept of a "callout server" for authentication. You may designate an additional application on your server machines or your router machine as a callout server with the RTRFacilityManager class methods. Callout servers will be asked to check all requests in a facility, and are asked to vote on every transaction.
The CreateFacility method in the RTRFacilityManager class includes a boolean parameter bEnableBackendCallout for specifying a callout server.
Events
In addition to messages, RTR can be used to dispatch asynchronous events on servers and clients. A callback function in the user’s server and client applications can be designated which RTR will call asynchronously to dispatch events to your application.
Shadowing
This tutorial only discussed failover to a standby server. But RTR also supports shadowing: while your server is making changes to your database, another "shadow" server can be making changes to an exact copy of your database in real time. If your primary server fails, your shadow server will take over, and record all of the transactions occurring while your primary server is down. Your primary server will be given the opportunity to update the original database and catch up to the correct state when it comes back up. Primary and secondary shadow server can also have standby servers for failover! So as you can see, if your database and transactions are important enough to you, you have the opportunity to double and triple protect them with an RTR configuration including any of
Transactions
One of RTR’s greatest strengths is in supporting transactions. The RTR Application Design Guide goes into more detail regarding transactions and processing of transactions.
RTR Utility
You’ve seen how to use the RTR utility (or the command line interface) to start RTR and to create a facility. But the RTR utility contains many more features than this, and in fact can be used to prototype an application. Refer to the RTR System Manager’s Manual for details.