6.3. Utility routines for working with select()

Working with descriptor sets (the 'fd_set' data type in C) is a little tricky in RPG. We will want to add utility routines to our 'sockutil' service program to help us work with them.

As you know, the socket API returns an integer. This integer that it returns is called a socket descriptor. The first descriptor opened in a job will always return a 0. The next one will be a 1, then a 2, etc. Of course, the system itself, along with every program in the job, can open some of these -- so you really never know what number you'll get.

Remember that the select() API is able to tell you which sockets are 'readable', 'writable' or 'have a pending exception'. That means that you need a way to give select() a whole list of sockets, and it needs a way to return a whole list of results.

You do this by using a bit string. For example, if you called socket() twice, and the first time it returned a 1, and the second time it returned a 5, you'll set on bit #1, and bit #5 in the bit string. Now select will know that it needs to check to see if there's data to read on descriptors 1 and 5.

This string of bits is called an 'fd_set' (which stands for 'file descriptor set', since select() can also be used with stream files)

In C, the fd_set data type is an array of unsigned integers. Each integer is 32-bits long (that is, 4 bytes) and the array always has at least 7 integers in it. Therefore, the minimum size for a fd_set array is 28 bytes long.

The bits of each integer of the array are numbered from 0 to 31, starting with the rightmost (least significant) bit. Therefore, if we were to number the descriptors, it'd look something like this:

First integer in array represents the first 32 descriptors:

          3     2    2    1    1    
          1.....5....0....5....0....5....0
       


Second integer in array represents the next 32-descriptors:

          6  6    5    5    4    4    3  3
          3..0....5....0....5....0....5..2
       


And so on... until we got to descriptor #223.

Since bitwise operations in RPG are actually easier to do on character fields than they are on integer fields, I implemented the 'fd_set' data type in RPG as a simple 28-byte long alphanumeric field.

Each bit in that field must be numbered the same way that the array of integers was for the C programs, however, since we're calling the same select() API that C programs will call.

Therefore, I wrote the following subprocedure that will calculate which will take a descriptor number, and calculate which byte in the '28A' field needs to be checked, and a bitmask which can be used to set the specific bit within that byte on, or off, of whatever you want to do with it.

This subprocedure looks like this:

         P CalcBitPos      B
         D CalcBitPos      PI
         D    peDescr                    10I 0
         D    peByteNo                    5I 0
         D    peBitMask                   1A
         D dsMakeMask      DS
         D   dsZeroByte            1      1A
         D   dsMask                2      2A
         D   dsBitMult             1      2U 0 INZ(0)
         C     peDescr       div       32            wkGroup           5 0
         C                   mvr                     wkByteNo          2 0
         C                   div       8             wkByteNo          2 0
         C                   mvr                     wkBitNo           2 0
         C                   eval      wkByteNo = 4 - wkByteNo
         c                   eval      peByteNo = (wkGroup * 4) + wkByteNo
         c                   eval      dsBitMult = 2 ** wkBitNo
         c                   eval      dsZeroByte = x'00'
         c                   eval      peBitMask = dsMask
         P                 E
     

This routine will be called by many of the subprocedures that will write to replace the C-language 'pre-processor macros'.

The first pre-processor macro that we need to duplicate will be the 'FD_ZERO' macro. All this does is zero out all of the bits in a 'fd_set'. To do this in RPG, using a 28A field is child's play. We just do this:

         P*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
         P* Clear All descriptors in a set.  (also initializes at start)
         P*
         P*      peFDSet = descriptor set
         P*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
         P FD_ZERO         B                   EXPORT
         D FD_ZERO         PI
         D   peFDSet                     28A
         C                   eval      peFDSet = *ALLx'00'
         P                 E
     

next, we'll need to do 'FD_SET'. First of all, remember that C is case-sensitive, so 'FD_SET' is a different name from 'fd_set'. The FD_SET macro in C is used to turn on one bit (one descriptor) in a descriptor set.

To do this in RPG, we'll have to call the CalcBitPos routine that we described above to find out which byte & bitmask to use, then we'll use that bitmask to turn on the appropriate bit. Therefore, our FD_SET subprocedure will look like this:

         P FD_SET          B                   EXPORT
         D FD_SET          PI
         D   peFD                        10I 0
         D   peFDSet                     28A
         D wkByteNo        S              5I 0
         D wkMask          S              1A
         D wkByte          S              1A
         C                   callp     CalcBitPos(peFD:wkByteNo:wkMask)
         c                   eval      wkByte = %subst(peFDSet:wkByteNo:1)
         c                   biton     wkMask        wkByte
         c                   eval      %subst(peFDSet:wkByteNo:1) = wkByte
         P                 E
     

