The Common Programming Interface-Communications (CPI-C) provides a way for applications to communicate and exchange data across multiple platforms and operating systems. It allows programmers to create elegant client/server applications using PCs and AS/400s. The CPI-C API provides a standard set of function calls for getting session information, establishing communications, sending and receiving data, and confirming receipt of the data. It can be used from almost any SNA platform and may be used with several programming languages as well. The beauty of the CPI-C API is the ease of establishing communication with it; it can take as few as six function calls to begin the communication session. This article will provide an introduction to CPI-C programming by looking at one of the simplest CPI-C programs, APING.
Before looking at the program, we should look at some of the details of how a CPIC session is established. At its simplest level, a CPI-C session is merely a conversation between two transaction programs (TPs). In the example shown in Figure 1, a TP is started on the server and awaits a connection request from a client. The TP on the client then allocates a session and begins the transfer of data between the two TPs. The TP on the client is known as the invoking TP, since it establishes the communication. The TP on the server is the invoked TP, because it waits to begin processing until a client makes a request. All of this communication occurs over a previously created LU 6.2 pairing, and the CPI-C TPs must know the names of both LUs, as well as the mode and security level of the conversation. This information is usually contained in the communications side information (CSI) object, which is passed to the CPI-C TP as an argument at startup. In Client Access/400, the configuration of the LU 6.2 pair and the CSI object takes place in the NetSoft Router. (For more information on how this works, see Enabling CPIC-C Communications in Client Access/400 for Windows 95, Client Access/400 Expert, May/June 1997.) After the connection to the AS/400 is up, the CPI-C TPs are ready to communicate.
The APING and APINGD Transaction Programs
It should be obvious that CPI-C only works between a pair of programs. APING is no exception to this rule, and in order to understand how it communicates, we need to look both at APING and its server-side partner, APINGD. The APING program is a simple way to test communications between two Advanced Program-to-Program Communications (APPC) platforms. It functions in the same manner as the more familiar TCP/IP PING program, with one computer sending out messages and timing the response from another remote computer. APING is the client side of the equation, the invoking TP, and is passed the name of a server LU that is running APINGD. APINGD is the invoked TP, and, like most server-side TPs, is designated by the letter-D suffix. Any examination of the CPI-C API must begin with an invoked TP that has been started on a server and is awaiting a connection from a client. Figure 2 shows the order of the function calls and the flow of conversation between our two TPs.
Figures 3 and 4 show the code listings for APING and APINGD. Because the code is so extensive, Ive shown only the relevant CPI-C function calls.
The CPI-C error-handling routines have also been removed. The full versions of the programs illustrated here are by Peter J. Schwaller and are available from the APPC (Advanced Program-to-Program Communications) forum file libraries on CompuServe. (The text about these programs states that they are fair game for just about any purpose, so if you have access, you should pull them down and see what uses you can find for the code.)
The first call each program makes is WinCPICStartup. The CPI-C API is currently at version 1.2, but some additional functions have been added to adapt CPI-C to the Windows environment. The WinCPIC API contains all of the functions from CPI-C 1.2, as well as support for asynchronous communications. With the exception of WinCPICStartup, which initializes the API and the Windows environment, APING and APINGD do not use the WinCPIC extensions. Next, APINGD must prepare for a conversation from a client TP. It issues the cmsltp function (Specify_Local_TP_Name) to set the local transaction program name, which the client will call when it requests a conversation. APINGD next issues cmaccp (Accept_Conversation) and is ready for the client to connect. Notice that cmaccp uses the cm_conv_id variable. This will identify the CPI-C conversation once a client has connected and will be used by all subsequent function calls. Since APINGD does not use the asynchronous WinCPIC functions, it must also issue cmwait (Wait_For_Conversation) in case the client does not connect immediately. At this point, APINGD is blocked, meaning that no further processing will take place until the CPI-C function call is completed.
Conversations between TPs
APING is now ready to invoke a conversation with APINGD. Before we look at its function calls, it is necessary to talk about conversation states. A TP can only be in a send state or a receive state, but never both, so that only one TP in a conversation may send data at a time. At any time, a programmer may use the cmecs (Extract_Conversation_State) function to learn the current state of a TP. In addition, the send or receive type may be used to specify how a TP will handle data. APING begins by using cmsptr (Set_Prepare_To_Receive_Type) to set Cm_Prep_To_Receive_Flush, which is used to flush the contents of the local LUs send buffer and change the conversation to the receive state. APING then issues cmallc (Allocate) to initiate a conversation with APINGD, which also sets the conversation back to the send state. Most problems with CPI-C programs or LU setups will appear as return error codes from the Allocate function, so be sure to have a good error-handling routine ready. After the successful
Allocate, APINGD uses cmepln (Extract_Partner_LU_Name) to learn the name of the LU that it is communicating with.
APING and APINGD are now ready to send data packets back and forth. APING is currently in the send state and APINGD is in the receive state. APING begins by using the cmsst (Set_Send_Type) function to fill the send buffer with data. It will then issue cmsend (Send) for a number of consecutive times specified by the user of the starting APING, minus one. APINGD will perform an equal number of cmrcv (Receive) functions. Before the last send, APING will use the Set Send Type function again to indicate what type of response it expects from APINGD.
If it sets the send type to Cm_Send_And_Prep_To_Receive, APING will change to the receive state, and APINGD will change to the send state. APINGD may now use the Send function to echo APINGs communication. If, however, APING sets the send type to Cm_Send_And_Confirm, APINGD will only confirm that it has received the data, but will remain in the receive state. After responding with data or a confirmation, APINGD will use the Set Send Type function to return APING to the send state. This entire process may be performed once or repeated many times, depending on a variable specified by the user when starting APING. It should be apparent from this example that the conversation state is critical to a successful CPI-C conversation, and any pair of TPs must carefully coordinate their changing roles.
With the data exchanged, the conversation may now be terminated. As the invoking TP, APING takes care of this chore, although, in theory, either TP could perform this function. APING uses cmsdt (Set_Deallocate_Type) to control what type of deallocation will be performed, in this case using Cm_Deallocate_Flush to empty the send buffer. APING then issues the cmdeal (Deallocate) function and ends the conversation.
While APING and APINGD are relatively simple programs, they do illustrate the basic principles of a CPI-C conversation. The full context of the CPI-C function calls listed in this article may be found in any CPI-C reference. It is most critical, however, that CPI-C TPs have the correct conversation state and partner information. If they dont, the programs will fail to communicate. Once these concepts are understood, a programmer can create powerful client/server applications with minimal effort and take full advantage of CPI-Cs cross-platform nature.
References:
AS/400 Data and Networking Communications Sourcebook by Kris Neely, Midrange Computing
Common Programming Interface- Communications Reference (SC26-4399) Microsoft SNA Server CPI-C Programmers Guide, Microsoft
Figure 1: A CPI-C Conversation
Overview of APING CPI-Flows
Client (APING) Server (APINGD) Set up conversation Allocate Accept Conversation
For number of iterations (x)
For number of consecutive packets (z)
Send Data Receive For number of consecutive packets (z) Receive Send Data
Shut down the conversation
Deallocate (FLUSH)
Figure 2: CPI-C Program Flows in APING
/*****************************************************************************
* MODULE NAME: APING.C
*****************************************************************************/
/* These are the defaults to be used if the user does not provide arguments */
/* to override these values. */
#define DEFAULT_TP_NAME APINGD
#define DEFAULT_MODE_NAME #INTER
#define DEFAULT_SYM_DEST APINGD
#if (defined(WIN32) || defined(WINDOWS)) /*WIN32*/
/****************************************************************WIN32*/
/* Initialisation for WinCPIC *WIN32*/
/****************************************************************WIN32*/
if (WinCPICStartup(WinCPICVERSION,&CPICData)) /*WIN32*/
{ /*WIN32*/
return ; /*WIN32*/
} /*WIN32*/
#endif /*WIN32*/
CM_PREPARE_TO_RECEIVE_TYPE prep_to_receive = CM_PREP_TO_RECEIVE_FLUSH;
cmsptr(cm_conv_id, &prep_to_receive, &cm_rc); /* Set prepare to receive type */
cmallc(cm_conv_id, &cm_rc);
CM_SEND_TYPE send_type = CM_BUFFER_DATA;
cmsst(cm_conv_id, &send_type, &cm_rc);
start_time = get_time();
for (curr_concurrent = 1; /* Start current at one so we */
curr_concurrent
curr_concurrent++ ) { /* loop than the specifed number */
cmsend(cm_conv_id, buffer, &length, &rts_received, &cm_rc);
}
/*
* For the final send in the number of concurrent sends, set the send
* type to do a send and a prepare to receive. This will send both
* the data and the send permission indicator to our partner all at
* once.
*
* If the one_way_flag has been set, we will issue a Confirm along
* with the Send_Data. This will allow us to know when the partner
* has actually received all the data so we can get an accurate
* timing.
*
* On the partner side, if Send status is received, the partner will
* know to echo the data. If Confirm status is received, the partner
* will know to issued Confirmed and then get ready to receive
* more data, since the partner wont be echoing.
*/
{
CM_SEND_TYPE send_type;
if (flags.one_way_flag != 1) {
send_type = CM_SEND_AND_PREP_TO_RECEIVE;
} else {
send_type = CM_SEND_AND_CONFIRM;
}
cmsst(cm_conv_id, &send_type, &cm_rc);
}
cmsend(cm_conv_id, buffer, &length, &rts_received, &cm_rc);
if (flags.one_way_flag != 1) {
max_receive_len = flags.size;
do {
cmrcv (cm_conv_id, /* Receive Data */
buffer, /* Data Pointer */
&max_receive_len, /* Size of Data Buffer */
&data_received, /* returned - data received */
&received_len, /* returned - length of data */
&status_received, /* returned - status received */
&rts_received, /* returned - request to send */
&cm_rc);
}
if (data_received != CM_NO_DATA_RECEIVED) {
curr_concurrent;
}
} while ((status_received != CM_SEND_RECEIVED));
/* Repeat the receive loop until SEND permission has been rcvd. */
if (curr_concurrent != 0) {
write_error(
ERROR. );
write_error(
Partner did not send the expected number of records. );
}
} else {
}
end_time = get_time(); /* stop timer */
elapsed_time = end_time - start_time; /* calculate elapsed time */
write_output(%16ld, elapsed_time);
write_output(%17lu, flags.size * flags.number_concurrent *
flags.one_way_flag);
{
CM_DEALLOCATE_TYPE deallocate_type = CM_DEALLOCATE_FLUSH;
cmsdt(cm_conv_id, &deallocate_type, &cm_rc);
}
cmdeal(cm_conv_id, &cm_rc); /*****************************************************************************
* MODULE NAME: APINGD.C
*****************************************************************************/
Figure 3: APING CPI-C Function Calls
#if (defined(WIN32) || defined(WINDOWS)) /*WIN32*/
/****************************************************************WIN32*/
/* Initialisation for WinCPIC *WIN32*/
/****************************************************************WIN32*/
if (WinCPICStartup(WinCPICVERSION,&CPICData)) /*WIN32*/
{ /*WIN32*/
return ; /*WIN32*/
} /*WIN32*/
#endif /*WIN32*/
cmsltp(APINGD,&temp,&cm_rc);
cmaccp(cm_conv_id, /* Accept Conversation */
&cm_rc);
/*
* Note that as we have used cmsltp to specify our local TP name,
* cmaccp may return asynchronously, so we must do a cmwait
*/
if (cm_rc == CM_OPERATION_INCOMPLETE) /*WIN32*/
{ /*WIN32*/ cmwait(cm_conv_id, &cm_rc, &temp); /*WIN32*/
} /*WIN32*/
if (cm_rc != CM_OK)
{
cmepln(cm_conv_id, (unsigned char *)destination, &pln_length, &cm_rc );
}
CM_PREPARE_TO_RECEIVE_TYPE prep_to_receive = CM_PREP_TO_RECEIVE_FLUSH;
cmsptr(cm_conv_id, &prep_to_receive, &cm_rc); /* Set prepare to receive type */
do {
unsigned long count; /* number of consecutive */
/* sends or receives */
count = 0; /* initialize count of recvs */
do {
cmrcv (cm_conv_id, /* Receive Data */
buffer, /* Data Pointer */
&max_receive_len, /* Size of Data Buffer */
&data_received, /* returned - data received */
&received_len, /* returned - length of data */
&status_received, /* returned - status received */
&rts_received, /* returned - request to send */
&cm_rc);
if (data_received != CM_NO_DATA_RECEIVED) {
count++; /* keep track of receives */
}
} while ( (status_received != CM_SEND_RECEIVED) &&
(status_received != CM_CONFIRM_RECEIVED) &&
!cm_rc);
/*
* loop until we get permission to send data or until error
*/
if (cm_rc != CM_OK) {
if (cm_rc == CM_DEALLOCATED_NORMAL) {
do_exit(EXIT_SUCCESS);
} else {
cpicerr_handle_rc(cpicerr, MSG_CMRCV, cm_rc);
}
}
if (status_received != CM_CONFIRM_RECEIVED) {
/*
* count is now equal to the number of data blocks we received
* now we will send back the same number of data blocks of equal
* size
*/
{
CM_SEND_TYPE send_type = CM_BUFFER_DATA;
cmsst(cm_conv_id,
&send_type,
&cm_rc);
if (cm_rc != CM_OK) cpicerr_handle_rc(cpicerr, MSG_CMSST, cm_rc);
}
/* send back the same number except for one */
for ( count; count && !cm_rc; count ) {
length = received_len;
cmsend(cm_conv_id,
buffer,
&length,
&rts_received,
&cm_rc);
if (cm_rc != CM_OK) {
cpicerr_handle_rc(cpicerr, MSG_CMSEND, cm_rc);
}
}
/*
* Set the send type to do a send and a prepare to receive.
* This will send both the data and the send permission indicator
* to our partner all at once.
*/
{
CM_SEND_TYPE send_type = CM_SEND_AND_PREP_TO_RECEIVE;
cmsst(cm_conv_id,
&send_type,
&cm_rc);
if (cm_rc != CM_OK) cpicerr_handle_rc(cpicerr, MSG_CMSST, cm_rc);
}
length = received_len;
cmsend(cm_conv_id,
buffer,
&length,
&rts_received,
&cm_rc);
if (cm_rc != CM_OK) cpicerr_handle_rc(cpicerr, MSG_CMSEND, cm_rc);
} else {
/*
* The partner has requested one way data transfer only.
* Well just issue Confirmed, then go back up to receive
* more data.
*/
cmcfmd(cm_conv_id,
&cm_rc);
if (cm_rc != CM_OK) cpicerr_handle_rc(cpicerr, MSG_CMCFMD, cm_rc);
}
} while (cm_rc == CM_OK);
do_exit(EXIT_SUCCESS);
}
Figure 4: APINGD CPI-C Function Calls
LATEST COMMENTS
MC Press Online