[Top] [Prev] [Next] [Bottom]

2. Writing Client Programs


About This Chapter

The sections that follow describe the ATMI functions that enable a client program to

The chapter ends with information about how to compile client programs.

Examples Taken from the Sample Application

Many of the examples in this chapter are taken from audit.c, a client program that is part of the sample application.

Depending on command line options, audit.c retrieves either

The syntax of the command line is as follows:

audit {-a|-t} [bid]

audit is the name of the executable created when the audit.c program is compiled. The -a option requests that account balances be retrieved; the -t option specifies the teller balance. If no branch identifier, bid, is included on the command line, the default is to retrieve the total account or teller balance for all the branches of the bank. If the branch identifier is included, a balance of the type specified is retrieved for that branch only.

Preliminaries

Before a client program is ready to join the application, some preliminary processing may be called for to take advantage of BEA TUXEDO system capabilities.

Client Naming

An application can associate both a usrname and a cltname with an execution of a client process. Values furnished for these names are combined by the BEA TUXEDO system with the logical machine identifier (LMID) of the machine where the process runs, in order to establish a unique identification for the process. It is left to the discretion of application developers and programmers to work out ways of acquiring the value for the fields. Once acquired they are passed to tpinit() in a TPINIT buffer. Some possible ways are shown in later examples.

Note: If the process is running outside the administrative domain of the application, that is, if it is running on a workstation connected to the administrative domain, the LMID used is the one for the machine used by the workstation client to access the application.

Once a client process is uniquely identified, client authentication can be implemented, out-of-band messages can be sent to a specific client or to groups of clients via tpnotify(3c) and tpbroadcast(3c), and detailed statistical information can be gathered via tmadmin(1).

Figure 2-1 shows an example of how names might be associated with clients accessing an application. In the example, the application uses the cltname field to indicate a job function.

Figure 2-1 Client Naming

Unsolicited Notification

Unsolicited notification refers to any communication with a client that is not an expected response to a service request (or an error code). The example that comes to mind is a broadcast message to announce that the world is coming to an end in five minutes. Within the client program there are three things you may want to do to handle such messages:

The flag values in the TPINIT buffer are described below in the section called "Joining the Application." tpsetunsol(3c) and tpchkunsol(3c) are shown in examples later in this chapter and are described in the BEA TUXEDO Reference Manual.

Security and Client Authentication

The BEA TUXEDO system provides several levels of security:

Configuration of the security level is the responsibility of the system administrator and is discussed in the book Administering the BEA TUXEDO System. The following paragraphs explain the different levels and discuss what is needed when writing client programs with SECURITY set.

Operating System
For platforms that have underlying security mechanisms, this is the first line of defense. The security level is configured to "NONE." This implies, not that there is no security, but that there are no additional mechanisms (for example, a BEA TUXEDO system password) beyond what the platform provides. The BEA TUXEDO system has the notion of an application administrator who configures the application, starts up the application (servers run with the permissions of this administrator), and monitors the running application, making dynamic changes as necessary. Note that this implies that server programs are "trusted" since they run with the administrator's permissions. This is supported using the underlying operating system login mechanism and read/write permissions on files, directories, and system resources.

Client programs are run directly by the users with their own permissions. However, they normally have access to the administrative configuration file and the interprocess communication mechanisms, such as the Bulletin Board in shared memory, as part of normal processing. This is true whether or not additional BEA TUXEDO system security is configured. For some applications running on platforms supporting it, a more secure approach is to have the files and IPC mechanisms accessible only to the application administrator and to have "trusted" client programs run with the permissions of the administrator (using a setuid mechanism). Combining this with BEA TUXEDO system security will allow the application to "know" who the user is that is making the request. For the most secure environment, only workstation clients should be allowed to access the application; client programs should not be allowed to run on the machines where application server and administrative programs run. BEA TUXEDO system security mechanisms can be used in addition to operating system security to prevent unauthorized access. The additional security can be used to avoid simple violations like someone accessing an unattended terminal. Or it can protect the boundaries of the administrative domain from inter-domain or workstation client access over the network by unauthorized users.

Application Password
This security level requires that every client provide an application password as part of joining the application. The security level is configured to "APP_PW." The administrator must provide an application password when this level is configured and this password can also be changed administratively. It is the responsibility of the administrator to inform users of the application what the password is. If this level of security is used, BEA TUXEDO system-supplied client programs, ud(1) for example, prompt for the application password. Application-written client programs must include code to obtain the password from a user. The password should not be echoed to the user's terminal. The password is placed in clear text in the TPINIT buffer and evaluated when the client calls tpinit() to join the application. Code for handling a password is shown in examples later in this chapter.

User Authentication
The third level of BEA TUXEDO system security is based on authenticating each individual user in addition to providing the application password. The security level is set to "USER_AUTH." This level involves passing user-specific data to an authentication service. Often, the data is a per-user password. The data is automatically encrypted when passed over the network from workstation clients. The default authentication service, "AUTHSVC," is provided by a BEA TUXEDO system-supplied server, AUTHSVR. The operation of AUTHSVR is described in Chapter 3, "Writing Service Routines." This server can be replaced with an application authentication server with logic specific to the application. (For example, it might access the widely-used Kerberos mechanism for authentication.) With this level of security, authentication but not authorization is provided. That is, the user is checked when joining the application but then is free to execute any services, post events, and access application queues. It is possible for the servers to do application-specific authorization within the logic of the service routines, but there are no hooks for authorization checking for access to events or application queues. The alternative is to use the built-in access control checking.

Optional Access Control Lists
With the use of access control lists (ACLs), the user is not only authenticated when joining the application, but permissions are automatically checked when accessing application entities such as services. ACL security also includes the user-authentication security equivalent to "USER_AUTH." There are two levels of ACL checking. The first ACL security level is simply called "ACL." If "ACL" is configured, the Access Control Lists are checked whenever a user attempts to access a service name, queue name, or event name within the application. If there is no ACL associated with the name, the assumption is that permission is granted. This is why this level is considered "optional" ACLs. It allows the administrator to configure access for those resources that need more security, but ACLs need not be configured for services, queues, or events that are accessible to everyone. Some applications may find it necessary to use both system level and application authorization. An ACL can be used to control who can get to a service, and application logic can control data-dependent access (for example, who can handle transactions for more than a million dollars).

Mandatory Access Control Lists
The second ACL security level is "MANDATORY_ACL." This level is similar to "ACL," but an access control list must be configured for every object for which users are to have access. If "MANDATORY_ACL" is specified and there is no ACL for the name, permission is denied.

Link-Level Encryption
Users of the BEA TUXEDO system Security Add-On Package (Domestic or International) can establish data privacy for messages moving over the network links that connect machines of BEA TUXEDO system applications (or domains).

Writing Client Programs with SECURITY Set

Two things need to be done for clients that are running in an application with SECURITY set: a) getting the security data needed for the specific user, and b) passing this information to the BEA TUXEDO system when joining the application.

Getting the Security Data

The function tpchkauth(3c) is provided so a check on the level of security can be done before calling tpinit(). This is necessary so that the program can prompt for an application password and possibly user authentication data needed for the tpinit() call. tpchkauth() is called without arguments and returns one of the following values.

TPNOAUTH
Nothing is required beyond the normal operating system login and file permission security. This is returned for security level "NONE."

