Programming an Oracle Tuxedo ATMI Application Using C

     Previous  Next    Open TOC in new window    View as PDF - New Window  Get Adobe Reader - New Window
Content starts here

Writing Servers

This topic includes the following sections:

 


Oracle Tuxedo System main( )

To facilitate the development of ATMI servers, the Oracle Tuxedo system provides a predefined main() routine for server load modules. When you execute the buildserver command, the main() routine is automatically included as part of the server.

Note: The main() routine that the system provides is a closed abstraction; you cannot modify it.

In addition to joining and exiting from an application, the predefined main() routine accomplishes the following tasks on behalf of the server.

As indicated above, the main() routine handles all of the details associated with joining and exiting from an application, managing buffers and transactions, and handling communication.

Note: Because the system-supplied main() accomplishes the work of joining and leaving the application, you should not include calls to the tpinit() or tpterm() function in your code. If you do, the function encounters an error and returns TPEPROTO in tperrno. For more information on the tpinit() or tpterm() function, refer to Writing Clients.

 


System-Supplied Server and Services

The main() routine provides one system-supplied ATMI server, AUTHSVR, and two subroutines, tpsvrinit() and tpsvrdone(). The default versions of all three, which are described in the following sections, can be modified to suit your application.

Notes: If you want to write your own versions of tpsvrinit() and tpsvrdone(), remember that the default versions of these two routines call tx_open() and tx_close(), respectively. If you write a new version of tpsvrinit() that calls tpopen() rather than tx_open(), you should also write a new version of tpsvrdone() that calls tpclose(). In other words, both functions in an open/close pair must belong to the same set.
Note: In addition to the subroutines described in this topic, the system provides two subroutines called tpsvrthrinit(3c) and tpsvrthrdone(3c). For more information, refer to Programming a Multithreaded and Multicontexted ATMI Application.

System-Supplied Server: AUTHSVR( )

You can use the AUTHSVR(5) server to provide individual client authentication for an application. The tpinit() function calls this server when the level of security for the application is TPAPPAUTH.

The service in AUTHSVR looks in the data field of the TPINIT buffer for a user password (not to be confused with the application password specified in the passwd field of the TPINIT buffer). By default, the system takes the string in data and searches for a matching string in the /etc/passwd file.

When called by a native-site client, tpinit() forwards the data field as it is received. This means that if the application requires the password to be encrypted, the client program must be coded accordingly.

When called by a Workstation client, tpinit() encrypts the data before sending it across the network.

System-Supplied Services: tpsvrinit( ) Function

When a server is booted, the Oracle Tuxedo system main() calls tpsvrinit(3c) during its initialization phase, before handling any service requests.

If an application does not provide a custom version of this function within the server, the system uses the default function provided by main(), which opens the resource manager and logs an entry in the central event log indicating that the server has successfully started. The central user log is an automatically generated file to which processes can write messages by calling the userlog(3c) function. Refer to Managing Errors for more information on the central event log.

You can use the tpsvrinit() function for any initialization processes that might be required by an application, such as the following:

The following sections provide code samples showing how these initialization tasks are performed through calls to tpsvrinit(). Although it is not illustrated in the following examples, message exchanges can also be performed within this routine. However, tpsvrinit() fails if it returns with asynchronous replies pending. In this case, the replies are ignored by the Oracle Tuxedo system, and the server exits gracefully.

You can also use the tpsvrinit() function to start and complete transactions, as described in Managing Errors.

Use the following signature to call the tpsvrinit() function:

int
tpsvrinit(int argc, char **argv)

Receiving Command-line Options

When a server is booted, its first task is to read the server options specified in the configuration file up to the point that it receives an EOF indication. To do so, the server calls the getopt(3) UNIX function. The presence of a double dash (--) on the command line causes the getopt() function to return an EOF. The getopt function places the argv index of the next argument to be processed in the external variable optind. The predefined main() then calls tpsvrinit().

Listing 5-1 shows how the tpsvrinit() function is used to receive command-line options.

Listing 5-1 Receiving Command-line Options in tpsvrinit( )
tpsvrinit(argc, argv)
int argc;
char **argv;
{
        int c;
        extern char *optarg;
        extern int optind;
        .
        .
        .
        while((c = getopt(argc, argv, "f:x:")) != EOF)
            switch(c){
            .
            .
            .
            }
        .
        .
        .
}

When main() calls tpsvrinit(), it picks up any arguments that follow the double dash (--) on the command line. In the example above, options f and x each takes an argument, as indicated by the colon. optarg points to the beginning of the option argument. The switch statement logic is omitted.

Opening a Resource Manager

The following example illustrates another common use of tpsvrinit(): opening a resource manager. The Oracle Tuxedo system provides functions to open a resource manager, tpopen(3c) and tx_open(3c). It also provides the complementary functions, tpclose(3c) and tx_close(3c). Applications that use these functions to open and close their resource managers are portable in this respect. They work by accessing the resource manager instance-specific information that is available in the configuration file.

Note: If writing a multithreaded server, you must use the tpsvrthrinit() function to open a resource manager, as described in Programming a Multithreaded and Multicontexted ATMI Application.

These function calls are optional and can be used in place of the resource manager specific calls that are sometimes part of the Data Manipulation Language (DML) if the resource manager is a database. Note the use of the userlog(3c) function to write to the central event log.