The opposite of FD_SET is FD_CLR. FD_CLR turns a single bit off in a descriptor set. The code to do this will be nearly the same thing as FD_SET, except that we turn the bit off instead of on. Like so:

         P FD_CLR          B                   EXPORT
         D FD_CLR          PI
         D   peFD                        10I 0
         D   peFDSet                     28A
         D wkByteNo        S              5I 0
         D wkMask          S              1A
         D wkByte          S              1A
         C                   callp     CalcBitPos(peFD:wkByteNo:wkMask)
         c                   eval      wkByte = %subst(peFDSet:wkByteNo:1)
         c                   bitoff    wkMask        wkByte
         c                   eval      %subst(peFDSet:wkByteNo:1) = wkByte
         P                 E
     

The last utility routine that we need is one that can be used to check to see if a bit is still on after calling select(). To make this work nicely in RPG, it'll help to return an indicator field -- so that we can simply check for *ON or *OFF when we call the routine.

This is done with the FD_ISSET macro in C. We simulate it in RPG like this:

         P FD_ISSET        B                   EXPORT
         D FD_ISSET        PI             1N
         D   peFD                        10I 0
         D   peFDSet                     28A
         D wkByteNo        S              5I 0
         D wkMask          S              1A
         D wkByte          S              1A
         C                   callp     CalcBitPos(peFD:wkByteNo:wkMask)
         c                   eval      wkByte = %subst(peFDSet:wkByteNo:1)
         c                   testb     wkMask        wkByte                   88
         c                   return    *IN88
         P                 E
     

Each of these subprocedures listed here should be placed in your SOCKUTILR4 service program. In addition, the prototypes for the FD_xxx procedures should be placed in the SOCKUTIL_H source member so that they are available when you call them from other programs.

The CalcBitPos prototype should be placed at the top of the SOCKUTILR4 member so that routines within that service program can call it. Nobody should need to call CalcBitPos from outside the service program.

You might also find it helpful to define a 28A field in your SOCKUTIL_H member called 'fdset'. (can't call it fd_set, since the name is already taken by the subprocedure) Then you can define your descriptors as being 'like(fdset)'.

For example, add this to SOCKUTIL_H:

         D fdset           S             28A
     

Then use it like this:

         D readset         S                   like(fdset)
         D writeset        S                   like(fdset)
         D excpset         S                   like(fdset)
     

If you prefer, all of the code for SOCKUTIL_H, SOCKUTILR4, SOCKET_H and ERRNO_H are on my website, and you can simply download them and use them directly. http://www.scottklement.com/rpg/socktut/qrpglesrc.sockutil_h http://www.scottklement.com/rpg/socktut/qrpglesrc.sockutilr4 http://www.scottklement.com/rpg/socktut/qrpglesrc.socket_h http://www.scottklement.com/rpg/socktut/qrpglesrc.errno_h

Note: Since I originally posted my FD_xxx code, and advice for using the select() API, I've received a little bit of criticism for using a character string for 'fd_set' instead of an array of integers. However, I stand by my decision to use a 28A field. I think it's nicer, especially because of the ability to use "like" to set the data type.

Now that you've added these routines to the SOCKUTILR4 service program, you'll need to recompile it. You want the new FD_xx routines to be exported so that they can be called from other programs.

To do that, edit the binding language source that you created in chapter 4, so that they now look like this:

         STRPGMEXP PGMLVL(*CURRENT)
              EXPORT SYMBOL(RDLINE)
              EXPORT SYMBOL(WRLINE)
              EXPORT SYMBOL(FD_SET)
              EXPORT SYMBOL(FD_CLR)
              EXPORT SYMBOL(FD_ISSET)
              EXPORT SYMBOL(FD_ZERO)
         ENDPGMEXP
         STRPGMEXP PGMLVL(*PRV)
              EXPORT SYMBOL(RDLINE)
              EXPORT SYMBOL(WRLINE)
         ENDPGMEXP
     

As you can see... we've changed the '*CURRENT' section to list all of the procedures that we're now exporting. We've also added a '*PRV' section that will create a 'previous version signature' for the binder. This previous version only contains the procedures that we had before our changes. Therefore, the service program will remain backward compatible, like we discussed back in Chapter 4.

Type the following commands to re-compile the SOCKUTILR4 service program:

CRTRPGMOD SOCKUTILR4 SRCFILE(SOCKTUT/QRPGLESRC) DBGVIEW(*LIST)

CRTSRVPGM SOCKUTILR4 EXPORT(*SRCFILE) SRCFILE(SOCKTUT/QSRVSRC) TEXT('Socket Utility Service Pgm') ACTGRP(*CALLER)