TPSYSAUTH
An application password is required. The client program should prompt the user to provide the password, and should place it in the passwd field of the TPINIT buffer (described below). This is required for security level "APP_PW."

TPAPPAUTH
The application password is required and in addition the client is expected to provide a value to be passed to the authentication service in the data field of the TPINIT buffer. This is returned for security level "USER_AUTH," "ACL," or "MANDATORY_ACL."

Joining the Application

In an application configured with SECURITY, it is necessary to pass the security information to the BEA TUXEDO system via a TPINIT buffer. The TPINIT buffer is a special typed buffer used by a client program to pass client identification and authentication information to the system as the client attempts to join the application. It is defined in the atmi.h header file and contains the following fields.

char     usrname[MAXTIDENT+2];
char cltname[MAXTIDENT+2];
char passwd[MAXTIDENT+2];
char grpname[MAXTIDENT+2];
long flags;
long datalen;
long data;
The usrname, cltname, and grpname Members of TPINIT

usrname, cltname, and grpname are all NULL-terminated strings of up to MAXTIDENT characters. MAXTIDENT is defined as 30. usrname is a name representing the caller; you might elect to use the operating system user name. cltname is a client name whose semantics are application defined. You might use this field to indicate the role of the user when executing the client program. It is also used for selection of specific clients when sending broadcast messages. grpname allows a client to be associated with a resource manager group that is defined in the configuration file. This means that a client can access an XA-compliant resource manager as part of a global transaction. If grpname is passed as a 0-length string, the client is not associated with a resource manager group and is in the default client group.

The usrname and cltname fields are associated with the client process when tpinit() is called and are used for authentication, broadcast notification, and the retrieval of administrative statistics.

The passwd Member of TPINIT

passwd is a NULL-terminated string of up to 8 characters. It is an application password in unencrypted format that is used by tpinit() for validation against the configured application password.

The flags Member of TPINIT

The setting of flags is used to indicate the notification mechanism and system access mode to be used. Selections override values specified in the configuration file (with some exceptions explained below). Possible values for flags are:

TPU_DIP
Select unsolicited notification by dip-in. This is the default method if nothing is specified in the configuration file. It has the advantage of giving the receiving program more control over when unsolicited messages are handled. The system will detect unsolicited messages for your client process only while you are within ATMI calls. You may want to check for unsolicited messages as part of your regular checking routine following returns from ATMI calls. If you specify this flag (or accept it as the default method), you should include a call to tpsetunsol() early in your program. Until the handler for unsolicited messages is known, no messages can be delivered.

TPU_SIG
Select unsolicited notification by signals. This method has the advantage of immediate notification, but has the limitations that you must have the same uid as the sending process, and is not available on all platforms (specifically, it is not available with the MS-DOS instantiation of the workstation). If you specify this option but do not qualify for it, the system resets your choice to TPU_DIP and calls userlog() to note the event.

TPU_IGN
Ignore unsolicited notification.

TPSA_FASTPATH
Specifies a) that ATMI calls within application code can access BEA TUXEDO system internal tables via shared memory, and b) that the shared memory is not protected against access by application code outside of BEA TUXEDO system libraries. Overrides the value in UBBCONFIG, except when NO_OVERRIDE is specified. This is the default if SYSTEM_ACCESS mode is unspecified.

TPSA_PROTECTED
Specifies that ATMI calls within application code can access BEA TUXEDO system internal tables via shared memory but the shared memory is protected against access by application code outside of BEA TUXEDO system libraries. Overrides the value in UBBCONFIG, except when NO_OVERRIDE is specified.
The datalen and data Members of TPINIT

User-specific data is passed by using the datalen and data fields when security is set to "USER_AUTH," "ACL," or "MANDATORY_ACL." datalen is the length of the user-specific data that follows. The buffer type switch entry for the TPINIT typed buffer sets datalen based on the total size passed in for the typed buffer (the application data size is the total size less the size of the TPINIT structure itself plus the size of the data placeholder as defined in the structure). There is a macro, TPINITNEED, provided in atmi.h, that calculates the size needed when you call it with the number of bytes of application data you expect to pass.

data is a placeholder for variable length data that is forwarded to an authentication service. data is always the last element of the structure.

Allocating the TPINIT Buffer

The client program must call tpalloc() to allocate the TPINIT buffer. You can use the functions described for message typed buffers in the "Buffer Management" section later in this chapter. A sample is shown in Listing 2-1. The intent in this example is to prepare to pass 8 bytes of application-specific data to tpinit().

Listing 2-1 Allocating a TPINIT Typed Buffer
.
.
.
TPINIT *tpinfo;
.
.
.
if ((tpinfo = (TPINIT *)tpalloc("TPINIT",(char *)NULL,
TPINITNEED(8))) == (TPINIT *)NULL){
Error Routine
}

The Application Key

An application key is associated with each client program when it joins the application. You can think of this 32-bit value as the security credential for the client; it identifies the client for security purposes. This value cannot be reset by the client (other than by terminating its association and joining the application as a different user), and cannot be forged. The value is provided to every service invocation as part of the TPSVCINFO structure in the appkey field (see tpservice(3c)).

The following list indicates how the application key will be set for various security levels and clients.

Joining and Leaving an Application

The two routines discussed in this section allow a client process to join and leave a BEA TUXEDO system application. The syntax of these functions is as follows.

int
tpinit(tpinfo) /* Join a BEA TUXEDO Application */
TPINIT *tpinfo;

and

int
tpterm() /* Leave a BEA TUXEDO Application */

Before a client can make any service request, it must join the application. If a service request (or any ATMI function) is called before invoking tpinit(), then it is invoked automatically with a NULL parameter. This implies that the TPINIT features mentioned earlier in this chapter cannot be used; the default values are used for client naming, unsolicited notification type, and system access mode, the client cannot be associated with a resource manager group, and an application password cannot be specified. To use these features, the application must explicitly invoke the tpinit() function. Once invoked (either implicitly or explicitly), the calling process may initiate requests and receive replies. tpterm() removes the process from the application. When tpterm() returns successfully, the process must again join the application before communicating with a BEA TUXEDO system server process. A typical client process might begin and end as shown in Listing 2-2.

Listing 2-2 Typical Client Process Paradigm
main()
{
check level of security
call tpsetunsol() to name your handler for TPU_DIP
get usrname, cltname
prompt for application password
allocate a TPINIT buffer
place values into TPINIT buffer structure members

if (tpinit((TPINIT *) tpinfo) == -1){
error routine;
}

allocate a message buffer
while user input exists {
place user input in the buffer
make a service call
receive the reply
check for unsolicited messages
}
free buffers
. . .
if (tpterm() == -1){
error routine;
}
}

The argument to tpinit() is a pointer to a structure TPINIT, that is typedef'd in the atmi.h header file. If you use a buffer, a TPINIT typed buffer must be allocated via tpalloc() before calling tpinit().

tpterm() does not take an argument. Both functions return an integer. On error, the value of the returned integer is -1 and the external global variable, tperrno, is set to a value that indicates the nature of the error. tperrno is defined in the atmi.h header file and documented on the tperrno(5) reference page. The convention is to assign an error code to this global variable that reflects the type of error encountered. There is a discussion of the values of tperrno in Chapter 7, "Error Management." The complete list of error codes that can be returned for each of the ATMI functions can also be found on the reference pages that describe the function and the intro(3c) reference page in the BEA TUXEDO Reference Manual.