Note: To create an initialization function that both receives command-line options and opens a database, combine the following example shown in Listing 5-2 with the previous example.
Listing 5-2 Opening a Resource Manager in tpsvrinit( )
tpsvrinit()
{

    /* Open database */

    if (tpopen() == -1) {
          (void)userlog("tpsvrinit: failed to open database: ");
          switch (tperrno) {
             case TPESYSTEM:
                 (void)userlog("System error\n");
                 break;
             case TPEOS:
                 (void)userlog("Unix error %d\n",Uunixerr);
                 break;
             case TPEPROTO:
                 (void)userlog("Called in improper context\n");
                 break;
             case TPERMERR:
                 (void)userlog("RM failure\n");
                 break;
       }
       return(-1); /* causes the server to exit */
   }
  return(0);
}

To guard against errors that may occur during initialization, tpsvrinit() can be coded to allow the server to exit gracefully before starting to process service requests.

System-Supplied Services: tpsvrdone( ) Function

The tpsvrdone() function calls tpclose() to close the resource manager, similarly to the way tpsvrinit() calls tpopen() to open it.

Note: If writing a multithreaded server, you must use the tpsvrthrdone() command to open a resource manager, as described in Programming a Multithreaded and Multicontexted ATMI Application.

Use the following signature to call the tpsvrdone() function:

void
tpsvrdone() /* Server termination routine */

The tpsvrdone() function requires no arguments.

If an application does not define a closing routine for tpsvrdone(), the Oracle Tuxedo system calls the default routine supplied by main(). This routine calls tx_close() and userlog() to close the resource manager and write to the central event log, respectively. The message sent to the log indicates that the server is about to exit.

tpsvrdone() is called after the server has finished processing service requests but before it exits. Because the server is still part of the system, further communication and transactions can take place within the routine, as long as certain rules are followed. These rules are covered in Managing Errors.

Listing 5-3 illustrates how to use the tpsvrdone() function to close a resource manager and exit gracefully.

Listing 5-3 Closing a Resource Manager with tpsvrdone( )
void
tpsvrdone()
{

     /* Close the database */
     if(tpclose() == -1)
           (void)userlog("tpsvrdone: failed to close database: ");
           switch (tperrno) {
                   case TPESYSTEM:
                          (void)userlog("BEA TUXEDO error\n");
                          break;
                   case TPEOS:
                          (void)userlog("Unix error %d\n",Uunixerr);
                          break;
                   case TPEPROTO:
                          (void)userlog("Called in improper context\n");
                          break;
                   case TPERMERR:
                          (void)userlog("RM failure\n");
                          break;
          }
           return;
    }
    return;
}

 


Guidelines for Writing Servers

Because the communication details are handled by the Oracle Tuxedo system main() routine, you can concentrate on the application service logic rather than communication implementation. For compatibility with the system-supplied main(), however, application services must adhere to certain conventions. These conventions are referred to, collectively, as the service template for coding service routines. They are summarized in the following list. Refer to the tpservice(3c) reference page in the Oracle Tuxedo ATMI C Function Reference for more information on these conventions.

 


Defining a Service

You must define every service routine as a function that receives one argument consisting of a pointer to a TPSVCINFO structure. The TPSVCINFO structure is defined in the atmi.h header file and includes the following information:

char      name[32];
long flags;
char *data;
long len;
int cd;
int appkey;
CLIENTID cltid;

Table 5-1 summarizes the TPSVCINFO data structure.

Table 5-1 TPSVCINFO Data Structure
Field
Description
name
Specifies, to the service routine, the name used by the requesting process to invoke the service.
flags
Notifies the service if it is in transaction mode or if the caller is expecting a reply. The various ways in which a service can be placed in transaction mode are discussed in Writing Global Transactions.
The TPTRAN flag indicates that the service is in transaction mode. When a service is invoked through a call to tpcall() or tpacall() with the flags parameter set to TPNOTRAN, the service cannot participate in the current transaction. However, it is still possible for the service to be executed in transaction mode. That is, even when the caller sets the TPNOTRAN communication flag, it is possible for TPTRAN to be set in svcinfo->flags. For an example of such a situation, refer to Writing Global Transactions.
The flags member is set to TPNOREPLY if the service is called by tpacall() with the TPNOREPLY communication flag set. If a called service is part of the same transaction as the calling process, it must return a reply to the caller.
data
Pointer to a buffer that was previously allocated by tpalloc() within the main(). This buffer is used to receive request messages. However, it is recommended that you also use this buffer to send back reply messages or forward request messages.
len
Contains the length of the request data that is in the buffer referenced by the data field.
cd
For conversational communication, specifies the connection descriptor.
appkey
Reserved for use by the application. If application-specific authentication is part of your design, the application-specific authentication server, which is called when a client joins the application, should return a client authentication key as well as an indication of success or failure. The Oracle Tuxedo system holds the appkey on behalf of the client and passes the information to subsequent service requests in this field. By the time the appkey is passed to a service, the client has already been authenticated. However, the appkey field can be used within a service to identify the user invoking the service or some other parameters associated with the user.
If this field is not used, the system assigns it a default value of -1.
cltid
Structure of type CLIENTID used by the system to carry the identification of the client. You should not modify this structure.

When the data field in the TPSVCINFO structure is being accessed by a process, the following buffer types must agree:

Listing 5-4 illustrates a typical service definition. This code is borrowed from the ABAL (account balance) service routine that is part of the banking application provided with the Oracle Tuxedo software. ABAL is part of the BAL server.

Listing 5-4 Typical Service Definition
#include <stdio.h>      /* UNIX */
#include <atmi.h> /* BEA Tuxedo System */
#include <sqlcode.h> /* BEA Tuxedo System */
#include "bank.flds.h" /* bankdb fields */
#include "aud.h" /* BANKING view defines */

