Previous | Contents | Index |
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 |
With this command, you have now:
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 will write the application: your
client will talk to RTR, and your server will talk to RTR. RTR will
deliver the messages between them and, if the server crashes, bring in
the standby server to handle your client’s requests. The client will
never know that the server has been switched, and no data or requests
to retrieve or modify data will be lost!
Application:
The C modules and header files for this application are located in the
‘examples’
subdirectory of the directory into which you installed RTR. They
consist of the following files:
ADG_CLIENT.C | The client application |
ADG_SERVER.C | The server application |
ADG_SHARED.C | C code common to both the client and server applications |
ADG_HEADER.H | Header file containing definitions specific to both sample applications |
Although you won’t have much typing to do, this tutorial will explain what the code in each module is doing. Copy all four of these files into a working directory of your own. For convenience, you may also wish to copy rtr.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 ADG_HEADER.H and change the FACILITY value to "RTRTutor".
The application example 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 app and the server app. Each will talk only to RTR, who will move the messages and data between them. And you are free not to worry about:
Aren’t you relieved? Maybe you should take another break to celebrate!
Client App:
The files shipped with the RTR kit used in the client application for
this tutorial are ADG_CLIENT.C, ADG_SHARED.C and ADG_HEADER.H.
All applications that wish to talk to RTR through its API need to include ‘rtr.h’ as a header file. This file lives in the directory into which RTR was installed, and contains the definitions for RTR structures 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 (ADG_HEADER.H) whenever you need additional definitions for your application.
#include "ADG_header.h" #include "rtr.h" |
The client application design follows this outline:
Pretty straightforward, don’t you think? Let’s look at how it’s done:
Initialize RTR:
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 will find this happening in the declare_client
function in adg_client.c, and somewhat more simplified here.
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’ll open a channel that enables communication between the client and the RTR router. Remember that the RTR router has been described as "keeping track of everything" that goes on in an RTR application.
Declare the items needed for the open channel call:
rtr_status_t status; /* will be returned by RTR */ rtr_channel_t channel; /* a channel */ |
Open the channel:
status = rtr_open_channel( &channel, /* channel of communication */ RTR_F_OPE_CLIENT , /* I am a client */ “RTRTutor”,/* the facility we created */ NULL, /* recipient name */ RTR_NO_PEVTNUM, /* don’t send events, just messages */ NULL, /* access key */ RTR_NO_NUMSEG , /* number of key segments */ RTR_NO_PKEYSEG ); /* first key segment */ |
Let’s examine what this ‘open channel’ call does. First, the ‘channel’ parameter we sent to it is only a pointer to a block of memory; we’ve done nothing to set any values in it. RTR will use this block of memory to store the information it needs in order to assign and keep track of this channel. The channel 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.
The second parameter 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.
And now [trumpets are heard in the distance!] the third parameter 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.
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.
A Word About RTR’s API Parameters:
You may have noticed that although we’ve looked at only three of the
parameters in the ‘open channel’ call, there are a number more. It’s a
quirk of RTR that you’ll often need to tell it to default. Rather than
defaulting on its own when you do not provide a parameter (or provide a
null parameter), it needs the "default" parameter. So you’ll
see things like RTR_NO_PEVTNUM to tell it "I don’t want to be
notified of any events" which is actually a default, and
RTR_NO_NUMSEG to tell it "I have defined no key segments"
which is also a default. Whenever we skip the discussion on non-null
parameters, you’ll know they are default parameters.
The parameter RTR_NO_FLAGS tells RTR that there are no flags.
A Word About RTR’s 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 status 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
this (with a different string in the first parameter each time):
check_status( "rtr_open_channel", status ); |
This is good because, as you know from your Programming 101 course, you should always check your return status. But it’s also good that your program knows when something has gone wrong and can tell the user, or behave accordingly. The ‘check_status’ function is not part of RTR, but is something you will probably want to do in your application.
To check RTR’s return status, 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 status, call ‘rtr_error_text’ which returns a null terminated ASCII string containing the message in human readable format.
if (status != RTR_STS_OK) { sprintf(" Call failed: %s", rtr_error_text(status)); } |
Receiving Messages:
As explained earlier, RTR doesn’t hold your client up while it
processes your request, or even a request from another client. And
since nothing can continue until the system has been set up, you now
need to wait for the open channel call to let you know that everything
is started up and ready to go. This is what the rest of the code in the
"declare_client" function does. These statements:
receive_msg_t receive_msg = {0}; /* message received */ rtr_msgsb_t msg_status; /* message status block */ |
declare the memory for a "receive" message and a message status block. And now the rtr_receive_message waits to receive a message from RTR.
status = rtr_receive_message( &channel, /* channel on which message received */ RTR_NO_FLAGS, /* sending no flags (default) */ RTR_ANYCHAN, /* default channel */ &receive_msg, /* location to place return info */ sizeof(receive_msg),/* size of last */ RTR_NO_TIMEOUTMS, /* do not timeout */ &msg_status); /* location to return status */ |
The channel parameter and the RTR_NO_FLAGS parameter should now be familiar to you; we discussed them in the sections of this document on ‘Initialize’ and ‘Parameters’. RTR_ANYCHAN and RTR_NO_TIMEOUTMS are defaults for this API.
Remember Programming 101 --- check your status every time!
Information about whether RTR or your server has successfully handled your client’s request is returned in an rtr_msgsb_t message status block structure. It is received from RTR in as the last parameter in the rtr_receive_message call. For rtr_open_channel, we are looking for the "rtr_mt_opened" message type in the status block to confirm that the channel has been opened, and that we are now prepared to do all of the rest of the messaging on it for our application. If we don’t have the "opened" message, then we can expect there to be an error status in the receive message block.
if ( msg_status.msgtype != rtr_mt_opened ) { print(" Error opening rtr channel : "); print( rtr_error_text(receive_msg.receive_status_msg.status)); } |
The rtr.h header file provided with the RTR installation kit describes the rtr_msgsb_t structure in detail.
Send Messages:
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 the RTR router. Because of this, the client can receive, asynchronously, different types of messages:
In addition, 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 in this tutorial we will explain how to run a "message loop" that both sends and receives messages.
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 a VMS 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 converts the data to the native data types on the operating system with which it happens to be communicating at the time.
Think of RTR as your very own "Babel fish," if you’ve read the "Hitchhiker’s Guide to the Galaxy" series. It will translate everything necessary when your data gets to a new machine. The little fish you put in your ear is actually made up of the RTR application programming interface and the RTR data types.
To illustrate this, the example code evaluates your input parameters and places them into a message_data_t structure called ‘send_msg’. Message_data_t is not an RTR structure, but created by the programmer who wrote this client. The message_data_t structure is defined in ADG_HEADER.H.
typedef struct { rtr_uns_8_t routing_key; rtr_uns_32_t server_number; rtr_uns_32_t client_number; rtr_uns_32_t sequence_number; String31 text; } message_data_t; |
You’ll notice that the data types which make up message_data_t 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.
Earlier, we looked at the receive message code when the client opened a channel. The structure it used to obtain information, receive_message_t, is also one created by the programmer, and not a standard RTR structure. If you look at its definition in the ADG_HEADER.H file, you’ll see that it’s the same as the message_data_t structure, plus it contains a location for RTR status. There will be more on this in the next section.
Send/Receive Message Loop:
As mentioned earlier, the sample code for the RTR client application
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.
The following discussion will reference a simplified version of that loop; code in the sample to add time stamps and print to a log file has been removed here for clarity.
When you run the sample client, the client expects three parameters: a client number, a partition range, and the number of messages to send, in that order. We will talk more about partition ranges later when we look at the server application, but for now it is enough to know that we’ll use one character, the letter h.
The input command parameters are evaluated and placed in the message_data_t structure named send_msg. The ‘number of messages’ parameter which you’ll input on the command line is placed in the ‘txn_cnt’ variable. The ‘for’ loop which sends and receives messages will execute this number of times.
The message_data_t structure also holds a ‘sequence number’ value that is incremented each time the loop is executed; so now our loop begins:
for ( ; txn_cnt > 0; txn_cnt--, send_msg.sequence_number++ ) { status = rtr_send_to_server( channel, RTR_NO_FLAGS , &send_msg, sizeof(send_msg), RTR_NO_MSGFMT ); check_status( "rtr_send_to_server", status ); |
The first message has been sent to the server in the third parameter of the rtr_send_to_server call. As you will see, this is part of the flexibility and power of RTR. This third parameter is no more than 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 structure 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.
In the fourth parameter, you must tell RTR how big the piece of memory being pointed to by the third parameter is. RTR needs to 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.
The rest of the parameters bear some looking at, as well: there’s the channel again. You’ll see the channel parameter in almost every RTR call. You may be becoming suspicious about the channel, and think that it’s really more than just a line for communicating. And you’d be right. RTR uses the channel much like you use that third parameter in this call. The RTR developers are the only ones who know what’s in it, and it contains much of the information they need to make RTR work.
You’ll recognize two more default parameters, RTR_NO_FLAGS and RTR_NO_MSGFMT.
And now, the client waits for a response from the server.
/* * Get the server's reply OR an rtr_mt_rejected */ status = rtr_receive_message( &channel, RTR_NO_FLAGS, RTR_ANYCHAN, &receive_msg, sizeof(receive_msg), RTR_NO_TIMOUTMS, &msgsb); check_status( "rtr_receive_message", status ); |
Again you see the channel and the default flags; the receive_msg parameter is a pointer to another data structure 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. In your own application, you would actually create this data structure in your application’s header file. You can see what the example receive message looks like by checking out the receive_msg_t in the ADG_HEADER.H file. RTR picks it up from your server and writes it here for your client to read.
The msgsb parameter is an RTR data structure: you saw this message status block earlier when we looked at the open channel code. Its msgtype field contains a code that tells you what kind of a message you are now receiving. If msgsb.msgtype contains the value rtr_mt_reply, then you are receiving a reply to a message you already sent, and your receive message data structure has been written to with information from your server.
switch (msgsb.msgtype) { case rtr_mt_reply: fprintf(fpLog, " sequence %10d from server %d\n ", receive_msg.receive_data_msg.sequence_number, receive_msg.receive_data_msg.server_number); break; |
If msgsb.msgtype 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 status returned by the rtr_receive_message call. You will recall that making the rtr_error_text call and passing the status value will return a human readable null terminated ASCII string containing the error message.
case rtr_mt_rejected: fprintf(fpLog, " txn rejected at: %s", ctime( &time_val)); fprint_tid(fpLog, &msgsb.tid ); |
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 resends a rejected transaction to the server.
/* Resend message with same sequence_number after reject */ send_msg.sequence_number--; txn_cnt++; break; default: fprintf(fpLog, " unexpected msg”); fprint_tid(fpLog, &msgsb.tid ); fflush(fpLog); exit((int)-1); } |
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 status 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) { status = rtr_accept_tx( channel, RTR_NO_FLAGS, RTR_NO_REASON ); check_status( "rtr_accept_tx", status ); |
And now the client waits to find out what the result of the voting is.
status = rtr_receive_message( &channel, RTR_NO_FLAGS, RTR_ANYCHAN, &receive_msg, sizeof(receive_msg), receive_time_out, &msgsb); check_status( "rtr_receive_message", status ); time(&time_val); |
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.
switch ( msgsb.msgtype ) { case rtr_mt_accepted: fprintf(fpLog, " txn accepted at : %s", ctime( &time_val)); break; case rtr_mt_rejected: fprintf(fpLog, " txn rejected at : %s", ctime( &time_val)); /* Resend same sequence_number after reject */ send_msg.sequence_number--; txn_cnt++; break; default: fprintf(fpLog, " unexpected status on rtr_mt_accepted message\n"); fprintf(fpLog, " %s\n", rtr_error_text(receive_msg.receive_status_msg.status); break; } } } /* end of for loop */ |
Previous | Next | Contents | Index |