An example of tpinit() and tpterm() is shown in Listing 2-3. It is taken from the audit.c client program in the banking application.

Listing 2-3 Joining and Leaving the Application
#include <stdio.h>         /* UNIX */
#include <string.h> /* UNIX */
#include <fml.h> /* BEA TUXEDO */
#include <atmi.h> /* BEA TUXEDO */
#include <Uunix.h> /* BEA TUXEDO */
#include <userlog.h> /* BEA TUXEDO */
#include "bank.h" /* BANKING #defines */
#include "aud.h" /* BANKING view defines */

...

main(argc, argv)
int argc;
char *argv[];

{
...
if (strrchr(argv[0],'/') != NULL)
proc_name = strrchr(argv[0],'/')+1;
else
proc_name = argv[0];
...
/* Join application */
if (tpinit((TPINIT *) NULL) == -1) {
(void)userlog("%s: failed to join application\n", proc_name);
exit(1);
}
...
/* Leave application */
if (tpterm() == -1) {
(void)userlog("%s: failed to leave application\n", proc_name);
exit(1);
}
}

The previous example shows the client process attempting to join the application with a call to tpinit(). If an error is encountered (that is, if the return code is -1), a message is written to the central event log via a call to userlog(). The userlog() function takes arguments similar to printf() and is documented in the userlog(3c) reference page in the BEA TUXEDO Reference Manual. As explained, the client process is invoked by entering its name at the prompt with the mandatory -a or -t option. Its name is captured in argv[0] and is placed in the global variable proc_name which gets written to the event log as part of the message. A similar explanation applies to the call to tpterm().

Buffer Management

Before messages can be sent between processes, a buffer must be allocated for the message data. The following sections describe the buffer types supported by the BEA TUXEDO system and how buffers are allocated, changed in size, tested for type, and freed using ATMI functions.

Typed Buffers for Messages

The BEA TUXEDO system is delivered with nine message buffer types defined:

STRING CARRAY FML VIEW X_COMMON X_C_TYPE X_OCTET FML32 VIEW32

The buffer types are defined in tmtypesw.c (which can be found in $TUXDIR/lib/tmtypesw.c, with documentation in tuxtypes(5)). When the BEA TUXEDO system software is built, tmtypesw.o is archived in the BEA TUXEDO system libraries that are automatically linked in when the buildclient and buildserver commands are invoked, so the nine defined types are available to your application programs.

The tmtypesw.c file can be edited to add or remove buffer types. Information about how to do this can be found in the book Administering the BEA TUXEDO System. Only buffer types defined in tmtypesw.c can be known to your client or server programs. The ubbconfig(5) BUFTYPE parameter can be used to specify the types and subtypes a given service can know about.

Buffer Types: STRING

The STRING buffer type is what is conventionally understood as a string in the C language. It is a character array terminated by the null character. Data dependent routing is not provided for this buffer type. If routing functions are desired, they must be written as part of the application. Encoding and decoding is provided for this buffer type.

Buffer Types: CARRAY

The CARRAY buffer type (and equivalently X_OCTET) is an array of characters, any of which can be the null character. The application defines the semantics of the array; it is not interpreted by the BEA TUXEDO system. Data dependent routing is not provided for this buffer type. If routing functions are desired, they must be written as part of the application. No encoding or decoding is provided for a CARRAY buffer when crossing machine boundaries since the bytes are not interpreted by the system.

Buffer Types: FML and FML32

FML buffers offer the advantages of data independence and flexibility; fields may be present or absent, or may have multiple occurrences. Also, FML buffers interface well with both the BEA TUXEDO system DBMS and the DES. The BEA TUXEDO system DBMS supports fielded records in database files, and the mio client process of the BEA TUXEDO system DES uses fielded buffers for input and output data. In addition, this data type provides the functionality of data dependent routing. Automatic encoding and decoding is done if the buffer is passed between machines of different types.

FML functions are used to manipulate FML typed buffers. These functions include some that convert fielded buffers to C structures and back again, thus providing both the performance gains of C structures for lengthy field manipulations and the flexibility of fielded buffers. A C structure that is derived from a fielded buffer is called a VIEW.

FML32 is similar to FML but allows for larger string and character fields, more fields, and larger overall buffers. The FML32 buffer type uses environment variables suffixed with "32", for example, FIELDTBLS32 and FLDTBLDIR32. FML32 functions (like their FML counterparts but with a "32" suffix) are used to manipulate these buffers. Functions are also provided to convert between 16-bit and 32-bit FML buffers (assuming that the limits are not exceeded), and functions are available to convert between FML32 and VIEW32 buffers.

Buffer Types: VIEW, VIEW32, X_C_TYPE, and X_COMMON

Buffers of the VIEW type (and equivalently X_C_TYPE and X_COMMON) are C structures. The C structure is passed between processes in a VIEW typed buffer of a specific subtype. It can be one derived from a fielded buffer or one defined independently of a fielded buffer. The ATMI buffer management primitives for allocating, resizing, and freeing a VIEW buffer are the same for both types, but there are differences in the way the two types of VIEWS themselves are defined and in how they are handled within your programs. These differences were described in the section titled, "The BEA TUXEDO System Development Environment," in Chapter 1. Both types of VIEW buffer support data dependent routing and automatic encoding and decoding when the buffer is passed between unlike machines.

A comparison of how to create and use the two VIEW types is summarized in Table 2-1.

Table 2-1 Comparison of Two VIEW Types

FML-dependent VIEW FML-independent VIEW

Creating

create the view description file with FML information in it

create the view description file without FML information in it

use the viewc compiler without the -n option to compile the description file

use the viewc compiler with the -n option to compile the description file

Using

set and export FIELDTBLS, FLDTBLDIR, VIEWFILES, VIEWDIR in the ENVFILE for the machine the client process is running on

set and export VIEWFILES and VIEWDIR in the ENVFILE for the machine the client process is running on

#include fml.h, the header file created from the field table file, and the header file created from the view compiler in the programs that define FML and VIEW buffers

#include the header file created from the view compiler in the programs that define VIEW buffers

Buffers of type X_COMMON should contain only short, long, and character fields, which are common to both the COBOL and C languages.

The VIEW32 type is similar to the VIEW type but supports larger character fields and bigger records. It is also used for conversion to/from FML32 records. The VIEW32 buffer type uses environment variables suffixed with "32", for example, FIELDTBLS32, FLDTBLDIR32, VIEWFILES32, and VIEWDIR32.

Buffer Types: Summary

Although system configuration and defining buffer types are application design issues rather than programming issues, the above discussion has been included to explain how processes know about the various buffer types so you can allocate buffers correctly for the communication calls between processes.

ATMI Buffer Primitives

It is important for the BEA TUXEDO system programmer to know what buffer types are required and expected by the application. The ATMI functions that allocate, resize, and free the buffers take the buffer type and subtype as arguments. For the types provided by BEA TUXEDO, the subtype argument has meaning only when type is VIEW, VIEW32, X_C_TYPE, or X_COMMON. In this case, the subtype is the name of the specific C structure defined as a VIEW. In the other buffer types, the subtype argument is NULL.