EXEC SQL begin declare section;
static long branch_id; /* branch id */
static float bal; /* balance */
EXEC SQL end declare section;

/*
* Service to find sum of the account balances at a SITE
*/

void
#ifdef __STDC__
ABAL(TPSVCINFO *transb)

#else

ABAL(transb)
TPSVCINFO *transb;
#endif

{
     struct aud *transv; /* view of decoded message */

     /* Set pointer to TPSVCINFO data buffer */

     transv = (struct aud *)transb->data;

     set the consistency level of the transaction

     /* Get branch id from message, do query */

     EXEC SQL declare acur cursor for
      select SUM(BALANCE) from ACCOUNT;
     EXEC SQL open acur; /* open */
     EXEC SQL fetch acur into :bal; /* fetch */
     if (SQLCODE != SQL_OK) { /* nothing found */
      (void)strcpy (transv->ermsg,"abal failed in sql aggregation");
      EXEC SQL close acur;
      tpreturn(TPFAIL, 0, transb->data, sizeof(struct aud), 0);
     }
     EXEC SQL close acur;
     transv->balance = bal;
     tpreturn (TPSUCCESS, 0, transb->data, sizeof(struct aud), 0);
}

In the preceding example, the application allocates a request buffer on the client side by a call to tpalloc() with the type parameter set to VIEW and the subtype set to aud. The ABAL service is defined as supporting the VIEW typed buffer. The BUFTYPE parameter is not specified for ABAL and defaults to ALL. The ABAL service allocates a buffer of the type VIEW and assigns the data member of the TPSVCINFO structure that was passed to the ABAL subroutine to the buffer pointer. The ABAL server retrieves the appropriate data buffer by accessing the corresponding data member, as illustrated in the preceding example.

Note: After the buffer is retrieved, but before the first attempt is made to access the database, the service must specify the consistency level of the transaction. Refer to Writing Global Transactions for more details on transaction consistency levels.

Example: Checking the Buffer Type

The code example in this section shows how a service can access the data buffer defined in the TPSVCINFO structure to determine its type by using the tptypes() function. (This process is described in Checking for Buffer Type.) The service also checks the maximum size of the buffer to determine whether or not to reallocate space for the buffer.

This example is derived from the ABAL service that is part of the banking application provided with the Oracle Tuxedo software. It shows how the service is written to accept a request either as an aud VIEW or an FML buffer. If its attempt to determine the message type fails, the service returns a string with an error message plus an appropriate return code; otherwise it executes the segment of code that is appropriate for the buffer type. For more information on the tpreturn() function, refer to Terminating a Service Routine.

Listing 5-5 Checking for Buffer Type
#define TMTYPERR 1 /* return code indicating tptypes failed */
#define INVALMTY 2 /* return code indicating invalid message type */

void
ABAL(transb)

TPSVCINFO *transb;

{
   struct aud *transv; /* view message */
   FBFR *transf; /* fielded buffer message */
   int repc; /* tpgetrply return code */
   char typ[TMTYPELEN+1], subtyp[TMSTYPELEN+1]; /* type, subtype of message */
   char *retstr; /* return string if tptypes fails */

/* find out what type of buffer sent */
   if (tptypes((char *)transb->data, typ, subtyp) == -1) {
      retstr=tpalloc("STRING", NULL, 100);
      (void)sprintf(retstr,
      "Message garbled; tptypes cannot tell what type message\n");
      tpreturn(TPFAIL, TMTYPERR, retstr, 100, 0);
   }
/* Determine method of processing service request based on type */
   if (strcmp(typ, "FML") == 0) {
      transf = (FBFR *)transb->data;
... code to do abal service for fielded buffer ...
tpreturn succeeds and sends FML buffer in reply
   }
   else if (strcmp(typ, "VIEW") == 0 && strcmp(subtyp, "aud") == 0) {
      transv = (struct aud *)transb->data;
... code to do abal service for aud struct ...
tpreturn succeeds and sends aud view buffer in reply
   }
   else {
      retstr=tpalloc("STRING", NULL, 100);
      (void)sprintf(retstr,
      "Message garbled; is neither FML buffer nor aud view\n");
      tpreturn(TPFAIL, INVALMTY, retstr, 100, 0);
   }
}

Example: Checking the Priority of the Service Request

Note: The tpgprio() and tpsprio() functions, used for getting and setting priorities, respectively, are described in detail in Setting and Getting Message Priorities.

The example code in this section shows how a service called PRINTER tests the priority level of the request just received using the tpgprio() function. Then, based on the priority level, the application routes the print job to the appropriate destination printer and pipes the contents of pbufArrow symboldata to that printer.

The application queries pbufArrow symbolflags to determine whether a reply is expected. If so, it returns the name of the destination printer to the client. For more information on the tpreturn() function, refer to Terminating a Service Routine.

Listing 5-6 Checking the Priority of a Received Request
#include <stdio.h>
#include "atmi.h"

char *roundrobin();

PRINTER(pbuf)

TPSVCINFO *pbuf; /* print buffer */

