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):
It initiates a request for service by using tpconnect() rather than tpcall() or tpacall().
It passes the service request to a conversational server.
A conversational service differs in the following ways from a request/response service (described in Chapter 3):
It is part of a server identified in the configuration file as offering only conversational services.
It is prohibited from making a call to tpforward().
Both conversational clients and servers have the following characteristics:
The logical connection between them remains active until terminated; any number of messages can be transmitted across the connection.
They use tpsend() and tprecv() calls to send and receive data in conversations.
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.
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.
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,
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.
tpconnect()
is the
ATMI
function used to set up a conversation.
The syntax is:
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.
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 the section
"Values for the flags Argument: tpcall()"
of Chapter 2.
They are:
TPRECVONLY
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 Figure 1.
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:
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.
There are four valid values for the
flags
argument of
tpsend().
Three of them:
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.
Figure 2 shows
tpsend()
used in a code fragment.
The function used to receive data sent over an open
connection is
tprecv().
The syntax is:
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
tprecv()
has four valid
flags.
Three have been described previously in Chapter 2
in the section called
Figure 3 shows a fragment of code using
tprecv().
There are three ways in which
the connection can be taken down in an orderly fashion and
the conversation ended normally.
Figure 4 and Figure 5 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 TUXEDO System/T."
Figure 4 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 5 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.
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:
If the connection originated in a server,
the originator turns over control of the connection to
the called process.
That process can then call
tpreturn().
This is illustrated in Figure 4 above.
A subordinate process can call
tpreturn().
The subordinate must have control of the connection and
must make the call to
tpreturn()
before the originator does.
This is illustrated in Figure 5 above.
In each case, the subordinate has control and calls
tpreturn().
There are five events recognized in conversational
communication.
All five can be posted for
tprecv(),
three of the five can be posted for
tpsend().
Figure 6 summarizes them.
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:
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().
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 5 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().
There are some parameters in the configuration file that
pertain only to conversational processing.
As noted in Chapter 1 in the section called
"Configuration File",
the System/T 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:
CONV = { Y/N }
MIN and MAX
MAXSERVERS
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.
Conversational Mode
The Connection Descriptor
Buffer Management
Joining an Application
Establishing a Connection
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 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.
Values for the flags Argument: tpconnect()
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.
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.
Fig. 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
int
tpsend(cd, data, len, flags, revent)
int cd;
char *data;
long len;
long flags;
long *revent;
Values for the flags Argument: tpsend()
TPNOBLOCK
TPNOTIME
TPSIGRSTRT
have the same meaning described in Chapter 2 in the
section
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.
Fig. 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
int
tprecv(cd, data, len, flags, revent)
int cd;
char **data;
long *len;
long flags;
long *revent;
Values for the flags Argument: tprecv()
TPNOCHANGE
TPNOTIME
TPSIGRSTRT
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.
Fig. 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
Subordinate Calls tpreturn()
Fig. 4: Simple SENDONLY Connection and Return
Hierarchy of Connections and tpreturn()
Fig. 5: Connection Hierarchy
Ending a Conversation: Summary
Events and Their Significance
Fig. 6: Conversational Communication Events
Event
Rec'd 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
int
tpdiscon(cd)
int cd;
cd
is the connection descriptor returned by
tpconnect().
Request/Response Calls and Conversations
Configuration Parameters
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 value is adequate.
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.
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.
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 won't 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