Client Connections

Essentials

This section summarises the content on this page (read on for the full tutorial):

Socket
Create

Having created a socket for our client connection the following steps for building a connection can now be taken:

Connect Request

Some APIs allow connect as the first request and implicitly perform a create socket. Alternatively you could create a socket and perform some or all of the optional steps above before issuing the connect. To connect to a specific application on a remote server we need to know the ip address of the server and the port number on which the remote application is available. This information can be hardcoded but is better obtained via DNS where a server IP address can be found by its URL, the server can also advertise the services available and the associated port numbers to reach those services.

In addition to the socket number allocated on the create socket call, there are typically two, sometimes three key additional parameters to connect, as follows:

Address familyRequired on some APIs but has already been established on the socket open request.
IP addressSpecifies the IP address of the target server, this may be a character address e.g. "127.0.0.1", have been obtained via getaddrinfo(), PTON or programmatically (typically older IPv4 programs only e.g. 127.0.0.1 would be packed as four bytes 0x7F000001)
Port numberThis again can have been obtained via getaddrinfo() in this case it is already in network byte order. If you provide a string which you then convert to binary then that number is in host byte order (Intel chips use little-endian storage technology which is the reverse of network byte order). Check the API you are using to determine the byte order required on the port parameter, if necessary use htons() (host-to-network) like functions to perform the conversion.

Error Handling

What could possibly go wrong? A connection request is the first to generate network traffic to the server node so many things can go wrong, unable to access the network, unable to find the server, no application at the server to connect to, failure when accessing the application. The error description is returned as a code known as errno. The errno value is obtained in an API dependent way, it may be placed into a reserved variable of name errno or it may be returned to an area defined by an errno parameter on the api request. There are many possible values for errno, however the principal errors are:

13 EACCESS: The user is not authorized to access the socket resource, either locally or at the server.
54 ECONNRESET: The connection was reset/terminated. This could be due to a network outage or the remote application aborting the connection by simply closing its socket for the connection. Normally a TCP application would issue a Shutdown request to terminate the connection.
60 ETIMEDOUT: Typically a connection request timed out before it had completed. Absence of a response (negative or positive) is typically associated with a firewall blocking the connection request but may be an indication of dropped packets due to high network loads.
61 ECONNREFUSED: The TCP/IP stack at the remote host refused the connection. Either no TCP application was listening on the target port number or the application rejected the connection request. Check that the target application is active and listening on the port to which this connection request was directed.
 

Example Client Connection Requests

Follow the below links for examples applicable to your preferred environment:

UNIX
C
Lua
ooREXX
IBM TSO
REXX
IBM HPNS
HLASM

C (generic UNIX)

In this example we use the getaddrinfo api call to either interrogate DNS etc or to convert a numeric address and port number to the correct format for connect.


struct snys_conn {
  char        serverName[256];
  char        serverService[6];
  struct snys_glob * pGlob;
  struct snys_buf * pSndBuf;
  struct snys_buf * pRcvBuf;
  int           fd;
  unsigned char status;
#define conn_status_open 0b10000000    // socket open
#define conn_status_conn 0b01000000    // socket connected
#define conn_status_dump 0b00000001    // dump all sent/received data
};

	
/***
  Loop on the results from getaddrinfo to find working connection to target server.
  Open the socket within this loop to allow different inet family addresses eg. ipv4
  and ipv6. Bypass this open if subsequent addresses are in the same family.
***/
void doConnect( struct snys_conn *pConn) {
  struct snys_glob *pGlob;
  struct addrinfo  *pAddrFiltr;
  struct addrinfo *pAddrList, *pAddr;
  int i = 0, rc, old_aiFamily = 0;

  pGlob = pConn->pGlob;
  memset( pGlob->work, 0x0000, sizeof( pGlob->work));// clear addrinfo area
  pAddrFiltr = (struct addrinfo *)&pGlob->work;      // cast a new pointer
  pAddrFiltr->ai_family = AF_UNSPEC;                 // Allow IPv4 or IPv6
  pAddrFiltr->ai_socktype = SOCK_STREAM;             // TCP stream socket

  rc = getaddrinfo((const char *)&pConn->serverName, (const char *)&pConn->serverService,
                      pAddrFiltr, &pAddrList);
  if ( rc < 0) {
     doMessage( pGlob, 2, "getaddrinfo", rc, gai_strerror(rc));
     return;
  }

  for (pAddr =pAddrList; (pAddr != NULL) && (!( pConn->status & conn_status_conn));
          pAddr = pAddr->ai_next) {
                                               // check for re-use of an open socket
     if ( pAddr->ai_family != old_aiFamily) {  // if prev addr family not same as current
        if (old_aiFamily != 0) {               // and not the first addrinfo struct
           close( pConn->fd);                        // close the socket
           pConn->status &= 0xFF - conn_status_open; // reset open status
           doMessage( pGlob, 3, pConn->fd, "closed");// successful close
        }
                                               // open socket for current addr family
        if (( pConn->fd = socket( pAddr->ai_family, pAddr->ai_socktype,
                                    pAddr->ai_protocol)) >= 0) {
           doMessage( pGlob, 3, pConn->fd, "open");  // successful open
           pConn->status |= conn_status_open;        // set status open
           if ( connect( pConn->fd, pAddr->ai_addr, pAddr->ai_addrlen) >= 0 ) {
              doMessage( pGlob, 4, pConn->serverName);
              pConn->status |= conn_status_conn;
           } else {
              strcpy( pGlob->work, "Connect to Host at ");
              strcat( pGlob->work, pConn->serverName);
              doMessage( pGlob, 2, pGlob->work, errno, strerror(errno));
           }
        }
     }
  }
  if (!(pConn->status & conn_status_conn)) {         // no address succeeded
     doMessage( pGlob, 9, "Could not connect, addrinfo list exhausted");
     return;
  }
  freeaddrinfo(pAddrList);                           // free addrinfo list
}

