An Introduction to TPF Socket Programming
by Dan Yee, IBM TPF Development
In the Winter 1996 edition of the TPF Systems Technical Newsletter, the TPF Transmission Control Protocol/Internet Protocol (TCP/IP) support that is being provided on program update tape (PUT) 4 was discussed. If you have ordered some or all of the publications that were recommended in that article and had a chance to read through the material dealing with socket programming, it is time to think about writing socket application programs that will run on your TPF system once you install TPF 4.1 at PUT 4. This article provides an introduction to socket programming to those who are new to it and will also discuss the TPF-unique activate_on_receipt function to those who have some experience in socket programming.
TPF Socket API Support
TPF socket application programming interface (API) support is based on the industry-standard Berkeley Software Distribution (BSD) sockets. Because the socket API consists of standard ISO-C function calls, a familiarity with C language programming is required to write socket application programs. The socket API functions currently supported by the TPF system are accept, activate_on_receipt, bind, close, connect, gethostid, gethostname, getpeername, getsockname, getsockopt, htonl, htons, inet_addr, ioctl, listen, ntohl, ntohs, read, recv, recvfrom, select, send, sendto, setsockopt, shutdown, sock_errno, socket, write, and writev . The formats for each of these functions are being provided in the new TPF Transmission Control Protocol/Internet Protocol publication, which will soon be available with the TPF code on PUT 4. As was mentioned in the previous TPF Systems Technical Newsletter, an offload device, such as the IBM 3172 Interconnect Controller Model 3, is required for the processing of the socket API functions.
Creating the Socket
Every socket application needs to issue the socket API function call to define an endpoint for communication. This function call defines the address domain, socket type, and the protocol for the socket.
Currently, the TPF system only supports the INET addressing family, which is the internet addressing domain. In addition, the TPF system supports stream sockets, which are associated with Transmission Control Protocol (TCP), and datagram sockets, which are associated with User Datagram Protocol (UDP).
The TPF system also supports raw sockets, which are not associated with any one particular protocol and are used to directly access lower layer protocols such as Internet Protocol (IP). When choosing which socket type to use, consider performance, size of each message, and reliability. For short messages in which performance is important, use datagram sockets. For short and long messages in which reliability is important, use stream sockets because the data is sent across the TCP/IP network without errors or duplication and is received in the same order as it is sent. The focus of this article is on stream sockets.
Whenever the socket application issues the socket API function call, a socket descriptor is returned. The socket application can then use that socket descriptor, which happens to be an integer value, for subsequent API function calls.
Binding the Socket
In a stream socket programming environment, server and client roles are more clearly defined than in a datagram programming environment. As a server, the socket application is required to issue a bind socket API function call to bind a local name or IP address and port number to the socket. TPF socket API support allows the program to bind 1 address at a time or to bind all IP addresses on all offload devices at a time. TPF socket API support also provides a socket user exit, which is passed the IP addresses associated with each offload device as these devices are connected to the TPF system in computer room agent set (CRAS) state or above. This user exit facilitates the coding of the bind when the application needs to bind just 1 IP address at a time. Along with the IP address, the server normally chooses a port number so that the other half of the communication endpoint, or client, can define the correct address when attempting to connect with the server.
When servers are closed and then quickly reactivated with the same port number and IP address, it may be necessary to code the setsockopt function call with the SO_REUSEADDR option before the bind to prevent a bind error when the server is reactivated. This error may occur because it takes up to 5 minutes for the TCP/IP for the OS/2 program in the offload device to complete the process of closing the socket. If you cannot wait that long to restart your server application, add that setsockopt function call before the bind function call in your application program.
Establishing and Accepting a Connection
Once the server has issued a bind to its socket, it must issue listen and accept calls to wait for a connection from the client. The server establishes a backlog value with its listen call to inform the system the maximum number of connection requests from clients that can be placed on a queue, waiting to be accepted by the server. The server then issues an accept call, which is normally blocked until a connection is received. Once the server accepts a connection from a client, it receives a new socket descriptor, which the server can use to associate a single session with another client. It can then use the original socket descriptor to accept connection requests from other clients.
While the server has issued listen and accept calls to wait for a connection, the client has issued a socket call, an optional bind call, and a connect call to establish a connection to the server. The connect call specifies the address and port number of the server that the client wants to connect with.
Receiving Data with activate_on_receipt
Once it has accepted a connection, the next step for the server is to issue a read call to receive data from the client. For most socket API functions that are issued by the application in a TPF system, TPF socket API support sends the function to the offload device for processing and suspends the entry control block (ECB) of the application to wait for the response from the offload device. Because blocking mode is the default mode for any socket program, there could be many suspended ECBs if a large number of ECB-controlled programs issue a blocked read function call to the offload device. One solution to minimize the number of suspended ECBs is to set the socket to nonblocking mode before issuing read calls to the socket. When a socket is in nonblocking mode, control returns to the caller even if there is no data available for the caller to read in. However, nonblocking mode increases the amount of I/O that must be issued to the offload device because the read function calls must be reissued each time there is no data available. For TPF socket API support, a unique TPF socket function called activate_on_receipt is available to minimize the number of suspended ECBs and the number of read function calls that need to be sent to the offload device.
The activate_on_receipt call contains 3 parameters: the socket descriptor, a pointer to a 4-byte program name, and a pointer to an 8-byte parameter list. The program name is the name of a program that is activated when data arrives from the client. The 8-byte parameter list may contain data the server wants to pass to the activated program once it is activated. After the server issues an activate_on_receipt function call, it can exit the program or accept another connection request.
At this point, a client may send a message to the server to initiate data transmission between the server and the client. When the program specified in the server's activate_on_receipt call is activated, the socket descriptor, a pointer to the 8 bytes of data, the address of the data, and the length of the data are contained in the new ECB that is created when the program is activated. The new program must issue a read function call to read in the data using the information that was passed to it in the ECB. The server can then issue a send call to send a message to the client and issue a read or activate_on_receipt call to receive more data from the client. The client and server may continue to exchange data until either side ends the connection.
Ending (Terminating) the Connection
On a connected socket such as a stream socket, the usual way for a server or client to end a connected socket is through a shutdown call. In the current implementation of TPF socket API support, a shutdown call to the offload device will be blocked if there is a blocked read call outstanding for that socket. Therefore, shutdown calls issued to the offload device should not follow directly after the read call unless nonblocking mode has been set with an ioctl function call. If the client issues a shutdown call to the server, a read call issued from the server side will return a 0 return code, which means that the client has ended the connection. At that point, the server should issue a close call of the socket to enable the system to clean up all of the control blocks associated with the client/server session.
Stream socket data does not have a fixed size like datagram messages, so data received from the TCP/IP network may be fragmented. To ensure that an entire message is read in, place the read call that is in your socket program in a loop until the amount of data that is expected from the client or server is read in. A control character, such as a null character, can be placed at the end of the message before it is sent out so the recipient of the message can detect the end of the message. Because datagram messages are not fragmented after they are sent out on the TCP/IP network, socket applications that use datagram sockets can read an entire message in one function call.
Your socket application should always check for a -1 return code from the socket API function calls. When a socket is in blocking mode, a -1 return code usually means that an error has occurred on the socket and may mean that an error occurred from the offload device. In that case, your program will consume system resources if it attempts to issue additional calls to the offload device. To obtain the particular reason for the -1 return code, code the sock_errno function call in your socket application program. The secondary codes that can be returned by the sock_errno function call are listed in the socket.h header file. When a socket is in nonblocking mode, a -1 return code from blocked function calls such as accept, read, send, connect, or write does not necessarily mean that there was an error, so your program will have to test the secondary return code to determine if the -1 return code simply meant that data was not available.
On some TPF systems, it is important that you keep the number of outstanding ECBs to a minimum. On other TPF systems, it is just as important that you keep the number of ECBs created and the amount of enter/back processing to a minimum. If your application program reissues activate_on_receipt to reactivate that program when data arrives instead of issuing read calls to receive the data, the number of ECBs that are outstanding at one time is minimized, but the additional instructions that are executed as a result of ECB creation and enter/back processing may be an issue in terms of performance. The question on the appropriate use of activate_on_receipt can only be answered on a case by case basis.