7.5. Our server as a multi-job server

So, we've learned what the "job spawning approach" is, and we've learned about the givedescriptor() and takedescriptor() APIs, how to get the internal job id, and how to communicate that id back to the listener job.

Great. Time to put this knowledge to use!

Our "hello/goodbye" server that we originally wrote in Chapter 5 can now be made into a job spawning server. This will involve the two programs that we've discussed in this chapter, the "listener" and the "server instance" programs.

I've decided to name them "svrex6l" (server example 6 -- listener) and "svrex6i" (server example 6 -- instance). And here they are:

    File SOCKTUT/QRPGLESRC, Member SVREX6L:
         H DFTACTGRP(*NO) ACTGRP(*NEW)
         H BNDDIR('SOCKTUT/SOCKUTIL') BNDDIR('QC2LE')
    
          *** header files for calling service programs & APIs
    
         D/copy socktut/qrpglesrc,socket_h
         D/copy socktut/qrpglesrc,sockutil_h
         D/copy socktut/qrpglesrc,errno_h
         D/copy socktut/qrpglesrc,jobinfo_h
    
          *** prototypes for external calls
    
         D Cmd             PR                  ExtPgm('QCMDEXC')
         D   command                    200A   const
         D   length                      15P 5 const
    
          *** Prototypes for local subprocedures:
    
         D die             PR
         D   peMsg                      256A   const
    
         D NewListener     PR            10I 0
         D   pePort                       5U 0 value
         D   peError                    256A
    
         D KillEmAll       PR
    
          *** local variables & constants
    
         D MAXCLIENTS      C                   CONST(256)
    
         D svr             S             10I 0
         D cli             S             10I 0
         D msg             S            256A
         D err             S             10I 0
         D calen           S             10I 0
         D clientaddr      S               *
         D jilen           S              5P 0
    
         c                   eval      *inlr = *on
    
         C*************************************************
         C* Clean up any previous instances of the dtaq
         C*************************************************
         c                   callp(e)  Cmd('DLTDTAQ SOCKTUT/SVREX6DQ': 200)
         c                   callp(e)  Cmd('CRTDTAQ DTAQ(SOCKTUT/SVREX6DQ) ' +
         c                                        ' MAXLEN(80) TEXT(''Data ' +
         c                                        ' queue for SVREX6L'')': 200)
         c                   if        %error
         c                   callp     Die('Unable to create data queue!')
         c                   return
         c                   endif
    
         C*************************************************
         C* Start listening for connections on port 4000
         C*************************************************
         c                   eval      svr = NewListener(4000: msg)
         c                   if        svr < 0
         c                   callp     die(msg)
         c                   return
         c                   endif
    
         C*************************************************
         C* create a space to put client addr struct into
         C*************************************************
         c                   eval      calen = %size(sockaddr_in)
         c                   alloc     calen         clientaddr
    
         c                   dow       1 = 1
    
         C************************************
         C* Get a new server instance ready
         C************************************
         c                   callp(e)  Cmd('SBMJOB CMD(CALL PGM(SVREX6I))' +
         c                                       ' JOB(SERVERINST) ' +
         c                                       ' JOBQ(QSYSNOMAX) ' +
         c                                       ' JOBD(QDFTJOBD) ' +
         c                                       ' RTGDTA(QCMDB)': 200)
         c                   if        %error
         c                   callp     close(svr)
         c                   callp     KillEmAll
         c                   callp     Die('Unable to submit a new job to ' +
         c                             'process clients!')
         c                   return
         c                   endif
    
         C************************************
         C* Accept a new client conn
         C************************************
         c                   eval      cli = accept(svr: clientaddr: calen)
         c                   if        cli < 0
         c                   eval      err = errno
         c                   callp     close(svr)
         c                   callp     KillEmAll
         c                   callp     die('accept(): ' + %str(strerror(err)))
         c                   return
         c                   endif
    
         c                   if        calen <> %size(sockaddr_in)
         c                   callp     close(cli)
         c                   eval      calen = %size(sockaddr_in)
         c                   iter
         c                   endif
    
         C************************************
         C* get the internal job id of a
         C*  server instance to handle client
         C************************************
         c                   eval      jilen = %size(dsJobInfo)
         c                   callp     RcvDtaQ('SVREX6DQ': 'SOCKTUT': jilen:
         c                                     dsJobInfo: 60)
         c                   if        jilen < 80
         c                   callp     close(cli)
         c                   callp     KillEmAll
         c                   callp     close(svr)
         c                   callp     die('No response from server instance!')
         c                   return
         c                   endif
    
         C************************************
         C* Pass descriptor to svr instance
         C************************************
         c                   if        givedescriptor(cli: %addr(InternalID))<0
         c                   eval      err = errno
         c                   callp     close(cli)
         c                   callp     KillEmAll
         c                   callp     close(svr)
         c                   callp     Die('givedescriptor(): ' +
         c                                 %str(strerror(err)))
         c                   Return
         c                   endif
    
         c                   callp     close(cli)
         c                   enddo
    
    
          *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
          *  This ends any server instances that have been started, but
          *   have not been connected with clients.
          *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
         P KillEmAll       B
         D KillEmAll       PI
         c                   dou       jilen < 80
    
         c                   eval      jilen = %size(dsJobInfo)
         c                   callp     RcvDtaQ('SVREX6DQ': 'SOCKTUT': jilen:
         c                                     dsJobInfo: 1)
    
         c                   if        jilen >= 80
    
         c                   callp(E)  Cmd('ENDJOB JOB(' + %trim(JobNbr) +
         c                                  '/' + %trim(JobUser) + '/' +
         c                                  %trim(jobName) + ') OPTION(*IMMED)'+
         c                                  ' LOGLMT(0)': 200)
    
         C                   endif
    
         c                   enddo
         P                 E
    
    
          *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
          *  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: MAXCLIENTS) < 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 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
     
    File SOCKTUT/QRPGLESRC, member SVREX6I:
         H DFTACTGRP(*NO) ACTGRP(*NEW)
         H BNDDIR('SOCKTUT/SOCKUTIL') BNDDIR('QC2LE')
    
          *** header files for calling service programs & APIs
    
         D/copy socktut/qrpglesrc,socket_h
         D/copy socktut/qrpglesrc,sockutil_h
         D/copy socktut/qrpglesrc,errno_h
         D/copy socktut/qrpglesrc,jobinfo_h
    
          *** Prototypes for local subprocedures:
    
         D die             PR
         D   peMsg                      256A   const
    
         D GetClient       PR            10I 0
    
         D cli             S             10I 0
         D name            S             80A
    
         c                   eval      *inlr = *on
    
         c                   eval      cli = GetClient
         c                   if        cli < 0
         c                   callp     Die('Failure retrieving client socket '+
         c                              'descriptor.')
         c                   return
         c                   endif
    
         c                   callp     WrLine(cli: 'Please enter your ' +
         c                               'name now!')
    
         c                   if        RdLine(cli: %addr(name): 80: *On) < 0
         c                   callp     close(cli)
         c                   callp     Die('RdLine() failed!')
         c                   return
         c                   endif
    
         c                   callp     WrLine(cli: 'Hello ' + %trim(name) + '!')
         c                   callp     WrLine(cli: 'Goodbye ' +%trim(name)+ '!')
    
         c                   callp     close(cli)
         c                   return
    
    
          *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
          *  Get the new client from the listener application
          *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
         P GetClient       B
         D GetClient       PI            10I 0
    
         D jilen           S              5P 0
         D sock            S             10I 0
    
         c                   callp     RtvJobInf(dsJobI0100: %size(dsJobI0100):
         c                               'JOBI0100': '*': *BLANKS: dsEC)
         c                   if        dsECBytesA > 0
         c                   return    -1
         c                   endif
    
         c                   eval      JobName = JobI_JobName
         c                   eval      JobUser = JobI_UserID
         c                   eval      JobNbr = JobI_JobNbr
         c                   eval      InternalID = JobI_IntJob
    
         c                   eval      jilen = %size(dsJobInfo)
    
         c                   callp     SndDtaq('SVREX6DQ': 'SOCKTUT': jilen:
         c                                dsJobInfo)
    
         c                   eval      sock = TakeDescriptor(*NULL)
         c                   return    sock
         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