The necessary include files are listed in the full client program example which can be found here

Lua

Lua sockets support is via a TCP object (TCP thus identifying the socket type and protocol), a master object is initialized and this is converted to a client object (or server object) based on the socket calls made. In this case the client object implicitly specifies the socket number used on other APIs..


   host = "localhost";
   port = 80;

-- basis of assert( socket.connect( host, port));
   clnt, emsg = socket.tcp();
   if ( not clnt) then
      print( "Unable to create TCP object: " .. emsg);
   else
      cstat, emsg = clnt:connect( host, port);
      if ( not cstat) then
         print( "Unable to connect to host " .. host 
                  .. ", port " .. port .. ": " .. emsg);
      end;
   end;
 -- end of assert( socket.connect( host, port));

As indicated in the comments, this code can be replaced by the Lua function 'assert( socket.connect( host, port))', error handling and messages are then Lua determined but provide sufficient information for simple scripts.

You can see the full Lua client program here

ooREXX

ooRexx uses an address stem variable to pass the target server address information on the connect request


  ipAddr = '127.0.0.1'
  tcpPort = '80'

/*********************************************************************/
/*  Open the socket                                                  */
/*********************************************************************/
  socNum = SockSocket("AF_INET", "SOCK_STREAM", "IPPROTO_TCP")
  if ( socNum < 0) then do
      say 'Error opening socket, ERRNO:' errno
      exit
  end

/*********************************************************************/
/*  Connect to remote server                                         */
/*********************************************************************/
  address.family = "AF_INET"
  address.port = tcpPort
  address.addr = ipAddr
  if SockConnect(socNum, address.) = -1 then do
      say 'SockConnect to' ipAddr':'tcpPort 'failed, ERRNO:' errno
      SockClose( socket)
      exit
  end
  say 'Connection established with' ipAddr':'tcpPort 'using socket' socNum

Note that errno is a special variable maintained by the RxSock implementation. You can see the full program here

IBM TSO REXX

After the socket open we use the REXX unique SETSOCKOPT SO_ASCII function to provide automatic translation of sent and received data between EBCDIC and ASCII. The connect call requires that the address family, target ipaddress and port be supplied as individual parameters.

/*********************************************************************/
/*  Open the socket, set auto ASCII EBCDIC translation               */
/*********************************************************************/
  Resp = SOCKET('SOCKET', 'AF_INET', 'SOCK_STREAM', 'IPPROTO_TCP')
  if WORD( resp, 1) <> 0 then do
      say 'Error opening socket'
      say resp
      exit
  end
  SocNum = WORD( resp, 2)

  Resp = SOCKET('SETSOCKOPT', socNum, 'IPPROTO_TCP', 'SO_ASCII', 1)
  if WORD( resp, 1) <> 0 then do
      say 'Error setting socket options'
      say resp
      exit
  end

