A Multi-tasking Internet Daemon in REXX/MVS

By

Earl Hodil, Open Software Technologies (ehodil@mail.open-softech.com)


Part II.

In part one, we looked at the general architecture of an Internet daemon, and we examined the code and JCL required to implement the daemon's main task.  In this part we'll see how the sub-job, TELNS, works.

TELNS Processing.

TELNS, shown below, accepts the main job's client ID and the given socket descriptor as arguments.  These are used in the TAKESOCKET call to gain control of the connection.  TAKESOCKET returns a new socket descriptor, "s", and notifies, via the aforementioned EXCEPTION event, the RXINETD program that it can now reclaim the socket.  TELNS also uses SETSOCKOPT to tell TCP/IP that it would like all inbound data to be converted from ASCII to EBCDIC, and all outbound data to be converted from EBCDIC to ASCII.

   /* TELNS */
   parse arg ClientID '/' as .
   InBuf=''; OutBuf=''
   call rxsocket 'initialize', 'telns'
   parse value rxsocket('takesocket',ClientID,as) with rc s
   call rxsocket 'setsockopt',s,'Sol_socket','So_ASCII','On'
   call PutLine "Hello", "EOM"
   parse upper value GetLine() with verb operands
   do while verb <> 'QUIT'
     select
       when verb = 'TIME' then call PutLine time(), "EOM"
       when verb = 'DATE' then call PutLine date(), "EOM"
       otherwise               call PutLine "Unknown command", "EOM"
     end
     parse upper value GetLine() with verb operands
   end
   call PutLine "Goodbye", "EOM"
   call rxsocket 'close', s
   call rxsocket 'terminate', 'telns'

After this point, the programming is quite simple and completely variable.  In this example we start a simple lock-step conversation with the client.  The server says something, and then the client says something.  When either party determines that it is time to quit, they simply close the socket.

GetLine and PutLine.

TELNS uses 2 subroutines to read from and write to the socket.  These are PUTLINE and GETLINE.  GETLINE, shown below, buffers bytes of inbound data into a buffer named INBUF.  It scans INBUF for a carriage return/line feed pair in order to break out lines of data.  This is required because stream sockets do not define record boundaries.  The definition of data boundaries is left to the application.  By convention, many application protocols use the CRLF to delimit discrete bits of information.  The SMTP (email) and HTTP (web) protocols are good examples of this.

   GetLine: procedure expose InBuf s
   do while pos('0D25'x,InBuf)=0
     parse value rxsocket('read',s) with rc RL RData
     InBuf=InBuf||RData
   end
   parse var InBuf Line '0D25'x InBuf
   return Line

Note that GETLINE neatly handles all of the cases you can encounter:

  1. The READ retrieves exactly one full line of data.
  2. The READ retrieves less than one full line.
  3. The READ retrieves several lines of data.

PUTLINE (shown below) performs the reverse function.  It appends a CRLF to the end of each passed line of data and buffers the lines in a variable called OUTBUF.  The buffer is sent whenever either the End-Of-Message argument is passed, or when the buffer reaches a pre-determined size.

   PutLine: procedure expose OutBuf
   OutBuf = OutBuf||arg(1)||'0D25'x
   if (arg(2) = 'EOM') | (length(OutBuf) > 4096) then do
     call rxsocket 'write', s, OutBuf
     OutBuf = ''
   end
   return ""

Strictly speaking, the TELNS application does not need the PUTLINE function since it doesn't use buffering.  However, in other applications, where you may need to send a page or more of data in response to a client command, PUTLINE will *significantly* improve your application's performance.

Conclusion.

I hope that some of you out there in MVS land can put this technology to good use.  Already several customers of mine are using it in real applications.  If you'd like a copy of a "beefed-up" RXINETD (and a couple of example sub-jobs), just send me a note at ehodil@mail.open-softech.com.  The code is FREE and not copyrighted.  Do with it what you will.  I am hopeful that you will make it better!

Along the way, several variations on this scheme have developed.  Most prominent is the substitution of started tasks for both the main job and sub-jobs.  Instead of writing JCL to an internal reader, these applications use ADDRESS CONSOLE -- or some ISV software method -- to issue MVS START commands.  And, of course, the JCL is kept in a procedure library like 'SYS1.PROCLIB'.

In another variation, the sub-job prompts the client for user ID and password.  The sub-job then constructs JCL for a sub-sub-job that includes the user ID and password on the job card.  Next it gives the socket to the sub-sub-job, submits the JCL (using the ddname/internal reader trick), and terminates.  When the sub-sub-job starts, it runs under the specific authority of the client's user.  All without a single line of APF-authorized code!

One twist I'd like to see tried is the use of BatchPipes/MVS.  I don't have it on my system (yet), but a cursory reading of the manuals leads me to believe that several "hot" job steps could be created and passed connection request indications via "piped" ddnames.  This would provide two nice improvements to the architecture.  First, it would move all of the action into one address space.  And second, it would reduce the time it takes to hand a connection from the main task to the subtask.  If someone out there tackles this, please let me (and everyone else) know. I am *very* interested in this subject.