Allocating a Typed Buffer

Initially, a client process does not have any buffers. Before a message can be sent, the client process must allocate a buffer of a supported type to carry the message. A typed buffer is allocated by using the tpalloc() function. The syntax of this function is:

char*
tpalloc(type, subtype, size) /* Allocate a new data buffer */
char *type, *subtype;
long size;

The three arguments the function takes are type, subtype, and size. The value of type must be a type known to BEA TUXEDO.

The VIEW, VIEW32, X_C_TYPE, and X_COMMON buffers require the subtype argument. (See Listing 2-4.) In the cases where a subtype is not relevant, assign the NULL value to this argument. This is illustrated in Listing 2-5, Listing 2-6, and Listing 2-7.

Listing 2-4 Allocating a VIEW Buffer
struct aud *audv;  /* pointer to aud view structure */
. . .
audv = (struct aud *) tpalloc("VIEW", "aud", sizeof(struct aud));
. . .

Listing 2-5 shows the allocation of an FML typed buffer.

Listing 2-5 Allocating an FML Buffer
FBFR *fbfr;  /* pointer to an FML buffer structure */
. . .
fbfr = (FBFR *)tpalloc("FML", NULL, Fneeded(f, v))
. . .

The size argument can be set to zero for all the BEA TUXEDO system-supplied types except for CARRAY. If size is not specified (that is, if it is set to zero), BEA TUXEDO uses a default size that is defined for each buffer type. If the size argument is specified, the size of the buffer will be the larger of the specified size or the default size. The default size for STRING is 512 bytes, and it is 1024 bytes for FML, FML32, VIEW, X_C_TYPE, X_COMMON, and VIEW32.

For a CARRAY a size greater than zero must be specified (see example in Listing 2-6); the default size is 0 and this causes tpalloc() to return a NULL pointer and set tperrno to TPEINVAL.

Note that in cases of error, tpalloc() always returns the NULL pointer. Other causes for error include failure to specify a value for type (or subtype in the case of VIEW), specifying a type that is not known to the system, and failing to join the application before attempting allocation. Refer to the tpalloc(3c) reference page for the complete list of error codes and their explanation.

Upon success, tpalloc() returns a pointer of type char. For types other than STRING and CARRAY, you should cast it to the proper C structure pointer or to an FML pointer. (See Listing 2-4 and Listing 2-5.)

Listing 2-4 shows the allocation of a VIEW typed buffer. It is taken from the audit.c client program in the banking application. The aud structure is the VIEW typed buffer that is defined in Chapter 1, "Introduction and Overview."

tpalloc Examples

Listing 2-6 shows the allocation of a CARRAY typed buffer. The value of casize must not be zero.

Listing 2-6 Allocating a CARRAY Buffer
char *cptr;
long casize;
. . .
casize = 1024;
cptr = tpalloc("CARRAY", NULL, casize);
. . .

Listing 2-7 shows the allocation of a STRING typed buffer. In the example, the default size defined by the system is used as the value for the size argument to tpalloc().

Listing 2-7 Allocating a STRING Buffer
char *cptr;
. . .
cptr = tpalloc("STRING", NULL, 0);
. . .

What About FML Buffer Management Functions?

If you've been looking at the BEA TUXEDO FML Programmer's Guide, especially the section in Chapter 5 called "Buffer Allocation and Initialization," you probably realize it is also possible to manage FML buffers by the routines described there. However, if the buffers are to be used in the communication calls in the ATMI interface, they must be managed by the routines described on the tpalloc(3c) reference page. Specifically, this means that Falloc(), Frealloc(), and Ffree() should be replaced by tpalloc(), tprealloc(), and tpfree(). Finit() is not needed because tpalloc() automatically initializes the buffer. Also, since the FML typed buffer is given a default size by the system, Fneeded() should be used only when you wish to assign the buffer a specific size that is larger than the default size as is shown above in Listing 2-5. The f and v arguments to Fneeded are integer values that represent the number of fields and the space for field values in bytes required for the fielded buffer. All the other FML functions described in Chapter 5 of the BEA TUXEDO FML Programmer's Guide can be used with all FML typed buffers regardless of how the buffers were allocated.

Putting Data in the Buffer

Once the buffer has been allocated, data can be put in it. The aud VIEW typed buffer has three members (fields). They are b_id, the branch identifier taken from the command line (if given); balance, used to return the requested balance; and ermsg, used to return a message to the status line for the user. When audit is used to query a specific branch balance, the b_id member is set to the branch identifier to be queried, and the balance and ermsg members are set to zero and the null string, respectively. This is illustrated in Listing 2-8.

Listing 2-8 Placing Data in a Message Buffer - Example 1
...
audv = (struct aud *)tpalloc("VIEW", "aud", sizeof(struct aud));

/* Prepare aud structure */

audv->b_id = q_branchid;
audv->balance = 0.0;
(void)strcpy(audv->ermsg, "");
...

When audit is used to query the total bank balance, the total balance at each site is obtained by a call to the BAL server. To run a query on each site, a representative branch identifier is specified. Representative branch identifiers are stored in an array named sitelist[]. Hence, the aud structure is set up as illustrated in Listing 2-9.

Listing 2-9 Placing Data in a Message Buffer - Example 2
...
/* Prepare aud structure */

audv->b_id = sitelist[i];/* routing done on this field */
audv->balance = 0.0;
(void)strcpy(audv->ermsg, "");
...

An example of code that puts data into a STRING buffer is part of Listing 2-10.

Resizing a Typed Buffer

It is possible to resize the buffer that is initially allocated by tpalloc() if you want to use the same buffer to input and send messages of different sizes. The function you would use in this case is tprealloc(). The syntax of the function is as follows.

char*
tprealloc(ptr, size) /* Change a data buffer's size */
char *ptr;
long size;

For example, if a buffer has been allocated as type STRING, it is possible to reallocate a buffer of a different size but of the same type, as illustrated in Listing 2-10.

Listing 2-10 Resizing a Buffer
#include <stdio.h>
#include "atmi.h"

char instr[100]; /* string to capture stdin input strings */
long s1len, s2len; /* string 1 and string 2 lengths */
char *s1ptr, *s2ptr; /* string 1 and string 2 pointers */

main()

{
(void)gets(instr); /* get line from stdin */
s1len = (long)strlen(instr)+1; /* determine its length */

join application

if ((s1ptr = tpalloc("STRING", NULL, s1len)) == NULL) {
fprintf(stderr, "tpalloc failed for echo of: %s\n", instr);
leave application
exit(1);
}
(void)strcpy(s1ptr, instr);

make communication call with buffer pointed to by s1ptr

(void)gets(instr); /* get another line from stdin */
s2len = (long)strlen(instr)+1; /* determine its length */
if ((s2ptr = tprealloc(s1ptr, s2len)) == NULL) {
fprintf(stderr, "tprealloc failed for echo of: %s\n", instr);
free s1ptr's buffer
leave application
exit(1);
}
(void)strcpy(s2ptr, instr);

make communication call with buffer pointed to by s2ptr
. . .
}

