Sep 1 ’03
VSE TCP/IP PROGRAMMING
For many years, SNA has been the communications method for IBM mainframes. However, the IBM mainframe has been dragged — clawing and screaming — into the world of IP. Now application programmers are being asked to write TCP/IP programs to communicate with off-platform applications. These off-platform applications can be a simple customer balance inquiry or a complicated modeling tool. The primary IBM Application Programming Interface (API) for IP is the EZA interface. The EZASMI macro is used for Assembler while the EZASOKET High-Level Language (HLL) interface is used for COBOL and sometimes Assembler. Although additional options are available when using the macro interface, a programmer can easily write a full-featured IP program using the HLL interface. Examples in this article will discuss the HLL interface with the end result being a programming example designed for use in a CICS environment. However, the calls discussed here can also be used in the batch environment. There are actually three major methods to use IP: Transmission Control Protocol (TCP), User Datagram Protocol (UDP), and Internet Control Message Protocol (ICMP). An application programmer would normally only use TCP, so this article will not discuss UDP or ICMP.
There are two sides of a TCP/IP conversation — the server side and the client side. A mainframe programmer may be required to write either side. A programmer will normally write a server program when the mainframe has data that some other system needs, while a client program will normally be required when the mainframe needs to extract data stored on another system. “Normal” does not mean “always.” A mainframe program that would normally be designed as the client might be designed to be the server side of the conversation simply because the mainframe has greater availability (i.e., uptime).
In this article, I will first discuss the simpler programming requirements for the client side, then move into the more complex server programming. You can download several executable sample TCP/IP programs from www.vse2pdf.com/coolstuff. I recommend that you download the BSTTEZ10, BSTTEZ11, BSTTEZ12, and BSTTEZ13 programs, along with the BSTTEZA copybook. Figures 1 through 8 are also available for download at www.vse2pdf.com/coolstuff. They are condensed versions of the main EZA API calls extracted from these programs. To shorten the sample code, I have combined multiple lines when it would not greatly affect the readability of the sample code.
Before I discuss the actual programming, let’s examine the basics of using the EZA HLL API. Figure 9 shows a basic call.
The function field will indicate to the API what service is being requested. The input_options may be several fields that are used by the API, but are not updated. The output_options may be several fields that will be updated by the API. The field errno provides detailed information about any errors but is only valid when retcode is “-1.” A zero or positive value in retcode indicates a successful call. For many calls, there will not be any “output options” fields as such. Instead, the response from the call is solely contained within the retcode field.
In the samples in Figures 1 through 8, any option that is not normally used by a programmer is set in the coding paragraph. All other options need to be set by the program logic prior to performing the specific paragraph. Examples of how to set the input option fields are included at the start of each paragraph as comments. Examples of how to handle the output option fields are included at the end of each paragraph as comments.
There are several rules you can follow to handle the retcode and errno fields:
- If retcode is -1, an error occurred during the call. Any other number (including negative numbers) indicates a successful completion.
- The errno field contains the actual error code only when the retcode indicates an error. At other times, this field is unpredictable.
- For calls that return data to the program, the retcode field contains the length of the data returned. It is not unusual for a receive type call to return a zero length. The programmer should be ready to issue another receive.
WRITING A CLIENT
There are five basic steps to programming the client: initialization, creating a connection, transferring the data, closing the connection, and termination. Figure 10 shows the process flow between a client and a simple server.
The primary unitization call is the INIT-API call. This call establishes the TCP/IP environment for use by the programmer. Lines 1 through 9 of Figure 1 show this call.
Creating a Connection
The first step to establishing a connection is to allocate a “socket.” At a basic level, the SOCKET call allocates storage for use in the conversation. Lines 10 through 19 of Figure 1 show this call. This call will return in EZA-RETCODE a socket “number” that you can use for all following calls. Note that socket numbers start at zero, not one.
After you create the socket, the client program must establish the connection using a CONNECT call. Before this call, the program needs to know the address of the server. The API uses a data area called name to pass this information. The area name contains three fields: family, port, and ipaddress. The family field is always set to +2 to indicate that the connection is to be via IP. The client program must set the port and ipaddress fields prior to calling the API. Lines 20 through 29 of Figure 1 show a sample of this call.
Transferring the Data
To transfer data, the program needs to send a request to the server and then receive the response sent from the server. This can be accomplished using the SEND and RECV calls. Other similar calls can be used, each with slightly different options and capabilities. Lines 30 through 39 and lines 40 through 51 of Figure 2 show valid SEND and RECV calls.
One aspect of TCP/IP communication that many mainframe programmers find confusing is the fact that when data is received, it may not always be complete. This can happen when the amount of data sent exceeds the packet size limits for one of the intermediate segments of the IP communication path between the client and the server. The program must validate the number of bytes received, and when the complete data has not been received, the program must request additional data from the server. Optionally, if the data expected is extremely short (like “GOOD”), the programmer would not have to worry about multiple data receives being required. For simplicity’s sake, the sample assumes that the response is short and does not check the length of the data returned.
Because mainframe data is stored as EBCDIC and most other platforms store data as ASCII, it is normally the function of the mainframe program to handle the EBCDIC/ASCII conversions. IBM provides two routines to handle this function: EZACIC04 and EZACIC05. Lines 52 through 55 of Figure 3 show how these routines are called.
Closing the Connection
Once all data transfer has been performed, the program must close the socket using the CLOSE call. This will release all the storage acquired during the SOCKET call. Other than the socket number, there are no options with the CLOSE call that would normally be used. Lines 56 through 61 of Figure 3 show this type of call.
The programmer should be aware that a successful return from a CLOSE call does not indicate that all data transfer has completed. The IP stack will continue to transfer all data that may be queued for transmission to the other end of the conversation until the transfer is successful or fails. If it fails, there is no indication to the program that a failure occurred.
Finally, the program must disconnect from the IP software system using the TERMAPI call. Lines 62 through 64 of Figure 3 show this type of call. There are no options for this call. This is also the one call that does not have a retcode or errno field, as there are no errors possible with this type of call. Failure to perform the CLOSE or TERMAPI calls as needed will result in “partition GETVIS creep,” where excess partition storage continually decreases until no storage remains. Figure 11 shows the basic calls needed to create a client program.
Making It Better
The code I have discussed has one built-in problem. What happens if the server quits sending before the full receive is finished? The program will appear to “hang” forever during the RECV call. There are a couple of ways you can handle this. One option is to set the socket to “non-blocking” mode. In this mode, the receive will fail immediately if no data is available. The program is required to recognize a “would block” condition and perform another receive. This can use a lot of CPU time. Because of this, there is a better method you can use — the SELECT call. This call places the program in a wait condition until data is actually available, just like a RECV call, but with one exception. The SELECT call allows the programmer to pass a timeout value, that once exceeded, will cancel the wait and return an error to the program. Although a slight delay is still encountered, the length of the delay is controllable and will allow the program to terminate gracefully when the server fails to respond. Unfortunately, this call is more complicated than any of the other examples shown. Although the learning curve for a SELECT call may be steep, the rewards of using the call are well worth the effort. This call also allows the program to wait for multiple conditions on multiple sockets with one single socket call. Lines 65 through 96 of Figure 4 show an example of a SELECT call. Figure 12 shows the actual code that would be inserted into Figure 11 just prior to line 26.
For the SELECT function call, you must create six equal size arrays. The first three arrays pass data to the SELECT function. The last three arrays are responses from the call. Within each set of three arrays, there is a read, write, and exception array. Each array is actually a series of bits set either on or off, depending on which sockets are being processed. Because this bit string is not in what would be considered a standard order, and because bit strings are hard to handle in COBOL, IBM has provided an additional subroutine, EZACIC06, to convert a character array into the correct bit array. It also will perform the reverse to enable a COBOL program to examine the returned information. The character array is a set of 1-byte flags that represent each socket and must be set to either “0” or “1” (x’F0’ or x’F1’). When examining these flags, the very first character represents socket zero, so the index is actually “socket number plus one.” Lines 97 through 111 of Figure 5 show the actual coding to use EZACIC06.
To use the SELECT call to prevent an RECV lock-up, the program needs to set the flag in the RSNDMSK that corresponds to the socket number returned from the original SOCKET call. Lines 8 through 11 of Figure 12 show how the flags are set. Lines 17 through 23 show how the returned values are examined.
In addition to the flag arrays, you need to indicate the highest number socket you will want the function to examine (line 7) and the timeout value (lines 2 and 3). Once the call returns, you need to know whether the call completed due to data being received or due to a timeout. The SELECT call returns the number of sockets to be processed in retcode. If this is zero, then no sockets are ready and you know that a timeout condition occurred.
A SIMPLE SERVER
In the client example, the CONNECT call is used to connect to a server. To convert a client program into a server program, you need to replace the CONNECT with a series of calls that create the server environment and then accept connections from clients.
Creating the Server Environment
Before a client program can connect to a server program, the server program must tell the IP stack that it plans to service a specific port and that it is available to service connection requests. It does this by issuing a BIND followed by a LISTEN.
The BIND call tells the IP stack on which IP port the server wants to accept connections. As in the CONNECT call, this is done using a “name” data area. Where the CONNECT passes both a port and ipaddress, a BIND uses only the port number field. Lines 112 through 121 of Figure 5 show an example of a BIND.
A LISTEN call tells the IP stack to actually start listening for connection requests against the port passed in the BIND call. Lines 122 through 128 of Figure 6 show an example of a LISTEN call.
The LISTEN call actually converts the current socket from “active” to “passive.” This is a one-way conversion. A passive socket cannot be switched back to an active socket. A passive socket cannot be used for sending and receiving data. It only accepts connects, but once it accepts a connection, it will create a new active socket that is used for sending and receiving data. The creation of the new active socket does not destroy the original passive socket. This means that the original socket can continue to accept new connections, each new connection creating a new active socket.
To indicate to the IP stack that your program is ready to communicate, the program must issue an ACCEPT call. Once someone requests a connection against your server, an ACCEPT will complete. The retcode field will contain the number of the newly created active socket. This new socket has a connection already established and the program can immediately issue any send or receive requests. It must also be closed when all transmissions are complete. The CLOSE for this new socket does not affect the original socket used by the ACCEPT. An example of an ACCEPT call is shown in lines 129 through 137 of Figure 6.
Figure 13 shows the basic code for a simple server. Note that it saves the original socket number on line 11 and you always use it for any ACCEPT calls (line 25). The original socket is also closed on lines 50 through 53. As a server normally receives requests, the RECV and SEND calls are reversed when compared to Figure 11. Lines 44 through 49 show that the new active socket is closed and then the server loops back up to the ACCEPT call to process any new connection requests.
SELECTing in a Server
When creating the client program, I discussed how the READ would appear to hang if the server did not respond. A server also needs to handle the condition where a connection is requested, but the client never sends a request. You can insert code to perform a SELECT call similar to Figure 12 into Figure 13 prior to line 31.
In addition, an ACCEPT call will not complete until someone requests a connection. If you insert the SELECT code in Figure 5 into Figure 6 prior to line 26, the program can gain control whenever a timeout condition exists. Once a timeout occurs, the program can check for a CICS shutdown condition. If CICS is not performing a shutdown, the program can just loop back to the SELECT and wait for a connection request.
There are two different types of servers. The simple server I have just discussed is an example of an iterative server because it processes requests from clients serially, or “one at a time.” In contrast, a concurrent server accepts connection requests and then passes the connection to a separate child program. This allows the server to immediately process additional connection requests. If the request is seldom used or is simple, and therefore quick, an iterative server might be all that is needed, but most servers need to be written as concurrent servers. Figure 14 shows the process flow between a client, a concurrent server, and a child.
Some servers handle multiple request types. An iterative server can spawn different children, depending on the request type. There are several ways to determine which child process to spawn. One way is for the server to actually listen on multiple ports. Another method is for the request type to be contained in the first few bytes of the data initially sent to the server. The API provides a method where the server can examine the first few bytes of data without actually receiving the full datastream or causing it to be destroyed. The child process can then request the IP data and still receive all the data originally sent by the client. This is referred to as “peeking” at the data and is done using a modified RECV request. Lines 138 through 146 of Figure 6 show such a request.
You must consider security for requests. One method to secure a transaction is to check the IP address of the client against a list of authorized IP addresses. You can access the client IP address using GETPEERNAME. This will return a “name” area, which was described earlier when I discussed the RECV request. Lines 147 through 153 of Figure 7 show such a call. A GETPEERNAME request can be combined with a RECV request for efficiency by using a RECVFROM call. Lines 154 through 162 of Figure 7 show an example of this. You can also use the RECVFROM call to peek at the data using the same flag as a standard RECV. Such a call is shown on lines 163 through 171.
SERVER/CHILD A concurrent server handles multiple connections by creating a child process. In CICS, this would be done via an EXEC CICS START TASK command. The child is normally responsible for sending and receiving all data.
Starting the new transaction to process the data under CICS is actually easy. However, transferring the socket from the parent server to the child server involves several steps. The four functions needed are GETCLIENTID, GIVESOCKET, SELECT, and TAKESOCKET.
The GETCLIENTID (lines 172 through 178 of Figure 8) is required to retrieve the name assigned to your server by the IP stack. The child is required to know this information before it can take the socket. The returned name will be transferred to the child as part of the EXEC CICS START TASK command.
The GIVESOCKET (lines 179 through 187 of Figure 8) call informs the IP stack that the socket is being transferred to another program. The GIVESOCKET does not wait until the child takes the socket, but returns immediately. The server then starts the child task and must issue a SELECT call using the “exception” flags. Once notified that the child has successfully taken the socket, the parent will need to issue a standard CLOSE against the socket.
Once the child program starts, it should perform the normal INITAPI call. It will then use the data passed in the EXEC CICS START command to perform a TAKESOCKET call. Once the call returns, the socket number to be used by the child will be found in retcode, just like a SOCKET call. Lines 188 through 198 of Figure 8 show an example of a SOCKET. Figure 15 shows the code for a basic child program.
Due to the complexity of a concurrent server and magazine space limitations, an example of such a server is not shown here, but can be downloaded from www.vse2pdf.com/coolstuff. The program BSTTEZ11 handles concurrent ACCEPT, GIVESOCKET, and RECV requests using a single SELECT. The program BSTTEZ13 is the child program. This is how the SELECT call was designed to be used. You can also find sample batch programs, BSTTEZ01, BSTTEZ02, and BSTTEZ03 on the Website. These represent a concurrent server, a client, and a child program. The child program actually runs in a separate partition as a separate job.
At the Website mentioned, you will also find downloadable copies of useful IBM manuals. The Redbook is no longer available from IBM, and is a little dated, but is the best manual I have seen for the beginning mainframe IP programmer.