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

4. Conversational Clients and Services


Writing Conversational Clients and Services

This chapter covers the subject of conversational clients and services.

A conversational client differs in the following ways from a request/response client (described in Chapter 2, "Writing Client Programs,"):

A conversational service differs in the following ways from a request/response service (described in Chapter 3, "Writing Service Routines,"):

Both conversational clients and servers have the following characteristics:

Conversational Mode

In the conversational mode of communication, a half-duplex connection is established between the client (or initiator) and a server. Control of the connection can be passed back and forth between the initiator and the subordinate server. At any point in the conversation, the process that has control can send messages; the process that does not have control can only receive. The connection remains up until an event occurs that tears it down. One event, TPEV_SENDONLY, notifies the receiving program that control of the connection has been passed to it and it can successfully call tpsend(). Other events are notifications that something significant has occurred; they have the result of either bringing the conversation to a normal conclusion or precipitating a disorderly disconnection.

The Connection Descriptor

A connection descriptor, cd, is returned when a connection is established with tpconnect(). The cd is used to identify subsequent message transmissions with a particular conversation. A client or conversational service can have more than one conversation active simultaneously. The maximum number is 64.

Buffer Management

Data is passed in typed buffers just as in request/response mode. The buffer types must be recognized by the application; they must be allocated with ATMI functions as described in Chapter 2, "Writing Client Programs," and in tpalloc(3c) in the BEA TUXEDO Reference Manual.

Joining an Application

Conversational clients must join the application via a call to tpinit() prior to attempting to establish a connection to a service. The procedure for joining the application is described in Chapter 2, "Writing Client Programs."

Establishing a Connection

tpconnect() is the ATMI function used to set up a conversation. The syntax is:

int
tpconnect(name, data, len, flags)
char *name, *data;
long len, flags;

name must point to the name of a service posted in the bulletin board by a conversation server. If name is not a pointer to a conversational service, the call fails with a -1 and tperrno is set to the error code TPENOENT. If the calling program has already reached the maximum number of active connections allowed, the call will fail with the error code TPELIMIT.

Data can be sent at the same time the connection is being established by having data point to a buffer previously allocated by tpalloc(). The type and subtype of the buffer pointed to by data must be a type recognized by the service being called. If no data is being sent, data can be set to NULL. len is used to specify how much of the buffer to send. If the buffer is self-defining (for example, an FML buffer), len can be set to 0. The conversational service being called receives the data and len pointers via the TPSVCINFO data structure passed to it by main() when the service is invoked. So far, this should sound a lot like what happens when a request/response service is invoked, because it is. Differences begin to appear when we consider options for the flags argument.

Values for the flags Argument: tpconnect()

As with other ATMI functions, the behavior of the called program can be controlled by values of the flags argument of tpconnect(). Four of the values are identical to their use in tpcall() and are described in "Values for the flags Argument: tpcall()" in Chapter 2, "Writing Client Programs." They are:

TPNOTRAN    TPNOBLOCK
TPNOTIME TPSIGRSTRT

New valid flags options are:

TPSENDONLY
The calling program retains control of the connection, and the called service is permitted only to receive. The called service learns of this through the flags member of its TPSVCINFO structure; TPSVCINFO->flags == TPRECVONLY. TPSENDONLY and TPRECVONLY are mutually exclusive; one or the other must be specified.

TPRECVONLY
Control of the connection is being passed to the called service, and the called service can only send. The called service learns of this through the flags member of its TPSVCINFO structure; TPSVCINFO->flags == TPSENDONLY. TPSENDONLY and TPRECVONLY are mutually exclusive; one or the other must be specified.

As mentioned above, on successful completion tpconnect() returns a connection descriptor that is used in all subsequent calls of the conversation. Your call to tpconnect() should be coded something like that shown in Listing 4-1.

Listing 4-1 Establishing a Conversational Connection
#include atmi.h
#define FAIL -1
int cd1; /* Connection Descriptor */
main()
{
if ((cd = tpconnect("AUDITC",NULL,0,TPSENDONLY)) == -1) {
error routine
}
}

Sending

After the conversational connection is set up, communication between the client (or initiator) and the service is accomplished with send/receive calls. The connection is half-duplex. That means communication can be in only one direction at a time. The process that has control of the connection can send; the process that does not have control can receive. Initially, control is decided by the originator and is specified by the TPSENDONLY or TPRECVONLY flag value of the tpconnect() call; TPSENDONLY means control is retained by the originator, TPRECVONLY means control is given to the called service. After tpconnect() returns successfully, data is sent across the open connection with the tpsend() function.

The syntax of tpsend() is:

int
tpsend(cd, data, len, flags, revent)
int cd;
char *data;
long len;
long flags;
long *revent;

cd is the connection descriptor returned by tpconnect() that identifies the connection over which to send the data. *data and len are, respectively, a pointer to a buffer created by tpalloc(), and the length of the data to be sent. The same rules apply to data and len that have been outlined earlier: The buffer must be of a type recognized by the program that receives it and length can be 0 if the buffer is self-defining. There is no requirement that data be sent. If the data pointer is NULL, len is ignored.