As illustrated, tprealloc() takes two parameters, a pointer to the buffer that is to be resized and a long integer that tells the function the new size of the buffer. The pointer passed to tprealloc() must have originally been allocated by a call to tpalloc(); otherwise the call will fail and tperrno will be set to TPEINVAL to signify that invalid arguments have been passed to the function. The pointer returned by tprealloc() will point to a buffer of the same type as the original buffer. You must use the returned pointer to reference the resized buffer because the location of the buffer may have changed. The contents of the buffer, up to the smaller of the two sizes, remains unchanged. When tprealloc() is called to make a buffer larger, new space is available beyond the existing contents. When tprealloc() is called to make a buffer smaller, the buffer does not actually become smaller; space beyond the specified size is unusable. If you really want to free up the unused space, you must copy the data into a buffer of the appropriate size and free the larger buffer.

tprealloc() returns the NULL pointer on error and sets tperrno as indicated on the tpalloc(3c) reference page. When tprealloc() returns the NULL pointer, the contents of the buffer passed to it may have been altered and may be no longer valid.

Listing 2-11 shows an expanded version of the example in Listing 2-10 that could be used to check for all error codes tprealloc() can return.

Listing 2-11 Error Checking for tprealloc()
. . .
if ((s2ptr=tprealloc(s1ptr, s2len)) == NULL)
switch(tperrno) {
case TPEINVAL:
fprintf(stderr, "given invalid arguments\n");
fprintf(stderr, "will do tpalloc instead\n");
tpfree(s1ptr);
if ((s2ptr=tpalloc("STRING", NULL, s2len)) == NULL) {
fprintf(stderr, "tpalloc failed for echo of: %s\n", instr);
leave application
exit(1);
}
break;
case TPEPROTO:
fprintf(stderr, "tried to tprealloc before tpinit;\n");
fprintf(stderr, "program error; contact product support\n");
leave application
exit(1);
case TPESYSTEM:
fprintf(stderr,
"BEA TUXEDO error occurred; consult today's userlog file\n");
leave application
exit(1);
case TPEOS:
fprintf(stderr, "Operating System error %d occurred\n",Uunixerr);
leave application
exit(1);
}

Checking for Buffer Type

The tptypes() function takes a pointer to a data buffer as its first argument and returns the type and subtype (if there is one) for that buffer in its other two arguments. It returns a long integer which, on success, is the length of the buffer. The syntax of this function is:

long
tptypes(ptr, type, subtype) /* Determine a data */
char *ptr, *type, *subtype; /* buffer's type and subtype */

The pointer you supply to this function must point to a buffer originally allocated or reallocated by tpalloc() or tprealloc(), otherwise it will fail complaining of invalid arguments. If the type is not VIEW, the subtype parameter will point to a character array containing the null string upon return from the function call. All three of the parameters of tptypes() are pointers to character. The first parameter is the pointer to the typed buffer and must be non-null. Be sure to cast it as a pointer to a character before passing it to this function since it is expecting a character pointer. The second and third parameters return the type and subtype of the buffer pointed to by the first parameter. The second parameter must be a character array of at least TM_TYPELEN characters, and the third parameter must be a character array of at least TM_STYPELEN characters. On success, the size of the buffer is returned. On error, tptypes() returns -1 and sets tperrno to an error code that signifies the problem. All the possible codes are listed on the intro(3c) and tpalloc(3c) reference pages.

In addition to the fragment shown below (in Listing 2-12), an example of tptypes() can be found in Listing 3-2 in Chapter 3, "Writing Service Routines." It demonstrates a service routine checking the type of buffer received.

The size value returned by tptypes() can be used to determine if the default buffer size is large enough to hold your data.

Listing 2-12 Getting Buffer Size
. . .
iptr = (FBFR *)tpalloc("FML", NULL, 0);
ilen = tptypes(iptr, NULL, NULL);
. . .
if (ilen < mydatasize)
tprealloc(iptr, mydatasize);

Freeing a Typed Buffer

To free a buffer allocated by tpalloc() or reallocated by tprealloc(), use the tpfree() function. The syntax of this function is:

void
tpfree(ptr) /* Free a data buffer */
char *ptr;

The argument to this function is a pointer previously returned by the tpalloc() or tprealloc() function. If tpfree() is given a pointer that does not point to a buffer obtained from tpalloc() or tprealloc(), it returns without freeing anything, and it does not return an error condition. tpfree() expects a character pointer as its only parameter. Listing 2-13 shows an example of its use.

Listing 2-13 Freeing a Buffer
struct aud *audv;  /* pointer to aud view structure */
. . .
audv = (struct aud *)tpalloc("VIEW", "aud", sizeof(struct aud));
. . .
tpfree((char *)audv);

Service Calls

Once a client process has joined the application, allocated a buffer, and placed the input data request in it, it can then send the request message to a service subroutine for processing and receive a reply message. The next sections discuss the ATMI functions that allow processes that are acting as clients to send message requests to services and receive replies either synchronously or asynchronously.

The tpcall() function sends a request to a service subroutine and synchronously waits for its reply.

The tpacall() function sends a request to a service and immediately returns. The reply to the service call is asynchronously received by calling the tpgetrply() function.

Sending Synchronous Messages: tpcall()

tpcall() is used to send synchronous messages. The syntax of this function is:

int
tpcall(svc, idata, ilen, odata, olen, flags) /* Send service request */
char *svc, *idata; /* and await its reply */
long ilen;
char **odata;
long *olen, flags;

tpcall() sends a request to the service that is specified in its first parameter, svc. The service named in svc must be one offered in your application. tpcall() waits for the expected reply. It is logically the same as calling the tpacall() function immediately followed by tpgetrply(). The request carries the priority that is set by the system for the service specified in svc unless a different priority has been explicitly set by a call to tpsprio().

The second parameter of the function, idata, is a pointer that contains the address of the data portion of the request. The pointer must reference a typed buffer that was allocated by a prior call to tpalloc(). Note that the type (and subtype) of idata must match the type (and subtype) expected by the service routine. If the types do not match, the system sets tperrno to TPEITYPE and the function call fails.

The third parameter, ilen, specifies the length of the request data in the buffer pointed to by idata. If the buffer is a self-defining type, that is, an FML, FML32, VIEW, VIEW32, X_COMMON, X_C_TYPE, or STRING buffer, ilen is ignored and can be set to zero. If the request requires no data, set idata to the NULL pointer. This causes the ilen parameter to be ignored. If no data is being sent with the request, there is no need to allocate a buffer for idata.

The next two parameters are the address of a pointer to the output buffer, odata, and a pointer to the length of the reply data, olen. The output buffer, **odata, must have been allocated by a previous call to tpalloc(). This buffer is used to receive the reply message. If the reply message sent back contains no data portion, upon successful return from tpcall(), *olen will be set to zero, and the pointer and the contents of the output buffer will remain unchanged. It is an error for either **odata or *olen to point to NULL.

The same buffer can be used for both the request and reply message. If this is the case, then odata must be set to the address of the pointer returned from allocating the input buffer.

Listing 2-14 shows the client program, audit.c, making a synchronous call using the same buffer for both the request and reply message. Using the same buffer is appropriate in this particular case, since the *audv message buffer has been set up to accommodate both request and reply information in the same buffer. The b_id field is queried by the service but not overwritten and the bal and ermsg fields have been initialized to zero and the null string, respectively, in anticipation of the values to be returned by the service. The svc_name and hdr_type variables represent the service name and the balance type (account or teller) requested.