{
char prname[20], ocmd[30]; /* printer name, output command */
long rlen; /* return buffer length */
int prio; /* priority of request */
FILE *lp_pipe; /* pipe file pointer */

prio=tpgprio();
if (prio <= 20)
   (void)strcpy(prname,"bigjobs"); /* send low priority (verbose)
                                     jobs to big comp. center
                                     laser printer where operator
                                     sorts output and puts it
                                     in a bin */
else if (prio <= 60)
   (void)strcpy(prname,roundrobin()); /* assign printer on a
                                        rotating basis to one of
                                        many local small laser printers
                                        where output can be picked
                                        up immediately; roundrobin() cycles
                                        through list of printers */
else
   (void)strcpy(prname,"hispeed");
                                   /* assign job to high-speed laser
                                      printer; reserved for those who
                                      need verbose output on a daily,
                                      frequent basis */

(void)sprintf(ocmd, "lp -d%s", prname); /* output lp(1) command */
lp_pipe = popen(ocmd, "w"); /* create pipe to command */
(void)fprintf(lp_pipe, "%s", pbuf->data); /* print output there */
(void)pclose(lp_pipe); /* close pipe */

if ((pbuf->flags & TPNOREPLY))
   tpreturn(TPSUCCESS, 0, NULL, 0, 0);
rlen = strlen(prname) + 1;
pbuf->data = tprealloc(pbuf->data, rlen); /* ensure enough space for name */
(void)strcpy(pbuf->data, prname);
tpreturn(TPSUCCESS, 0, pbuf->data, rlen, 0);

char *
roundrobin()

{
static char *printers[] = {"printer1", "printer2", "printer3", "printer4"};
static int p = 0;

if (p > 3)
   p=0;
return(printers[p++]);
}

 


Terminating a Service Routine

The tpreturn(3c), tpcancel(3c), and tpforward(3c) functions specify that a service routine has completed with one of the following actions:

Sending Replies

The tpreturn(3c) function marks the end of the service routine and sends a message to the requester. Use the following signature to call the tpreturn() function:

void
tpreturn(int rval, int rcode, char *data, long len, long flags)

Table 5-2 describes the arguments to the tpreturn() function.

Table 5-2 tpreturn( ) Function Arguments
Argument
Description
rval
Indicates whether or not the service has completed successfully on an application-level. The value is an integer that is represented by a symbolic name. Valid settings include:
  • TPSUCCESS—the calling function succeeded. The function stores the reply message in the caller’s buffer. If there is a reply message, it is in the caller’s buffer.
  • TPFAIL (default)—the service terminated unsuccessfully. The function reports an error message to the client process waiting for the reply. In this case, the client’s tpcall() or tpgetrply() function call fails and the system sets the tperrno(5) variable to TPESVCFAIL to indicate an application-defined failure. If a reply message was expected, it is available in the caller’s buffer.
  • TPEXIT—the service terminated unsuccessfully. The function reports an error message to the client process waiting for the reply, and exits.
For a description of the effect that the value of this argument has on global transactions, refer to Writing Global Transactions.
rcode
Returns an application-defined return code to the caller. The client can access the value returned in rcode by querying the tpurcode(5) global variable. The function returns this code regardless of success or failure.
data
Pointer to the reply message that is returned to the client process. The message buffer must have been allocated previously by tpalloc().
If you use the same buffer that was passed to the service in the SVCINFO structure, you need not be concerned with buffer allocation or disposition because both are handled by the system-supplied main(). You cannot free this buffer using the tpfree() command; any attempt to do so quietly fails. You can resize the buffer using the tprealloc() function.
If you use another buffer (that is, a buffer other than the one passed to the service routine) to return the message, it is your responsibility to allocate it. The system frees the buffer automatically when the application calls the tpreturn() function.
If no reply message needs to be returned, set this argument to the NULL pointer.

Note: If no reply is expected by the client (that is, if TPNOREPLY was set), the tpreturn() function ignores the data and len arguments and returns control to main().

len
Length of the reply buffer. The application accesses the value of this argument through the olen parameter of the tpcall() function or the len parameter of the tpgetrply() function.
Acting as the client, the process can use this returned value to determine whether the reply buffer has grown.
If a reply is expected by the client and there is no data in the reply buffer (that is, if the data argument is set to the NULL pointer), the function sends a reply with zero length, without modifying the client’s buffer.
The system ignores the value of this argument if the data argument is not specified.
flag
Currently not used.

The primary function of a service routine is to process a request and return a reply to a client process. It is not necessary, however, for a single service to do all the work required to perform the requested function. A service can act as a requester and pass a request call to another service the same way a client issues the original request: through calls to tpcall() or tpacall().

Note: The tpcall() and tpacall() functions are described in detail in Writing Request/Response Clients and Servers.

When tpreturn() is called, control always returns to main(). If a service has sent requests with asynchronous replies, it must receive all expected replies or invalidate them with tpcancel() before returning control to main(). Otherwise, the outstanding replies are automatically dropped when they are received by the Oracle Tuxedo system main(), and an error is returned to the caller.

If the client invokes the service with tpcall(), after a successful call to tpreturn(), the reply message is available in the buffer referenced by *odata. If tpacall() is used to send the request, and tpreturn() returns successfully, the reply message is available in the tpgetrply() buffer that is referenced by *data.

If a reply is expected and tpreturn() encounters errors while processing its arguments, it sends a failed message to the calling process. The caller detects the error by checking the value placed in tperrno. In the case of failed messages, the system sets tperrno to TPESVCERR. This situation takes precedence over the value of the tpurcode global variable. If this type of error occurs, no reply data is returned, and both the contents and length of the caller’s output buffer remain unchanged.

If tpreturn() returns a message in a buffer of an unknown type or a buffer that is not allowed by the caller (that is, if the call is made with flags set to TPNOCHANGE), the system returns TPEOTYPE in tperrno(5). In this case, application success or failure cannot be determined, and the contents and length of the output buffer remain unchanged.

The value returned in the tpurcode(5) global variable is not relevant if the tpreturn() function is invoked and a timeout occurs for the call waiting for the reply. This situation takes precedence over all others in determining the value that is returned in tperrno(5). In this case, tperrno(5) is set to TPETIME and the reply data is not sent, leaving the contents and length of the caller’s reply buffer unchanged. There are two types of timeouts in the Oracle Tuxedo system: blocking and transaction timeouts (discussed in Writing Global Transactions).

The example code in this section shows the TRANSFER service that is part of the XFER server. Basically, the TRANSFER service makes synchronous calls to the WITHDRAWAL and DEPOSIT services. It allocates a separate buffer for the reply message since it must use the request buffer for the calls to both the WITHDRAWAL and the DEPOSIT services. If the call to WITHDRAWAL fails, the service writes the message cannot withdraw on the status line of the form, frees the reply buffer, and sets the rval argument of the tpreturn() function to TPFAIL. If the call succeeds, the debit balance is retrieved from the reply buffer.

Note: In the following example, the application moves the identifier for the “destination account” (which is retrieved from the cr_id variable) to the zeroth occurrence of the ACCOUNT_ID field in the transf fielded buffer. This move is necessary because this occurrence of the field in an FML buffer is used for data-dependent routing. Refer to Setting Up an Oracle Tuxedo Application for more information.

A similar scenario is followed for the call to DEPOSIT. On success, the service frees the reply buffer that was allocated in the service routine and sets the rval argument to TPSUCCESS, returning the pertinent account information to the status line.

Listing 5-7 tpreturn( ) Function
#include <stdio.h>      /* UNIX */
#include <string.h> /* UNIX */
#include "fml.h" /* BEA Tuxedo System */
#include "atmi.h" /* BEA Tuxedo System */
#include "Usysflds.h" /* BEA Tuxedo System */
#include "userlog.h" /* BEA Tuxedo System */
#include "bank.h" /* BANKING #defines */
#include "bank.flds.h" /* bankdb fields */


/*
* Service to transfer an amount from a debit account to a credit
* account
*/

void
#ifdef __STDC__
TRANSFER(TPSVCINFO *transb)

#else

TRANSFER(transb)
TPSVCINFO *transb;
#endif

{
   FBFR *transf; /* fielded buffer of decoded message */
   long db_id, cr_id; /* from/to account id’s */
   float db_bal, cr_bal; /* from/to account balances */
   float tamt; /* amount of the transfer */
   FBFR *reqfb; /* fielded buffer for request message*/
   int reqlen; /* length of fielded buffer */
   char t_amts[BALSTR]; /* string for transfer amount */
   char db_amts[BALSTR]; /* string for debit account balance */
   char cr_amts[BALSTR]; /* string for credit account balance */

/* Set pointr to TPSVCINFO data buffer */
transf = (FBFR *)transb->data;

/* Get debit (db_id) and credit (cr_id) account IDs */

/* must have valid debit account number */
if (((db_id = Fvall(transf, ACCOUNT_ID, 0)) < MINACCT) || (db_id > MAXACCT)) {
   (void)Fchg(transf, STATLIN, 0,"Invalid debit account number",(FLDLEN)0);
   tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}
/* must have valid credit account number */
if ((cr_id = Fvall(transf, ACCOUNT_ID, 1)) < MINACCT || cr_id > MAXACCT) {
   (void)Fchg(transf,STATLIN, 0,"Invalid credit account number",(FLDLEN)0);
   tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}

/* get amount to be withdrawn */
if (Fget(transf, SAMOUNT, 0, t_amts, < 0) 0 || strcmp(t_amts,"") == 0) {
   (void)Fchg(transf, STATLIN, 0, "Invalid amount",(FLDLEN)0);
   tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}
(void)sscanf(t_amts,"%f",tamt);

/* must have valid amount to transfer */
if (tamt = 0.0) {
   (void)Fchg(transf, STATLIN, 0,
      "Transfer amount must be greater than $0.00",(FLDLEN)0);
    tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}

/* make withdraw request buffer */
if ((reqfb = (FBFR *)tpalloc("FML",NULL,transb->len)) == (FBFR *)NULL) {
   (void)userlog("tpalloc failed in transfer\n");
   (void)Fchg(transf, STATLIN, 0,
      "unable to allocate request buffer", (FLDLEN)0);
   tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}
reqlen = Fsizeof(reqfb);

/* put ID in request buffer */
(void)Fchg(reqfb,ACCOUNT_ID,0,(char *)&db_id, (FLDLEN)0);

/* put amount in request buffer */
(void)Fchg(reqfb,SAMOUNT,0,t_amts, (FLDLEN)0);

/* increase the priority of withdraw call */
if (tpsprio(PRIORITY, 0L) == -1)
   (void)userlog("Unable to increase priority of withdraw\n");

if (tpcall("WITHDRAWAL", (char *)reqfb,0, (char **)&reqfb,
      (long *)&reqlen,TPSIGRSTRT) == -1) {
   (void)Fchg(transf, STATLIN, 0,
         "Cannot withdraw from debit account", (FLDLEN)0);
   tpfree((char *)reqfb);
   tpreturn(TPFAIL, 0,transb->data, 0L, 0);
}

/* get "debit" balance from return buffer */

(void)strcpy(db_amts, Fvals((FBFR *)reqfb,SBALANCE,0));
void)sscanf(db_amts,"%f",db_bal);
if ((db_amts == NULL) || (db_bal < 0.0)) {
   (void)Fchg(transf, STATLIN, 0,
         "illegal debit account balance", (FLDLEN)0);
   tpfree((char *)reqfb);
   tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}

/* put deposit account ID in request buffer */
(void)Fchg(reqfb,ACCOUNT_ID,0,(char *)&cr_id, (FLDLEN)0);

/* put transfer amount in request buffer */
(void)Fchg(reqfb,SAMOUNT,0,t_amts, (FLDLEN)0);

/* Up the priority of deposit call */
if (tpsprio(PRIORITY, 0L) == -1)
(void)userlog("Unable to increase priority of deposit\n");

/* Do a tpcall to deposit to second account */
if (tpcall("DEPOSIT", (char *)reqfb, 0, (char **)&reqfb,
        (long *)&reqlen, TPSIGRSTRT) == -1) {
   (void)Fchg(transf, STATLIN, 0,
           "Cannot deposit into credit account", (FLDLEN)0);
   tpfree((char *)reqfb);
   tpreturn(TPFAIL, 0,transb->data, 0L, 0);
}

/* get "credit" balance from return buffer */

(void)strcpy(cr_amts, Fvals((FBFR *)reqfb,SBALANCE,0));
(void)sscanf(cr_amts,"%f",&cr_bal);
if ((cr_amts == NULL) || (cr_bal 0.0)) {
   (void)Fchg(transf, STATLIN, 0,
         "Illegal credit account balance", (FLDLEN)0);
    tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}

/* set buffer for successful return */
(void)Fchg(transf, FORMNAM, 0, "CTRANSFER", (FLDLEN)0);
(void)Fchg(transf, SAMOUNT, 0, Fvals(reqfb,SAMOUNT,0), (FLDLEN)0);
(void)Fchg(transf, STATLIN, 0, "", (FLDLEN)0);
(void)Fchg(transf, SBALANCE, 0, db_amts, (FLDLEN)0);
(void)Fchg(transf, SBALANCE, 1, cr_amts, (FLDLEN)0);
tpfree((char *)reqfb);
tpreturn(TPSUCCESS, 0,transb->data, 0L, 0);
}

