7.6. Working towards our next example

If you haven't yet tried the first "job spawning" example, try it now.

Compile it by typing:

CRTBNDRPG SVREX6L SRCFILE(SOCKTUT/QRPGLESRC) DBGVIEW(*LIST)

CRTBNDRPG SVREX6I SRCFILE(SOCKTUT/QRPGLESRC) DBGVIEW(*LIST)

And then start it like so:

SBMJOB CMD(CALL SVREX6L) JOBQ(QSYSNOMAX) JOB(LISTENER)

Test it from your PC by doing a 'telnet as400 4000', as we've discussed in the past few chapters. Look! Nice!

When you're done playing with it, type 'NETSTAT *CNN' and find the reference to 'Local Port' 4000, that's in 'Listen' state. Use the '4' option to end the server.

Looking back at the sourcecode for SVREX6L, you'll see that when SVREX6L receives an error on the accept() command, it goes and does an 'ENDJOB' command to the server instance. So, in fact, when you entered the 4 in NETSTAT, SVREX6L ended all of the programs involved. Not too bad, huh?

However, if you tried to end the job using other methods, the listener may end without stopping the server instances. We should really be checking the SHTDN op-code, and ending all of the instances whenever the ENDJOB, ENDSBS, ENDSYS or PWRDWNSYS commands are run.

To do this, we need to call select() prior to accept(). Calling select() will allow us to detect when a client connects, but still provide us with a timeout value, so we can check for the system shutting down periodically.

Add these to the bottom of your D-specs:

         D rc              S             10I 0
         D tolen           S             10I 0
         D timeout         S               * 
         D readset         S                   like(fdset)
         D excpset         S                   like(fdset)
     

Then, add this code right after the 'alloc calen' line:

         c                   eval      tolen = %size(timeval)
         c                   alloc     tolen         timeout
     

Then, right before calling accept(), add the code to check for shutdown. It should look something like this:

         c                   dou       rc > 0
    
         c                   callp     FD_ZERO(readset)
         c                   callp     FD_ZERO(excpset)
         c                   callp     FD_SET(svr: readset)
         c                   callp     FD_SET(svr: excpset)
         c                   eval      p_timeval = timeout
         c                   eval      tv_sec = 20
         c                   eval      tv_usec = 0
    
         c                   eval      rc = select(svr+1: %addr(readset):
         c                                     *NULL: %addr(excpset): timeout)
    
         c                   shtdn                                        99
         c                   if        *in99 = *on
         c                   callp     close(svr)
         c                   callp     KillEmAll
         c                   callp     die('shutdown requested!')
         c                   return
         c                   endif
    
         c                   enddo
     

Now, when you end the job with *CNTRLD, it'll end all of the server instances as well. We're making progress!

We also didn't implement the 'quit' command that we've used in chapters 5 & 6 to end this server from the client side. If you're looking for a new challenge, you might want to try to implement this with the new, "job spawning" approach. However, we won't do that in this tutorial. (Being realistic, it's unusual that you want the client to be able to end your server!)

Another thing that you may note about this new server is that if you start up many clients rapidly, it doesn't respond as quickly as the examples from chapter 6 did. (Though, if you have a faster AS/400, this may be hard to detect!)

The reason that it's slower is that it takes a bit of time for the system to create a new job, make it active, and have it communicate back to the listener job. The easiest way to improve the performance of this is to "pre-submit" several different server instances, which can be waiting and ready when a client connects.

To implement this, I'm going to add a new named constant, right after the 'MAXCLIENTS' constant. It'll look like this:

         D PRESTART        C                   CONST(5)
     

Then, after we call the 'NewListener' procedure, we'll insert this code:

         c                   do        PRESTART
         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                   enddo
     

When each of these jobs starts, it'll put it's job info onto the data queue. When the listener program goes to read the data queue, it'll get them in the order they were added. This should cut the delay between the time it takes to accept each new client down to 1/5th the time.

Since the 'KillEmAll' procedure (nice name, huh?) will end any server instance that is currently waiting for a client to service, it'll happily end all of the pre-submitted server instances when NETSTAT, ENDJOB, ENDSBS, ENDSYS or PWRDWNSYS is used to try to shut us down.

Now, we'll begin working towards a new example server program. This program will ask the client for 3 different things: a user-id, a password, and a program to call. It will validate the user-id and password, and then use that user's authority to call the program.

It will pass the program 2 parameters, the socket descriptor that it can use to talk to the client program and the user-id of the validated user.

When the called program has completed, the server instance will close the socket and then end.

The next few topics will explain this in more detail.