6.4. A combination client/server example

Now that we've had a fairly thorough discussion of how the select API works, and we've put the appropriate utilities into our SOCKUTILR4 service program, we're ready to do an example program using select.

This example program is the "proxy" that was described in the introduction to this chapter. It's main purpose will be to allow us to use a telnet client to debug other client programs.

In fact, you'll be able to use it to interact with just about any client program around -- including your web browser... :)

Here's how this program will work:

  1. We accept two port numbers as parms from the command line.

  2. As we've done in the previous chapter, set up a socket to bind() and listen() to each of the two ports.

  3. accept() a connection on the first port. Then close the listener for that port (we'll only handle one connection per port)

  4. accept() a connection on the second port. Then close the listener for THAT port.

  5. Set things up so that select() will detect when there is either data to read or an exceptional condition on either of these sockets.

  6. If there was data to read on the first socket, simply send() that data to the other socket. and vice-versa.

  7. If we receive any errors or exceptional conditions, exit the program. (It probably means that one side or the other has disconnected)

  8. If we didn't receive any errors, go back to step 5.

Here's the source to this new example program. At the bottom of this page, I'll show you how to play with it. :)

    File: SOCKTUT/QRPGLESRC Member: PROXY1
         H DFTACTGRP(*NO) ACTGRP(*NEW)
         H BNDDIR('SOCKTUT/SOCKUTIL') BNDDIR('QC2LE')
    
         D/copy socktut/qrpglesrc,socket_h
         D/copy socktut/qrpglesrc,sockutil_h
         D/copy socktut/qrpglesrc,errno_h
    
         D die             PR
         D   peMsg                      256A   const
    
         D NewListener     PR            10I 0
         D   pePort                       5U 0 value
         D   peError                    256A
    
         D copysock        PR            10I 0
         D  fromsock                     10I 0 value
         D  tosock                       10I 0 value
    
         D s1              S             10I 0
         D s2              S             10I 0
         D s1c             S             10I 0
         D s2c             S             10I 0
         D port1           S             15P 5
         D port2           S             15P 5
         D len             S             10I 0
         D connfrom        S               *
         D read_set        S                   like(fdset)
         D excp_set        S                   like(fdset)
         D errmsg          S            256A
         D max             S             10I 0
    
         c                   eval      *inlr = *on
    
         C     *entry        plist
         c                   parm                    port1
         c                   parm                    port2
    
         c                   if        %parms < 2
         c                   callp     die('This program requires two port ' +
         c                             'numbers as parameters!')
         c                   return
         c                   endif
    
         C*************************************************
         C* Listen on both ports given
         C*************************************************
         c                   eval      s1 = NewListener(port1: errmsg)
         c                   if        s1 < 0
         c                   callp     die(errmsg)
         c                   return
         c                   endif
    
         c                   eval      s2 = NewListener(port2: errmsg)
         c                   if        s2 < 0
         c                   callp     die(errmsg)
         c                   return
         c                   endif
    
         C*************************************************
         C* Get a client on first port, then stop
         C*   listening for more connections
         C*************************************************
         c                   eval      len = %size(sockaddr_in)
         c                   eval      s1c = accept(s1: connfrom: len)
         c                   if        s1c < 0
         c                   eval      errmsg = %str(strerror(errno))
         c                   callp     close(s1)
         c                   callp     close(s2)
         c                   callp     die(errmsg)
         c                   return
         c                   endif
         c                   callp     close(s1)
    
         C*************************************************
         C* Get a client on second port, then stop
         C*   listening for more connections
         C*************************************************
         c                   eval      len = %size(sockaddr_in)
         c                   eval      s2c = accept(s2: connfrom: len)
         c                   if        s2c < 0
         c                   eval      errmsg = %str(strerror(errno))
         c                   callp     close(s1c)
         c                   callp     close(s2)
         c                   callp     die(errmsg)
         c                   return
         c                   endif
         c                   callp     close(s2)
    
         c                   eval      max = s1c
         c                   if        s2c > max
         c                   eval      max = s2c
         c                   endif
    
         C*************************************************
         C*  Main loop:
         C*    1) create a descriptor set containing the
         C*        "socket 1 client" descriptor and the
         C*        "socket 2 client" descriptor
         C*
         C*    2) Wait until data appears on either the
         C*        "s1c" socket or the "s2c" socket.
         C*
         C*    3) If data is found on s1c, copy it to
         C*         s2c.
         C*
         C*    4) If data is found on s2c, copy it to
         C*         s1c.
         C*
         C*    5) If any errors occur, close the sockets
         C*         and end the proxy.
         C*************************************************
         c                   dow       1 = 1
    
         c                   callp     FD_ZERO(read_set)
         c                   callp     FD_SET(s1c: read_set)
         c                   callp     FD_SET(s2c: read_set)
         c                   eval      excp_set = read_set
    
         c                   if        select(max+1: %addr(read_set): *NULL:
         c                                %addr(excp_set): *NULL) < 0
         c                   leave
         c                   endif
    
         c                   if        FD_ISSET(s1c: excp_set)
         c                               or FD_ISSET(s2c: excp_set)
         c                   leave
         c                   endif
    
         c                   if        FD_ISSET(s1c: read_set)
         c                   if        copysock(s1c: s2c) < 0
         c                   leave
         c                   endif
         c                   endif
    
         c                   if        FD_ISSET(s2c: read_set)
         c                   if        copysock(s2c: s1c) < 0
         c                   leave
         c                   endif
         c                   endif
    
         c                   enddo
    
         c                   callp     close(s1c)
         c                   callp     close(s2c)
         c                   return
    
    
    
    
          *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
          *  Create a new TCP socket that's listening to a port
          *
          *       parms:
          *         pePort = port to listen to
          *        peError = Error message (returned)
          *
          *    returns: socket descriptor upon success, or -1 upon error
          *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
         P NewListener     B
         D NewListener     PI            10I 0
         D   pePort                       5U 0 value
         D   peError                    256A
    
         D sock            S             10I 0
         D len             S             10I 0
         D bindto          S               *
         D on              S             10I 0 inz(1)
         D linglen         S             10I 0
         D ling            S               *
    
         C*** Create a socket
         c                   eval      sock = socket(AF_INET:SOCK_STREAM:
         c                                           IPPROTO_IP)
         c                   if        sock < 0
         c                   eval      peError = %str(strerror(errno))
         c                   return    -1
         c                   endif
    
         C*** Tell socket that we want to be able to re-use the server
         C***  port without waiting for the MSL timeout:
         c                   callp     setsockopt(sock: SOL_SOCKET:
         c                                SO_REUSEADDR: %addr(on): %size(on))
    
         C*** create space for a linger structure
         c                   eval      linglen = %size(linger)
         c                   alloc     linglen       ling
         c                   eval      p_linger = ling
    
         C*** tell socket to only linger for 2 minutes, then discard:
         c                   eval      l_onoff = 1
         c                   eval      l_linger = 120
         c                   callp     setsockopt(sock: SOL_SOCKET: SO_LINGER:
         c                                ling: linglen)
    
         C*** free up resources used by linger structure
         c                   dealloc(E)              ling
    
         C*** Create a sockaddr_in structure
         c                   eval      len = %size(sockaddr_in)
         c                   alloc     len           bindto
         c                   eval      p_sockaddr = bindto
    
         c                   eval      sin_family = AF_INET
         c                   eval      sin_addr = INADDR_ANY
         c                   eval      sin_port = pePort
         c                   eval      sin_zero = *ALLx'00'
    
         C*** Bind socket to port
         c                   if        bind(sock: bindto: len) < 0
         c                   eval      peError = %str(strerror(errno))
         c                   callp     close(sock)
         c                   dealloc(E)              bindto
         c                   return    -1
         c                   endif
    
         C*** Listen for a connection
         c                   if        listen(sock: 1) < 0
         c                   eval      peError = %str(strerror(errno))
         c                   callp     close(sock)
         c                   dealloc(E)              bindto
         c                   return    -1
         c                   endif
    
         C*** Return newly set-up socket:
         c                   dealloc(E)              bindto
         c                   return    sock
         P                 E
    
    
          *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
          *  This copies data from one socket to another.
          *    parms:
          *         fromsock = socket to copy data from
          *         tosock = socket to copy data to
          *
          *    returns:  length of data copied, or -1 upon error
          *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
         P copysock        B
         D copysock        PI            10I 0
         D  fromsock                     10I 0 value
         D  tosock                       10I 0 value
         D buf             S           1024A
         D rc              S             10I 0
         c                   eval      rc = recv(fromsock: %addr(buf): 1024: 0)
         c                   if        rc < 0
         c                   return    -1
         c                   endif
         c                   if        send(tosock: %addr(buf): rc: 0) < rc
         c                   return    -1
         c                   endif
         c                   return    rc
         P                 E
    
          *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
          *  This ends this program abnormally, and sends back an escape.
          *   message explaining the failure.
          *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
         P die             B
         D die             PI
         D   peMsg                      256A   const
    
         D SndPgmMsg       PR                  ExtPgm('QMHSNDPM')
         D   MessageID                    7A   Const
         D   QualMsgF                    20A   Const
         D   MsgData                    256A   Const
         D   MsgDtaLen                   10I 0 Const
         D   MsgType                     10A   Const
         D   CallStkEnt                  10A   Const
         D   CallStkCnt                  10I 0 Const
         D   MessageKey                   4A
         D   ErrorCode                32766A   options(*varsize)
    
         D dsEC            DS
         D  dsECBytesP             1      4I 0 INZ(256)
         D  dsECBytesA             5      8I 0 INZ(0)
         D  dsECMsgID              9     15
         D  dsECReserv            16     16
         D  dsECMsgDta            17    256
    
         D wwMsgLen        S             10I 0
         D wwTheKey        S              4A
    
         c                   eval      wwMsgLen = %len(%trimr(peMsg))
         c                   if        wwMsgLen<1
         c                   return
         c                   endif
    
         c                   callp     SndPgmMsg('CPF9897': 'QCPFMSG   *LIBL':
         c                               peMsg: wwMsgLen: '*ESCAPE':
         c                               '*PGMBDY': 1: wwTheKey: dsEC)
    
         c                   return
         P                 E
    
          /define ERRNO_LOAD_PROCEDURE
          /copy socktut/qrpglesrc,errno_h
     