Invalidating Descriptors

If a service calling tpgetrply() (described in detail in Writing Request/Response Clients and Servers) fails with TPETIME and decides to cancel the request, it can invalidate the descriptor with a call to tpcancel(3c). If a reply subsequently arrives, it is silently discarded.

Use the following signature to call the tpcancel() function:

void
tpcancel(int cd)

The cd (call descriptor) argument identifies the process you want to cancel.

tpcancel() cannot be used for transaction replies (that is, for replies to requests made without the TPNOTRAN flag set). Within a transaction, tpabort(3c) does the same job of invalidating the transaction call descriptor.

The following example shows how to invalidate a reply after timing out.

Listing 5-8 Invalidating a Reply After Timing Out
int cd1;
.
.
.
      if ((cd1=tpacall(sname, (char *)audv, sizeof(struct aud),
         TPNOTRAN)) == -1) {
      .
      .
      .
      }
      if (tpgetrply(cd1, (char **)&audv,&audrl, 0) == -1) {
      if (tperrno == TPETIME) {
          tpcancel(cd1);
          .
          .
          .
         }
      }
      tpreturn(TPSUCCESS, 0,NULL, 0L, 0);

Forwarding Requests

The tpforward(3c) function allows a service to forward a request to another service for further processing.

Use the following signature to call the tpforward() function:

