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.