/*********************************************************************/
/*  Connect to remote server                                         */
/*********************************************************************/
  Resp = SOCKET( 'CONNECT', socNum, 'AF_INET' tcpPort ipAddr)
  if WORD( resp, 1) <> 0 then do
      say 'Error connecting to' ipAddr':'tcpPort
      say resp
      exit
  end
  say 'Connection with' ipAddr':'tcpPort 'established'

You can see the full program here

IBM HPNS API

IBM's High Performance Network Sockets (HPNS) API uses the EZASMI macro to invoke the API. Before issuing this SOCKET request the API will need to have been initialized, an EZASMI INITAPI call (in our case) with APITYPE=2 synchronous requests.

You can see the full program here

********************************************************************** 
* Connect to the target system                                       * 
**********************************************************************
         USING WORK_AREA_1,R13 
         USING CWA_BLOCK,R9 
DO_CONNECT DS  0H                      ******************************* 
         STMG  R0,R15,W1_SAV2          * Save callers regs           * 
         MVC   CWA_REQNM,=CL12'CONNECT'  Request name                * 
         MVC   CWA_PLIST(MODEL_EZASMI_LENGTH),MODEL_EZASMI init plst * 
         EZASMI TYPE=CONNECT,          *                             * X 
               S=CWA_SOCNO,            *                             * X 
               NAME=CWA_RMT_ADDR,      *                             * X 
               TASK=W1T_TASK_WA,       *                             * X 
               ERRNO=CWA_ERRNO,        *                             * X 
               RETCODE=CWA_RETCD,      *                             * X 
               MF=(E,CWA_PLIST)        *                             * 
         CLC   =F'0',CWA_RETCD         * Complete OK ?               * 
         JH    CONN_REQ_ERR            * Y - skip err msg            * 
         OI    CWA_STATE,CWA_STATE_CONN  Flag socket connected       * 
         JAS   R14,DO_REQU_OK          *                             * 
         XGR   R15,R15                 *                             * 
         J     CONN_RET                * return                      * 
****************************************                             * 
* EZASMI call error processing, issue message set return code        * 
****************************************                             * 
CONN_REQ_ERR DS 0H                     *                             * 
         JAS   R14,DO_REQ_ERR          * Go report error             * 
         LA    R15,16                  * Return code                 * 
CONN_RET DS    0H                      *                             * 
         LMG   R0,R14,W1_SAV2          * Restore callers regs        * 
         BR    R14                     * Return                      * 
*                                      ******************************* 
         DROP  R9,R13 


**********************************************************************
* Program constants                                                  *
**********************************************************************		 
MODEL_EZASMI EZASMI MF=L               *                             * 
MODEL_EZASMI_LENGTH EQU *-MODEL_EZASMI *                             * 


**********************************************************************  
* CWA - Connection Work Area                                         *  
**********************************************************************  
CWA_BLOCK DSECT ,                      ******************************* 
CWA_ID    DS   CL2                     * CB                          * 
CWA_LEN   DS   H                       * length                      * 
CWA_CONN_NAME DS CL64                  *                             * 
CWA_SBUF  DS   A                       * Send buffer address         * 
CWA_RBUF  DS   A                       * Recv buffer address         * 
CWA_PLIST DS   XL(MODEL_EZASMI_LENGTH) *                             * 
CWA_SOCKET DS  0F                      * Fwd socket number           * 
         DS    H                       *                             * 
CWA_SOCNO DS   H                       * Hwd socket number           * 
CWA_RMT_ADDR DS XL(SOCK#LEN+SOCK_SIN#LEN) IPV4 address structure     * 
CWA_RETCD DS   F                       * Return code                 * 
CWA_ERRNO DS   F                       * Error Number (RetCd = -1)   * 
CWA_ECB  DS    F                       * Request ECB                 * 
         DS    XL100                   * HPNS work area (contig ECB) * 
CWA_REQNM DS   CL12                    * Request name for log/trace  * 
CWA_ROLE DS    C                       * L, S or C                   * 
CWA_STATE DS   X                       * State flags                 * 
CWA_STATE_OPEN   EQU B'10000000'       * Socket is open              * 
CWA_STATE_CONN   EQU B'01000000'       * Socket is connected         * 
CWA_STATE_SRTY   EQU B'00000001'       * Send retry in progress      * 
CWA_STATE_DUMP   EQU B'00100000'       * 0C1 when on                 * 
         DS    0D                      *                             * 
CWA_BLOCK_LENGTH EQU *-CWA_BLOCK       *                             * 

 

Send
Data
Next
Sockets
Contents
Contents
Socket
Create
Prev