Values for the flags Argument: tpsend()

There are four valid values for the flags argument of tpsend(). Three of them:

TPNOBLOCK
TPNOTIME
TPSIGRSTRT

have the same meaning described in Chapter 2 (in "Values for the flags Argument: tpcall()" section). The fourth value is like one that is used in tpconnect(), but has added significance in this function.

TPRECVONLY
Signals the intent of the calling program to issue no more tpsend() calls at the moment and to pass control of the connection over to the other side of the connection. When the called program receives the data, it also receives a TPEV_SENDONLY event at the address pointed to by revent.

It is not a requirement that control be passed each time the tpsend() call is made. The process authorized to make tpsend() calls on the connection can make as many calls as necessary before turning over control of the connection. In fact, the logic of the conversational program may be such that one side of the conversation retains control of the connection throughout the life of the conversation.

Listing 4-2 shows tpsend() used in a code fragment.

Listing 4-2 Sending Data in Conversational Mode
if (tpsend(cd,line,0,TPRECVONLY,revent) == -1) {
(void)userlog("%s: tpsend failed tperrno %d",
argv[0],tperrno);
(void)tpabort(0);
(void)tpterm();
exit(1);
}

Receiving

The function used to receive data sent over an open connection is tprecv(). The syntax is:

int
tprecv(cd, data, len, flags, revent)
int cd;
char **data;
long *len;
long flags;
long *revent;

cd is the connection descriptor. If the function is being issued from a subordinate program (that is, not the originator of the connection), cd is in the TPSVCINFO structure for the program. If tprecv() is being issued by the originator, cd is the descriptor returned by tpconnect(). When the call is made, *data is a pointer to the address of a previously tpalloc'd buffer and len is a pointer to the size of the buffer. len, data, and *data are not allowed to be NULL. The call fails and tperrno is set to TPEINVAL.

Upon successful return, *data points to the data received and len contains the size of the buffer. If len is greater than the total size of the buffer before the call to tprecv(), it indicates the buffer's new size. If len is 0, no data was received.

If an event exists for cd, tprecv() returns a -1 and tperrno is set to TPEEVENT. The event type is returned in revent. With events TPESVCSUCC, TPESVCFAIL, and TPESENDONLY, data can be received. These three events are all normal completions of the tprecv() call, so it is not correct to assume the -1 return value means the call has failed. A more complete discussion of events can be found in "Events and Their Significance" later in this chapter.

Values for the flags Argument: tprecv()

tprecv() has four valid flags. Three of them are described in Chapter 2 (in the "Values for the flags Argument: tpcall()" section). They are:

The fourth valid flag value is TPNOBLOCK.

When the flag is set, tprecv() does not wait for data to arrive. If data is available, fine; tprecv() gets the data and returns. If data is not available, the call fails and tperrno is set to TPEBLOCK. When the flag is not set, tprecv() waits and does not return until data arrives or a timeout occurs.

Listing 4-3 shows a fragment of code using tprecv().

Listing 4-3 Receiving Data in Conversation
if (tprecv(cd,line,len,TPNOCHANGE,revent) != -1) {
(void)userlog("%s: tprecv failed tperrno %d revent %ld",
argv[0],tperrno,revent);
(void)tpabort(0);
(void)tpterm();
exit(1);
}

Ending a Conversation

There are three ways in which the connection can be taken down in an orderly fashion and the conversation ended normally. Figure 4-1 and Figure 4-2 show two scenarios that help to illustrate how conversations are ended where global transactions are not involved. The third approach of ending a conversation where a transaction is involved is shown in Chapter 5, "Global Transactions in BEA TUXEDO System."

Subordinate Calls tpreturn()

Figure 4-1 shows a simple A to B conversation. The connection is set up initially with a call to tpconnect() with the TPSENDONLY flag set. In due course, A turns control of the connection over to B by calling tpsend() with the TPRECVONLY flag set. This generates a TPEV_SENDONLY event. The next call by B to tprecv() returns a -1, tperrno is set to TPEEVENT, and revent shows the event TPEV_SENDONLY. B knows from the TPEV_SENDONLY event that it now controls the connection. Subsequently, B calls tpreturn() with rval set to TPSUCCESS. This generates a TPEV_SVCSUCC event for A. The call to tpreturn() also brings down the connection. When A calls tprecv() and learns of the event, it recognizes that the conversation has been terminated. Data can be received on this call to tprecv() even if the event is TPEV_SVCFAIL. In this illustration, A can be either a client or a server, B can only be a server.

Figure 4-1 Simple SENDONLY Connection and Return

Hierarchy of Connections and tpreturn()

Figure 4-2 shows a hierarchy of connections. The scenario applies to a service in a conversation, B, that has initiated a connection to a second service, C. In other words, there are two active connections, A to B, and B to C. If B is in control of both connections, a call to tpreturn() has the following effect: the call will fail, a TPEV_SVCERR event will be posted on all open connections, and the connections will be closed in a disorderly manner. The proper sequence is for B to call tpsend() with the TPRECVONLY flag set on the connection to C, turning control of the B-C connection over to C. C can then call tpreturn() with rval set to TPSUCCESS, TPFAIL or TPEXIT, as appropriate. B can then call tpreturn(), posting an event (either TPEV_SVCSUCC or TPEV_SVCFAIL) for A. Both connections are terminated normally.