void
tpforward(char *svc, char *data, long len, long flags)

Table 5-3 describes the arguments to the tpreturn() function.

Table 5-3 tpreturn() Function Arguments
Argument
Description
svc
Character pointer to the name of the service to which the request is to be forwarded.
data
Pointer to the reply message that is returned to the client process. The message buffer must have been allocated previously by tpalloc().
If you use the same buffer that was passed to the service in the SVCINFO structure, you need not be concerned with buffer allocation or disposition because both are handled by the system-supplied main(). You cannot free this buffer using the tpfree() command; any attempt to do so quietly fails. You can resize the buffer using the tprealloc() function.
If you use another buffer (that is, a buffer other than the one that is passed to the service routine) to return the message, it is your responsibility to allocate it. The system frees the buffer automatically when the application calls the tpreturn() function.
If no reply message needs to be returned, set this argument to the NULL pointer.

Note: If no reply is expected by the client (that is, if TPNOREPLY was set), the tpreturn() function ignores the data and len arguments and returns control to main().

len
Length of the reply buffer. The application accesses the value of this argument through the olen parameter of the tpcall() function or the len parameter of the tpgetrply() function.
Acting as the client, the process can use this returned value to determine whether the reply buffer has grown.
If a reply is expected by the client and there is no data in the reply buffer (that is, if the data argument is set to the NULL pointer), the function sends a reply with zero length, without modifying the client’s buffer.
The system ignores the value of this argument if the data argument is not specified.
flag
Currently not used.

The functionality of tpforward() differs from a service call: a service that forwards a request does not expect a reply. The responsibility for providing the reply is passed to the service to which the request has been forwarded. The latter service sends the reply to the process that originated the request. It becomes the responsibility of the last server in the forward chain to send the reply to the originating client by invoking tpreturn().

Figure 5-1 shows one possible sequence of events when a request is forwarded from one service to another. Here a client initiates a request using the tpcall() function and the last service in the chain (SVC_C) provides a reply using the tpreturn() function.

Figure 5-1 Forwarding a Request

Forwarding a Request

Service routines can forward requests at specified priorities in the same manner that client processes send requests, by using the tpsprio() function.