Listing 2-14 Using the Same Buffer for Request and Reply Messages
. . .
/* Create buffer and set data pointer */

audv = (struct aud *)tpalloc("VIEW", "aud", sizeof(struct aud));

/* Prepare aud structure */

audv->b_id = q_branchid;
audv->balance = 0.0;
(void)strcpy(audv->ermsg, "");

/* Do tpcall */

if (tpcall(svc_name,(char *)audv,sizeof(struct aud),
(char **)&audv,(long *)&audrl,0)== -1){
(void)fprintf (stderr, "%s service failed\n %s: %s\n",
svc_name, svc_name, audv->ermsg);
retc = -1;
}
else
(void)printf ("Branch %ld %s balance is $%.2f\n",
audv->b_id, hdr_type, audv->balance);
. . .

Note: For an example in which different buffers are used for input and output, see Listing 3-2 in Chapter 3, "Writing Service Routines."

Buffers used for receiving messages can grow upon receipt of the message if the message proves to be too large for the allocated buffer. BEA TUXEDO guarantees that a received message will fit into the buffer by growing the buffer automatically. However, it is necessary for the programmer to test for size changes of reply buffers in order to determine their actual sizes. The new size is accessible by the address returned in the olen parameter. To determine if a reply buffer changed in size, compare the size of the reply buffer before the call to tpcall() with the value of *olen after its return. If *olen is larger than the original size, the buffer has grown. If not, the buffer has not changed in size. You should reference the output buffer by the value returned in odata after the call, because the output buffer may change for reasons other than increase in buffer size. This scenario does not apply to request buffers since there is no possibility that the request data will grow upon placing it in the buffer. Note that if you use the same buffer for the request and reply message, and the pointer to the reply buffer changed because the buffer grew, then the input buffer pointer no longer references a valid address.

Listing 2-15 offers a generic example of an application testing for a change in buffer size after a call to tpcall(). The logic exercised in this particular example is that the input and output buffers must remain equal in size.

Listing 2-15 Testing for Change in Size of the Reply Buffer
char *svc, *idata, *odata;
long ilen, olen, bef_len, aft_len;
. . .
if (idata = tpalloc("STRING", NULL, 0) == NULL)
error

if (odata = tpalloc("STRING", NULL, 0) == NULL)
error

place string value into idata buffer

ilen = olen = strlen(idata)+1;
. . .
bef_len = olen;
if (tpcall(svc, idata, ilen, &odata, &olen, flags) == -1)
error

aft_len = olen;

if (aft_len > bef_len){ /* message buffer has grown */

if (idata = tprealloc(idata, olen) == NULL)
error
}

Values for the flags Argument: tpcall()

The last argument that tpcall() takes is flags. The values given to the flags argument can change the operation of the communication call in some way, allowing additional flexibility to the application. If flags is set to 0, the communication is conducted in the default manner.

TPNOTRAN
If the client process is in transaction mode when it calls tpcall(), and flags is set to TPNOTRAN, the service that is invoked by the call will not be part of the transaction; that is, the operations that the service performs are not part of the caller's transaction. There's more on this subject in Chapter 5, "Global Transactions in BEA TUXEDO System."

TPNOCHANGE
By using this value, the calling program is indicating that it wants the message returned in the same type of buffer that was originally allocated as the output buffer. In other words, when this flag is set, the type of buffer returned to the caller must be the same as the one pointed to by *odata. This is known as strong type checking. The default is to allow a buffer type to be different than the original one so long as the caller recognizes the type. In this case, the buffer type for *odata changes to the received buffer type. This is known as weak type checking. A call to tptypes() informs the recipient of the new buffer type.

TPNOBLOCK
TPNOBLOCK concerns the action a function call takes if a blocking condition exists. Callers of the communication routines typically block when waiting for a reply to arrive although they may also block when trying to send a request if all server queues or internal buffers are full. A default blocking time-out period is defined for the application in the configuration file. It specifies the amount of time a caller should wait for a blocking condition to subside when one exists. If the condition persists beyond this limit, the function call fails and tperrno is set to TPETIME. When the value of flags is set to TPNOBLOCK, if a blocking condition exists, the call fails immediately and the request message is not sent. In this case, tperrno is set to TPEBLOCK. Note that tpcall() is a dual function in that it both sends a request and receives a reply. When TPNOBLOCK is set, it affects only the send part of the function; if all the server queues are filled or the internal buffers into which the message buffers are copied are full, the call will not block but immediately return. However, if it must wait for the reply (which is usually the case), this flag setting does not immunize the call from blocking while it waits.

TPNOTIME
By setting flags to TPNOTIME, you are telling the system to ignore the blocking time-out limit because the caller is willing to wait indefinitely for the blocking condition to subside. However, if the caller is in transaction mode, this flag has no effect; it is subject to the transaction time-out limit. The timing out of transactions is discussed in Chapter 5, "Global Transactions in BEA TUXEDO System."

TPSIGRSTRT
Another valid value for the flags argument is TPSIGRSTRT, which concerns the action to take if there is a signal interrupt. When flags is set to this value, the call is automatically made again. As a result, if a signal interrupts the underlying system call, the function call is reissued. When flags is not set to this value and there is a signal interrupt, the function call fails and tperrno returns TPGOTSIG.

Flag values can be or'd together.

tpcall() returns an integer. On failure, the value of this integer is -1 and the value of tperrno is set to an appropriate value reflecting the type of error that occurred. Some of the causes for error have already been discussed, while others have transaction implications and will be introduced in Chapter 5, "Global Transactions in BEA TUXEDO System." In general, communication calls may fail for a variety of errors. Many of the errors returned on communication calls can be fixed on an application level. They include application defined errors (TPESVCFAIL), errors in processing return arguments (TPESVCERR), typed buffer errors (TPEITYPE, TPEOTYPE), time-out (TPETIME), and protocol errors (TPEPROTO), among others. They are all discussed in Chapter 7, "Error Management," and are listed on the intro(3c) and tpcall(3c) reference pages. The communication of these failures will also be explained in the discussion of the tpreturn() function in Chapter 3, "Writing Service Routines."

Examples of the Use of flags Arguments

The next three examples show tpcall() using the communication flags in various scenarios.

Listing 2-16 is based on the TRANSFER service, which is part of the XFER server process of bankapp. The TRANSFER service assumes the role of a client when it calls on the services of WITHDRAWAL and DEPOSIT. In the example, we have set the communication flag to TPSIGRSTRT in these service calls to give the transaction a better chance of committing.

Listing 2-16 Sending a Synchronous Message with TPSIGRSTRT Set
     /* Do a tpcall to withdraw from first account */

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);
}
...
/* 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);
}

Listing 2-17 illustrates a communication call that suppresses transaction mode. It is being made to a service that is not affiliated with a resource manager and it would be an error to allow it to participate in the transaction. Specifically in this example, an accounts receivable report, accrcv is to be printed against a database named accounts. The service routine REPORT interprets the parameters and sends the byte stream for the completed report as a reply. The client, shown here, uses tpcall() to send the byte stream to a service called PRINTER that prints out the byte stream to the appropriate printer for this client. It receives a reply from the PRINTER service naming the printer that was chosen to print the report to make it convenient for the user to pick up the hard copy. Listing 2-19 shows a similar example using an asynchronous message call.

Listing 2-17 Sending a Synchronous Message with TPNOTRAN Set
#include <stdio.h>
#include "atmi.h"

main()

{
char *rbuf; /* report buffer */
long r1len, r2len, r3len; /* buffer lengths of send, 1st reply,
and 2nd reply buffers for report */
join application