Figure 4-2 Connection Hierarchy

Ending a Conversation: Summary

It is an error to end a conversation with connections still open. Either tpcommit() or tpreturn() will fail in a disorderly manner.

To summarize the ways a conversation can be ended in an orderly manner:

In each case, the subordinate has control and calls tpreturn().

Events and Their Significance

There are five events recognized in conversational communication. All five can be posted for tprecv(), three of the five can be posted for tpsend(). Table 4-1 summarizes them.

Table 4-1 Conversational Communication Events

Event Rec'By Meaning

TPEV_SENDONLY

tprecv()

control of the connection has been passed; this process can now call tpsend()

TPEV_DISCONIMM

tpsend() tprecv() tpreturn()

a disorderly disconnect; the connection has been torn down; no further communication is possible; posted by tpdiscon() in the originator of the connection, and posted to all open connections when tpreturn is called while connections to subordinate services remain open. All connections are closed in a disorderly fashion. If a transaction exists, it is aborted.

TPEV_SVCERR

tpsend()

received by the originator of the connection, usually indicates the subordinate program has issued a tpreturn without having control of the connection

tprecv()

received by the originator of the connection, indicates the subordinate program has issued a tpreturn with TPSUCCESS or TPFAIL and a valid data buffer, but an error occurred that prevented the call from completing

TPEV_SVCFAIL

tpsend()

received by the originator of the connection, indicates the subordinate program has issued a tpreturn without having control of the connection, and tpreturn was called with TPFAIL or TPEXIT and no data

tprecv()

received by the originator of the connection, indicates the subordinate service finished unsuccessfully (tpreturn was called with TPFAIL or TPEXIT)

TPEV_SVCSUCC

tprecv()

received by the originator of the connection, indicates the subordinate service finished successfully, that is, called tpreturn() with TPSUCCESS

Disorderly Disconnection

The tpdiscon() function has an innocent sound to it, as though it was the logical opposite of tpconnect(), but it is really the equivalent of pulling the plug on the connection. It can be called only by the initiator of a conversation.

The syntax is simple:

int
tpdiscon(cd)
int cd;

cd is the connection descriptor returned by tpconnect().

tpdiscon() generates a TPEV_DISCONIMM event for the service at the other end of the connection, and the cd is no longer valid. If a transaction is in progress, it is aborted. Data may be lost. If tpdiscon() is called from a service that was not the originator of the connection identified by cd, it fails with an error code of TPEBADDESC.

The preferred way of bringing down a connection is for the subordinate to call tpreturn().

Request/Response Calls and Conversations

There is nothing that prevents a conversational service from making request/response calls if it needs to communicate with another service. In the examples of connection hierarchies shown in Figure 4-2 above, the calls from B to C could have been made with tpcall() or tpacall() instead of tpconnect(). Remember, however, that conversational services are not permitted to make calls to tpforward().

Configuration Parameters

There are some parameters in the configuration file that pertain only to conversational processing. As noted in Chapter 1 (in the "Configuration File" section), the BEA TUXEDO system administrator normally is responsible for setting up the production version of the configuration file for the application, but you may need to set some parameters in your own development configuration.

Here are the parameters you need to know about:

MAXCONV
sets the maximum number of simultaneous conversations for a single machine. The range is from 0 to 32,767. The default is 10 when conversational servers are specified. The parameter can be specified in the RESOURCES section for all machines in the configuration and can be overridden in the MACHINES section for each machine. It is quite probable that for an application under development the default is adequate.

CONV = { Y/N }
is a parameter in the SERVERS section. Connections can only be made to servers that have this value set to Y. If it is set to N or left unspecified, a tpconnect() call to a service of the server will fail.

MIN and MAX
are parameters in the SERVERS section that specify the minimum and maximum number of occurrences of the server to be started by tmboot(1). If not specified, MIN defaults to 1 and MAX defaults to MIN. The same parameters are available for use with request/response servers. However, conversational servers are automatically spawned as needed. So if you set MIN = 1 and MAX = 10, for example, tmboot starts one initially. When a tpconnect() call is made to a service offered by that server, the system starts up a second copy. As each copy is called a new one is spawned up to a limit of 10.

MAXSERVERS
specifies the high-water mark for all servers of the configuration. This figure needs to take into account the MAX values for all conversational servers. You probably will not need to worry about this for an application under development, but it could be something that needs attention when the application reaches the production stage. The parameter is in the RESOURCES section.

Building Conversational Clients and Servers

The utilities described in Chapters 2 and 3, buildclient(1) and buildserver(1), are used for building conversational clients and servers.

Conversational servers must be built only with conversational services; that is, mixing of conversational services and request/response services in the same server is not allowed.

Conversational services and request/response services can not use the same name.



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