When a process calls tpforward(), the system-supplied main() regains control, and the server process is free to do more work.

Note: If a server process is acting as a client and a reply is expected, the server is not allowed to request services from itself. If the only available instance of the desired service is offered by the server process making the request, the call fails, indicating that a recursive call cannot be made. However, if a service routine sends a request (to itself) with the TPNOREPLY communication flag set, or if it forwards the request, the call does not fail because the service is not waiting for itself.

Calling tpforward() can be used to indicate success up to that point in processing the request. If no application errors have been detected, you can invoke tpforward(), otherwise, you can call tpreturn() with rval set to TPFAIL.

Listing 5-9 is borrowed from the OPEN_ACCT service routine which is part of the ACCT server. This example illustrates how the service sends its data buffer to the DEPOSIT service by calling tpforward(). The code shows how to test the SQLCODE to determine whether the account insertion is successful. If the new account is added successfully, the branch record is updated to reflect the new account, and the data buffer is forwarded to the DEPOSIT service. On failure, tpreturn() is called with rval set to TPFAIL and the failure is reported on the status line of the form.

Listing 5-9 tpforward( ) Function
 ...
/* set pointer to TPSVCINFO data buffer */
transf = (FBFR *)transb->data;
...
/* Insert new account record into ACCOUNT*/
account_id = ++last_acct; /* get new account number */
tlr_bal = 0.0; /* temporary balance of 0 */
EXEC SQL insert into ACCOUNT (ACCOUNT_ID, BRANCH_ID, BALANCE,
ACCT_TYPE, LAST_NAME, FIRST_NAME, MID_INIT, ADDRESS, PHONE) values
(:account_id, :branch_id, :tlr_bal, :acct_type, :last_name,
      :first_name, :mid_init, :address, :phone);
if (SQLCODE != SQL_OK) { /* Failure to insert */
      (void)Fchg(transf, STATLIN, 0,
          "Cannot update ACCOUNT", (FLDLEN)0);
      tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}

/* Update branch record with new LAST_ACCT */

EXEC SQL update BRANCH set LAST_ACCT = :last_acct where BRANCH_ID = :branch_id;
if (SQLCODE != SQL_OK) { /* Failure to update */
       (void)Fchg(transf, STATLIN, 0,
           "Cannot update BRANCH", (FLDLEN)0);
       tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}
/* up the priority of the deposit call */
if (tpsprio(PRIORITY, 0L) == -1)
     (void)userlog("Unable to increase priority of deposit\n");

/* tpforward same buffer to deposit service to add initial balance */
tpforward("DEPOSIT", transb->data, 0L, 0);

 


Advertising and Unadvertising Services

When a server is booted, it advertises the services it offers based on the values specified for the CLOPT parameter in the configuration file.

Note: The services that a server may advertise are initially defined when the buildserver command is executed. The -s option allows a comma-separated list of services to be specified. It also allows you to specify a function with a name that differs from that of the advertised service that is to be called to process the service request. Refer to the buildserver(1) in the Oracle Tuxedo Command Reference for more information.

The default specification calls for the server to advertise all services with which it was built. Refer to the UBBCONFIG(5) or servopts(5) reference page in the File Formats, Data Descriptions, MIBs, and System Processes Reference for more information.

Because an advertised service uses a service table entry in the bulletin board, and can therefore be resource-expensive, an application may boot its servers in such a way that only a subset of the services offered are available. To limit the services available in an application, define the CLOPT parameter, within the appropriate entry in the SERVERS section of the configuration file, to include the desired services in a comma-separated list following the -s option. The -s option also allows you to specify a function with a name other than that of the advertised service to be called to process the request. Refer to the servopts(5) reference page in the File Formats, Data Descriptions, MIBs, and System Processes Reference for more information.

An Oracle Tuxedo application administrator can use the advertise and unadvertise commands of tmadmin(1) to control the services offered by servers. The tpadvertise() and tpunadvertise() functions enable you to dynamically control the advertisement of a service in a request/response or conversational server. The service to be advertised (or unadvertised) must be available within the same server as the service making the request.

Advertising Services

Use the following signature to call the tpadvertise(3c) function:

int
tpadvertise(char *svcname, void *func)

Table 5-4 describes the arguments to the tpadvertise() function.

Table 5-4 tpadvertise( ) Function Arguments
Argument
Description
svcname
Pointer to the name of the service to be advertised. The service name must be a character string of up to 127 characters. Names longer than 127 characters are truncated. The NULL string is not a valid value. If it is specified, an error (TPEINVAL) results.
func
Pointer to the address of an Oracle Tuxedo system function that is called to perform a service. Frequently, this name is the same as the name of the service. The NULL string is not a valid value. If it is specified, an error results.

Unadvertising Services

The tpunadvertise(3c) function removes the name of a service from the service table of the bulletin board so that the service is no longer advertised.

Use the following signature for the tpunadvertise() function:

tpunadvertise(char *svcname)
char *svcname;

The tpunadvertise() function contains one argument, which is described in Table 5-5.

Table 5-5 tpunadvertise( ) FunctionArguments
Argument
Description
svcname
Pointer to the name of the service to be advertised. The service name must be a character string of up to 127 characters. Names longer than 127 characters are truncated. The NULL string is not a valid value. If it is specified, an error (TPEINVAL) results.

Example: Dynamic Advertising and Unadvertising of a Service