if (rbuf = tpalloc("STRING", NULL, 0) == NULL) /* allocate space for report */
leave application and exit program
(void)strcpy(rbuf,
"REPORT=accrcv DBNAME=accounts"); /* send parms of report */
r1len = strlen(rbuf)+1; /* length of request */

start transaction

if (tpcall("REPORT", rbuf, r1len, &rbuf,
&r2len, 0) == -1) /* get report print stream */
error routine
if (tpcall("PRINTER", rbuf, r2len, &rbuf,
&r3len, TPNOTRAN) == -1) /* send report to printer */
error routine
(void)printf("Report sent to %s printer\n",
rbuf); /* indicate which printer */

terminate transaction
free buffer
leave application
}

In Listing 2-17, where error routine has been indicated, it should include printing an error message, aborting the transaction, freeing allocated buffers, leaving the application, and exiting the program.

Listing 2-18 illustrates the use of the TPNOCHANGE communication flag to enforce strong buffer type checking. This example refers to the same REPORT service that is used above in Listing 2-17. In this one, the reply is received in a VIEW typed buffer called rview1 and the elements are printed in printf() statements. The strong type check flag, TPNOCHANGE, is used to force the reply to be returned in a buffer of type VIEW and of subtype rview1. A possible reason for this check is to guard against errors that may occur in the REPORT service subroutine in processing the request that could result in a reply buffer of an incorrect type. Another reason is to prevent changes that are not made consistently across all areas of dependency. For example, someone could have changed the REPORT service to standardize all replies in some other VIEW format without modifying the client process to reflect the change.

Listing 2-18 Sending a Synchronous Message with TPNOCHANGE Set
#include <stdio.h>
#include "atmi.h"
#include "rview1.h"

main(argc, argv)
int argc;
char * argv[];

