Chapter 2. Looking up host names & services

Table of Contents
2.1. Services and Ports
2.2. Host names and addresses
Written by Scott Klement.

2.1. Services and Ports

The previous sections described an overview of TCP/IP terminology as well as an overview of calling the sockets API for a TCP connection. This section describes the details of calling "getservbyname()" and explains how to understand the IBM UNIX-type API manual.

The Socket API is described in the IBM Information Center under the category "UNIX-type APIs." Unfortunately, the calling syntax that's described is for people using the ILE C/400 programming language.

The getservbyname() API is documented here: http://publib.boulder.ibm.com/pubs/html/as400/v4r5/ic2924/info/apis/gsrvnm.htm

And it says that in order to call getservbyname, we need to specify parms like this:

             struct servent *getservbyname(char *service_name,
                                           char *protocol_name)
     


By now, unless you're familiar with programming in C, you're probably saying "huh? what does that mean?" So, I'll tell you :) Let's look at that statement again, but break it into pieces:

             (1)struct servent *(2)getservbyname((3)char *service_name, (4)char *protocol_name)
     
(1)
Each prototype in C starts with the prcoedure's return value. In this case, the '*' means that it returns a pointer, and the 'struct servent' means that this pointer points to a structure of type 'servent' (service entry).
(2)
The name of the procedure to be called is 'getservbyname'.
(3)
The first parameter is a pointer. (I know this because of the '*') and it points to a character variable called "service_name." Character variables are type 'A' in RPG and DDS.
(4)
The second parameter is another pointer to a character variable. This one is called "protocol name".

Since names in C are case sensitive, we MUST supply the name 'getservbyname' as all lowercase. (That's important since RPG likes to convert everything to uppercase at compile-time).

So, to define this same prototype in RPG, we need to do this:

    DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++++
    D getservbyname   PR              *   ExtProc('getservbyname')
    D   service_name                  *   value options(*string)
    D   protocol_name                 *   value options(*string)
     


Note that all pointers passed to C procedures are always passed by "value". The keyword "options(*string)" is an option added to RPG to help make it compatible with C programs. Strings in C end with a "null" character (x'00') that allows it to recognize the end of a variable length string. Specifying options(*string) causes RPG to automatically add this trailing null when it makes the call.

If you look further down, (in the return value section) they describe this "servent" structure by showing us this:

          struct servent {
            char            *s_name;    (1)
            char            **s_aliases;(2)
            int             s_port;     (3)
            char            *s_proto    (4)
          };

This means that the servent structure contains 4 pieces of data:

(1)
A pointer to an area of memory containing an alphanumeric string. This piece of data is called "s_name".
(2)
This one is quite difficult for your typical RPG programmer to understand. It is a pointer to an array. That array is an array of pointers. Each element of that array points to an alphanumeric string.
(3)
An integer called "s_port".
(4)
The last piece of data is a pointer to an alphanumeric string call "s_proto".

So, if we wanted to define this structure in RPG, we'd do it like this:

    DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++++
    D p_servent       S               *
    D servent         DS                  based(p_servent)
    D   s_name                        *
    D   s_aliases                     *
    D   s_port                      10I 0
    D   s_proto                       *
     


As you can probably tell from this reading this page, so far, the hardest part of calling the UNIX-type APIs from RPG programs is converting things from C syntax to RPG syntax. That's why it's so frustrating that IBM doesn't provide a manual and header files specifically for RPG programmers.

For this tutorial, I will show you how to make the conversions from the C prototypes to the RPG prototypes. If you want to learn more about how to do this yourself, you might try this link: http://www.opensource400.org/callc.html

But, anyway... I'll get off the soap box and get back to the tutorial...

Once we have set up this data structure and procedure prototype, we can call the getservbyname procedure simply by coding:

    CL0N01Factor1+++++++Opcode&ExtExtended-factor2+++++++++++++++++++++++++
    C                   eval      p_servent = getservbyname('http': 'tcp')
     


And then check the value of s_port to find out which port http is located on. As you can see, defining the prototype and data structure is the hard part. Once we've got that, making the call is easy.

So, here's a simple program that accepts a service name as a parameter, and displays the corresponding port number:

    Member: QRPGLESRC, SERVPORT
         H DFTACTGRP(*NO) ACTGRP(*NEW)
    
         D getservbyname   PR              *   ExtProc('getservbyname')
         D  service_name                   *   value options(*string)
         D  protocol_name                  *   value options(*string)
    
         D p_servent       S               *
         D servent         DS                  based(p_servent)
         D   s_name                        *
         D   s_aliases                     *
         D   s_port                      10I 0
         D   s_proto                       *
    
         D service         S             10A
         D msg             S             50A
    
         c     *entry        plist
         c                   parm                    service
    
         c                   eval      p_servent = getservbyname(
         c                                 %trim(service): 'tcp')    (1)
    
         c                   if        p_servent = *NULL             (2)
         c                   eval      msg = 'No such service found!'
         c                   else
         c                   eval      msg = 'port = ' + %editc(s_port:'L')
         c                   endif
    
         c                   dsply                   msg
    
         c                   eval      *inlr = *on
     

Compile this by typing: CRTBNDRPG SERVPORT SRCFILE(SOCKTUT/QRPGLESRC)

Run it by typing CALL SERVPORT PARM('http')

A few notes:

(1)
Note that we must do a %trim() on the service when we call getservbyname. Otherwise, it would search for a service called 'http ' instead of 'http'. Since service names can't legally have spaces in them, this wouldn't work.
(2)
The IBM manual states that getservbyname will return NULL when it can't find a service. In RPG, the special value NULL can be represented by the special constant *NULL, so we check for *NULL to see that the service wasn't found.