Listing 5-10 shows how to use the tpadvertise() function. In this example, a server called TLR is programmed to offer only the service called TLR_INIT when booted. After some initialization, TLR_INIT advertises two services called DEPOSIT and WITHDRAW. Both are performed by the tlr_funcs function, and both are built into the TLR server.

After advertising DEPOSIT and WITHDRAW, TLR_INIT unadvertises itself.

Listing 5-10 Dynamic Advertising and Unadvertising
extern void tlr_funcs()
.
.
.
if (tpadvertise("DEPOSIT", (tlr_funcs)(TPSVCINFO *)) == -1)
       check for errors;
if (tpadvertise("WITHDRAW", (tlr_funcs)(TPSVCINFO *)) == -1)
       check for errors;
if (tpunadvertise("TLR_INIT") == -1)
       check for errors;
tpreturn(TPSUCCESS, 0, transb->data,0L, 0);

 


Building Servers

To build an executable ATMI server, compile your application service subroutines with the Oracle Tuxedo system server adaptor and all other referenced files using the buildserver(1) command.

Note: The Oracle Tuxedo server adaptor accepts messages, dispatches work, and manages transactions (if transactions are enabled).

Use the following syntax for the buildserver command:

buildserver  -o filename -f filenames -l filenames -s -v

Table 5-6 describes the buildserver command-line options:

Table 5-6 buildserver Command-line Options
This Option . . .
Allows You to Specify the . . .
-o filename
Name of the executable output file. The default is a.out.
-f filenames
List of files that are link edited before the Oracle Tuxedo system libraries. You can specify the -f option more than once, and multiple filenames for each occurrence of -f. If you specify a C program file (file.c), it is compiled before it is linked. You can specify other object files (file.o) separately, or in groups in an archive file (file.a).
-l filenames
List of files that are link edited after the Oracle Tuxedo system libraries. You can specify the -l option more than once, and multiple filenames for each occurrence of -l. If you specify a C program file (file.c), it is compiled before it is linked. You can specify other object files (file.o) separately, or in groups in an archive file (file.a).
-r filenames
List of resource manager access libraries that are link edited with the executable server. The application administrator is responsible for predefining all valid resource manager information in the $TUXDIR/updataobj/RM file using the buildtms(1) command. You can specify only one resource manager. Refer to Setting Up an Oracle Tuxedo Application for more information.
-s [service:]function
Name of service or services offered by the server and the name of the function that performs each service. You can specify the -s option more than once, and multiple services for each occurrence of -s. The server uses the specified service names to advertise its services to clients.
Typically, you should assign the same name to both the service and the function that performs that service. Alternatively, you can specify any names. To assign names, use the following syntax: service:function
-t
Specifies that the server is coded in a thread-safe manner and may be booted as multithreaded if specified as such in the configuration file.

Notes: The Oracle Tuxedo libraries are linked in automatically. You do not need to specify the Oracle Tuxedo library names on the command line.
Note: Link editing must be done by running the buildserver command.

The order in which you specify the library files to be link edited is significant: it depends on the order in which functions are called and which libraries contain references to those functions.

By default, the buildserver command invokes the UNIX cc command. You can specify an alternative compile command and set your own flags for the compile and link-edit phases, however, by setting the CC and CFLAGS environment variables, respectively. For more information, refer to Setting Environment Variables.

The following command processes the acct.o application file and creates a server called ACCT that contains two services: NEW_ACCT, which calls the OPEN_ACCT function, and CLOSE_ACCT, which calls a function of the same name.

buildserver – o ACCT – f acct.o – s NEW_ACCT:OPEN_ACCT – s CLOSE_ACCT

See Also

 


Using a C++ Compiler

There are basically two differences between using a C++ compiler and a C compiler to develop application ATMI servers:

Declaring Service Functions

When declaring a service function for a C++ compiler, you must declare it to have “C” linkage using extern “C”. Specify the function prototype as follows:

#ifdef __cplusplus
extern "C"
#endif
MYSERVICE(TPSVCINFO *tpsvcinfo)

By declaring the name of your service with “C” linkage, you ensure that the C++ compiler will not modify the name. Many C++ compilers change the function name to include type information for the parameters and function return.

This declaration also allows you to:

Using Constructors and Destructors

C++ constructors are called to initialize class objects when those objects are created, and destructors are invoked when class objects are destroyed. For automatic (that is, local, non-static) variables that contain constructors and destructors, the constructor is called when the variable comes into scope and the destructor is called when the variable goes out of scope. However, when you call the tpreturn() or tpforward() function, the compiler performs a non-local goto (using longjmp(3)) such that destructors for automatic variables are not called. To avoid this problem, write the application so that you call tpreturn() or tpforward() from the service routine directly (instead of from any functions that are called from the service routine). In addition, one of the following should be true:

In other words, you should define the application so that there are no automatic variables with destructors in scope in the current function or on the stack when the tpreturn() or tpforward() function is called.

For proper handling of global and static variables that contain constructors and destructors, many C++ compilers require that you compile main() using the C++ compiler.

Note: Special processing is included in the main() routine to ensure that any constructors are executed when the program starts and any destructors are executed when the program exits.

Because main() is provided by the Oracle Tuxedo system, you do not compile it directly. To ensure that the file is compiled using C++, you must use the C++ compiler with the buildserver command. By default, the buildserver command invokes the UNIX cc command. You can specify that the buildserver command invoke the C++ compiler, instead, by setting the CC environment variable to the full path name for the C++ compiler. Also, you can set flags for any options that you want to include on the C++ command line by setting the CFLAGS environment variable. For more information, refer to Setting Environment Variables.


  Back to Top       Previous  Next