{
char *rbuf; /* report buffer */
struct rview1 *rrbuf; /* report reply buffer */
long rlen, rrlen; /* buffer lengths of send and reply
buffers for report */
if (tpinit((TPINIT *) tpinfo) == -1)
fprintf(stderr, "%s: failed to join application\n", argv[0]);

if (rbuf = tpalloc("STRING", NULL, 0) == NULL) { /* allocate space for report */
tpterm();
exit(1);
}
/* allocate space for return buffer */
if (rrbuf = (struct rview1 *)tpalloc("VIEW", "rview1", sizeof(struct rview1)) \ == NULL{
tpfree(rbuf);
tpterm();
exit(1);
}
(void)strcpy(rbuf, "REPORT=accrcv DBNAME=accounts FORMAT=rview1");
rlen = strlen(rbuf)+1; /* length of request */
/* get report in rview1 struct */
if (tpcall("REPORT", rbuf, rlen, (char **)&rrbuf, &rrlen, TPNOCHANGE) == -1) {
fprintf(stderr, "accounts receivable report failed in service call\n");
if (tperrno == TPEOTYPE)
fprintf(stderr, "report returned has wrong view type\n");
tpfree(rbuf);
tpfree(rrbuf);
tpterm();
exit(1);
}
(void)printf("Total accounts receivable %6d\n", rrbuf->total);
(void)printf("Largest three outstanding %-20s %6d\n", rrbuf->name1, rrbuf->amt1);
(void)printf(" %-20s %6d\n", rrbuf->name2, rrbuf->amt2);
(void)printf(" %-20s %6d\n", rrbuf->name3, rrbuf->amt3);
tpfree(rbuf);
tpfree(rrbuf);
tpterm();
}

Sending Asynchronous Messages: tpacall()

This section discusses the sending of asynchronous messages where the sender of the request does not wait for the reply. The first half of this communication is performed by tpacall(). The syntax of this function is:

int
tpacall(svc, data, len, flags) /* Send service request */
char *svc, *data;
long len, flags;

The tpacall() function sends a request message to the service named in the svc parameter and immediately returns from the call. The next three parameters, data, len, and flags, have the same semantics as idata, ilen, and flags of the tpcall() function. Upon successful completion of the call, tpacall() returns an integer that serves as a descriptor used to get the correct reply for the sent request. While tpacall() is in transaction mode (topic of Chapter 5, "Global Transactions in BEA TUXEDO System,"), there may be no outstanding replies when the transaction commits; that is, within a given transaction, for each request sent expecting a reply, a corresponding reply must eventually be received.

Values for the flags Argument: tpacall()

The communication flags that tpacall() takes as values for the flags argument pertain to the send part of the communication. As a result, the flag value TPNOCHANGE is removed since it concerns the output buffer which is not present in this call, and the value TPNOREPLY is added since the receive part is not implicit to this communication call. When tpcall() is used, the fact that a reply is expected is implicit. tpacall() represents only the sending part of tpcall(), and it is possible to indicate whether a reply is expected or not.

TPNOREPLY
If the value TPNOREPLY is assigned to the flags parameter, it signals to tpacall() that a reply is not expected. Guidelines for using this flag value correctly when a process is in transaction mode are discussed in Chapter 5, "Global Transactions in BEA TUXEDO System." When this flag is set, on success tpacall() returns the value of 0 as the reply descriptor, where 0 cannot be used by tpgetrply().

An example of tpacall() using the TPNOREPLY|TPNOTRAN flags is shown in Listing 2-19. This example is similar to the one presented above. In this case, however, a reply is not expected from the PRINTER service. By setting both of these flags, the client is indicating that no reply is expected and the PRINTER service is not to be a participant in the current transaction. Chapter 7 fully discusses this situation. Refer to the section called "Transaction Rules."

Listing 2-19 Sending an Asynchronous Message with TPNOTRAN|TPNOREPLY
#include <stdio.h>
#include "atmi.h"

main()

{
char *rbuf; /* report buffer */
long rlen, rrlen; /* buffer lengths of send, reply buffers for report */

join application

if (rbuf = tpalloc("STRING", NULL, 0) == NULL) /* allocate space for report */
error
(void)strcpy(rbuf, "REPORT=accrcv DBNAME=accounts");/* send parms of report */
rlen = strlen(rbuf)+1; /* length of request */

start transaction

if (tpcall("REPORT", rbuf, rlen, &rbuf, &rrlen, 0)
== -1) /* get report print stream */
error
if (tpacall("PRINTER", rbuf, rrlen, TPNOTRAN|TPNOREPLY)
== -1) /* send report to printer */
error

. . .
commit transaction
free buffer
leave application
}

On error, tpacall() returns -1 and sets tperrno to a value that reflects the nature of the error. tpacall() returns many of the same error codes as tpcall(). Again, the differences are based on the fact that one represents a synchronous call and the other an asynchronous call. These errors are discussed at length in Chapter 7, "Error Management."

Listing 2-20 illustrates a series of asynchronous calls being made that make up the total bank balance query. Since the banking application data is distributed among several database sites, an SQL query needs to be executed against each one. The audit client chooses to do this by selecting representative branch identifiers (that is, one branch identifier per database site), and calling the ABAL or TBAL service for each one. The representative branch identifier is not used in the actual SQL query, but it does cause the BEA TUXEDO system to route the request to the proper database site. In the following code, the for-loop invokes tpacall() once for each site. We'll see this same logic handled in a different way in Chapter 4, "Conversational Clients and Services."

Listing 2-20 Sending Asynchronous Requests
audv->balance = 0.0;
(void)strcpy(audv->ermsg, "");

for (i=0; i<NSITE; i++) {

/* Prepare aud structure */

audv->b_id = sitelist[i]; /* routing done on this field */

/* Do tpacall */

if ((cd[i]=tpacall(sname, (char *)audv, sizeof(struct aud), 0))
== -1) {
(void)fprintf (stderr,
"%s: %s service request failed for site rep %ld\n",
pgmname, sname, sitelist[i]);
tpfree((char *)audv);
return(-1);
}
}

Getting an Asynchronous Reply: tpgetrply()

tpgetrply() is the complementary function to tpacall(). It dequeues a reply from a request previously sent by tpacall(). The syntax of this function is:

int
tpgetrply(cd, data, len, flags) /* Receive reply to service request */
int *cd; /* Call Descriptor */
char **data;
long *len, flags;

tpgetrply() takes the address of the call descriptor returned by tpacall() as its first parameter, cd. In the default case, the function waits for the arrival of the reply that corresponds to the value pointed to by the cd parameter. In waiting for this specific reply, a blocking time-out may occur. A time-out means that tpgetrply() fails and tperrno is set to TPETIME (unless its flags parameter is set to TPNOTIME).

The second and third parameters to tpgetrply(), data and len, have identical semantics to those of the odata and olen parameters of the tpcall() function. data contains the address of a pointer that was previously assigned by a call to tpalloc().

Getting and Setting Priority

ATMI provides two functions that allow you to determine and set the priority of the message request. The priority affects how the request is dequeued by the server. Servers dequeue requests with the highest priorities first. The syntax of these functions is:

int
tpgprio(); /* Get service request priority */

and

int
tpsprio(prio, flags); /* Set service request priority */
int prio;
long flags;

The tpgprio() function can be called by a requester after invoking the tpcall() or tpacall() function to retrieve the priority of the request message just sent. If it was called and no request was sent, the function fails returning -1 and setting tperrno to TPENOENT. Upon success, tpgprio() returns an integer value in the range of 1 to 100, 100 being the highest priority value. If the priority has not been explicitly set by using the tpsprio() function, the value of the priority will be that of the service routine that handles the request. The priority of the service is assigned the system default value of 50 unless it has been specifically defined to some other value by the administrator. See Listing 2-21 for an example of determining the priority of a message that was sent in an asynchronous call.

Listing 2-21 Determining the Priority of the Sent Request
#include <stdio.h>
#include "atmi.h"

main ()
{
int cd1, cd2; /* call descriptors */
int pr1, pr2; /* priorities to two calls */
char *buf1, *buf2; /* buffers */
long buf1len, buf2len; /* buffer lengths */

join application

if (buf1=tpalloc("FML", NULL, 0) == NULL)
error
if (buf2=tpalloc("FML", NULL, 0) == NULL)
error

populate FML buffers with send request

if ((cd1 = tpacall("service1", buf1, 0, 0)) == -1)
error
if ((pr1 = tpgprio()) == -1)
error
if ((cd2 = tpacall("service2", buf2, 0, 0)) == -1)
error
if ((pr2 = tpgprio()) == -1)
error

if (pr1 >= pr2) { /* base the order of tpgetrplys on priority of calls */
if (tpgetrply(&cd1, &buf1, &buf1len, 0) == -1)
error
if (tpgetrply(&cd2, &buf2, &buf2len, 0) == -1)
error
}
else {
if (tpgetrply(&cd2, &buf2, &buf2len, 0) == -1)
error
if (tpgetrply(&cd1, &buf1, &buf1len, 0) == -1)
error
}
. . .
}

It is also possible to use this function to retrieve the priority of the request just received by the service. This is illustrated in Listing 3-3 in Chapter 3, "Writing Service Routines."

With the tpsprio() function, the programmer can override the priority level the request would normally inherit from the service to which it is dispatched. When tpsprio() is called, it affects the priority level only of the very next request that is sent by tpcall() or tpacall() or forwarded by a service subroutine. This function takes two parameters; the second one indicates how the first one is to be interpreted. The first parameter, prio, is an integer. In the default situation, its sign indicates whether the request's priority should be incremented or decremented in relation to the existing priority. For the first parameter to be treated as a relative value, the second parameter, flags, must be set to 0. If it is set to TPABSOLUTE, the priority value of the next request that is sent out will receive the absolute value of the integer contained in the prio parameter. The absolute value of prio must be in the range of 1 to 100. If the value is not in this range, the system uses the default value, 50.

Listing 2-22 shows an excerpt from the TRANSFER service acting as a client process to call services of WITHDRAWAL. It invokes tpsprio() to increase the priority of the request message it sends in its synchronous call to WITHDRAWAL. It does so to prevent the request from being queued for the WITHDRAWAL service (and later the DEPOSIT service) after already having waited on the TRANSFER queue.

Listing 2-22 Setting the Priority of a Request Message
/* 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);
}

Initiating a Conversational Connection

The discussion in this chapter has centered around how client programs initiate a request/response service request. Client programs can also connect to conversational servers by using tpconnect() instead of tpcall() or tpacall(). Chapter 4, "Conversational Clients and Services," describes that in detail.

Sending a Broadcast Message

The tpbroadcast() function is used to send an unsolicited message to registered clients within the application. It is mentioned in this chapter on client programs because it can be called by clients. A more complete discussion of its use can be found in Chapter 3, "Writing Service Routines."

Compiling Client Programs

To compile your client programs you have several methods to choose from. You can use regular C Compilation System utilities to make object files. The object files can be kept as individual files or collected into an archive file. If you prefer, you can retain your programs as source (.c) files. In any event, when you invoke buildclient to produce an executable client, you specify your input files on the command line with the -f option.

The buildclient Command

buildclient(1) is used to put together an executable client program. Options identify the name of the output file, input files provided by the application, and various libraries.

buildclient invokes the UNIX cc command. The environment variables CC and CFLAGS can be set to name an alternative compile command and to set flags for the compile and link edit phases.

The buildclient -o Option

The -o option is used to assign a name to the executable output file. If no name is provided, the file is named a.out.

The buildclient -f and -l Options

The -f and -l options are used to specify files to be used in the link edit phase. The files specified in the -f (first) option are brought in before the BEA TUXEDO system libraries, whereas the files specified in the -l (last) option are brought in after these libraries. There is a significance to the order of the options. The order is dependent on function references and in what libraries the references are resolved. Input files should be listed ahead of libraries that might be used to resolve their references. If input files are .c files, they are first compiled. Object files can be either separate .o files or groups of files in archive (.a) files. If more than a single file name is given as an argument to a -f or -l option, the syntax calls for a list enclosed in double quotes. You can use as many -f and -l options as you need.

The following represents the command line that was used to create the audit executable program. The environment variable CC is set to cc and the environment variable CFLAGS is set to -I $TUXDIR/include.

buildclient -o audit -f audit.o


[Top] [Prev] [Next] [Bottom]