Compile it by typing: CRTBNDRPG PROXY1 SRCFILE(SOCKTUT/QRPGLESRC) DBGVIEW(*LIST)

To run it:

  1. On the AS/400, type: CALL PROXY1 PARM(4000 4001)

  2. On a PC, type: telnet as400 4000 Then leave that window open.

  3. Start a 2nd telnet client by typing: telnet as400 4001

  4. Whatever you type in the first telnet client should appear in the other one's window, and vice-versa. If the client that you're using is a 'line-at-a-time' client, you may have to press enter before the line shows up.

  5. When you tell one of the telnet clients to disconnect, the AS/400 program will end, and the other telnet client should be disconnected.

    (To disconnect using the Windows telnet client, click on the "Connect" menu, and choose "Disconnect". For FreeBSD or Linux clients, press Ctrl-] and then type quit)

Lets try it with a web browser:

  1. On the AS/400, type: CALL PROXY1 PARM(4000 4001)

  2. On a PC, type: telnet as400 4000

    Tip: If you're using the Windows Telnet client, and you can't read what you're typing, you might try clicking "Terminal", then "Preferences" and enabling "Local Echo".

  3. In a web browser, set the Location: to the URL: http://as400:4001/

  4. Back in the telnet client, you'll see the web browser send a request for a web page. It'll also send a bunch of extra information about itself, such as the type of browser you're using, what types of documents it accepts, etc.

  5. In the telnet client, type something like:

                   <H1>Hello Web Browser!</H1>
                   <H3>Nice subtitle, eh?</H3>
             
    
  6. Then tell the telnet client to disconnect. Take a look at what the web browser has displayed!

I'm sure you can see how this could be a very valuable tool for debugging programs and testing what your clients and servers are doing!