Portfolio de proyectos

Proyecto: Interconectar sistemas AS400 y BD en JavaServer mediante sockets. Sockets en RPG.

Lo que estamos programando aquí en RPG IV ILE, es un cliente TCP/IP por sockets para envíar mensajes a través de una conexión con el servidor Java.
El fuerte del trabajo queda relegado al uso de estructuras en RPG, un ejemplo de la forma de trabajar con estructuras sería el siguiente:

	 D p_servent       S               *
     D servent         DS                  based(p_servent)
     D   s_name                        *
     D   s_aliases                     *
     D   s_port                      10I 0
     D   s_proto                       *

El puntero p_servent apuntará a una estructura de tipo servent, esto lo especificamos con la palabra reservada based(p_servent), esta estructura contendrá a su vez cuatro variables, la primera un puntero a un string s_name, qué indicara el nombre del servicio, una segunda, un puntero a un tipo string s_aliases que índicara el alias del servicio, la tercera un integer númerico de 10 digitos que nos dará el puerto del servicio, y la última, un puntero de tipo string que apuntará a la cadena de texto que contendrá el protocolo usado por el servicio, sea este TCP o UDP.

El trabajo son sockets

 
     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 inet_addr       PR            10U 0 ExtProc('inet_addr')
     D  address_str                    *   value options(*string)

     D INADDR_NONE     C                   CONST(4294967295)

     D inet_ntoa       PR              *   ExtProc('inet_ntoa')
     D  internet_addr                10U 0 value

     D p_hostent       S               *
     D hostent         DS                  Based(p_hostent)
     D   h_name                        *
     D   h_aliases                     *
     D   h_addrtype                  10I 0
     D   h_length                    10I 0
     D   h_addr_list                   *
     D p_h_addr        S               *   Based(h_addr_list)
     D h_addr          S             10U 0 Based(p_h_addr)

     D gethostbyname   PR              *   extproc('gethostbyname')
     D   host_name                     *   value options(*string)

     D socket          PR            10I 0 ExtProc('socket')
     D  addr_family                  10I 0 value
     D  type                         10I 0 value
     D  protocol                     10I 0 value

     D AF_INET         C                   CONST(2)
     D SOCK_STREAM     C                   CONST(1)
     D IPPROTO_IP      C                   CONST(0)

     D connect         PR            10I 0 ExtProc('connect')
     D  sock_desc                    10I 0 value
     D  dest_addr                      *   value
     D  addr_len                     10I 0 value

     D p_sockaddr      S               *
     D sockaddr        DS                  based(p_sockaddr)
     D   sa_family                    5I 0
     D   sa_data                     14A
     D sockaddr_in     DS                  based(p_sockaddr)
     D   sin_family                   5I 0
     D   sin_port                     5U 0
     D   sin_addr                    10U 0
     D   sin_zero                     8A

     D send            PR            10I 0 ExtProc('send')
     D   sock_desc                   10I 0 value
     D   buffer                        *   value
     D   buffer_len                  10I 0 value
     D   flags                       10I 0 value

     D recv            PR            10I 0 ExtProc('recv')
     D   sock_desc                   10I 0 value
     D   buffer                        *   value
     D   buffer_len                  10I 0 value
     D   flags                       10I 0 value

     D close           PR            10I 0 ExtProc('close')
     D  sock_desc                    10I 0 value

     D translate       PR                  ExtPgm('QDCXLATE')
     D   length                       5P 0 const
     D   data                     32766A   options(*varsize)
     D   table                       10A   const

     D msg             S             50A
     D sock            S             10I 0
     D port            S              5U 0
     D addrlen         S             10I 0
     D ch              S              1A
     D host            s             32A
     D file            s             32A
     D IP              s             10U 0
     D p_Connto        S               *
     D RC              S             10I 0
     D Request         S             60A
     D ReqLen          S             10I 0
     D RecBuf          S             50A
     D RecLen          S             10I 0
     c     *entry        plist
     c                   parm                    host
     c                   parm                    file

     c                   eval      *inlr = *on
          /free
          //***************************************
          //* Obtener el puerto del servicio http
          //***************************************
            dsply 'Hola sock';
          //eval      p_servent = getservbyname('http':'tcp');
            eval      p_servent = getservbyname('as-netprt':'tcp');
            if        p_servent = *NULL;
             eval      msg = 'Can''t find the http service|';
             dsply msg;
             return;
            endif;

            eval      port = s_port;
          //*****************************************
          //* Direccion IP 32 bits a partir del host dado por el usuario
          //*****************************************
            eval      IP = inet_addr(%trim(host));
            if        IP = INADDR_NONE;
             eval      p_hostent = gethostbyname(%trim(host));

             if        p_hostent = *NULL;
              eval      msg = 'Unable to find that host|';
              dsply     msg;
              return;
             endif;

             eval      IP = h_addr;
            endif;
          //*****************************************
          //*  Crea un socket
          //*****************************************
            eval      sock = socket(AF_INET: SOCK_STREAM: IPPROTO_IP);
            if        sock < 0;
              eval   msg = 'Error calling socket()|';
              dsply  msg;
              return;
            endif;
          //*****************************************
          //*  Crea una estructura de conexion para conectar el sockets
          //*****************************************
            eval      addrlen = %size(sockaddr);
            p_connto = %ALLOC(addrlen);
            eval      p_sockaddr = p_connto;
            eval      sin_family = AF_INET;
            eval      sin_addr = IP;
            eval      sin_port = port;
            eval      sin_zero = *ALLx'00';
          //*****************************************
          //*  Conectarse al host solicitado
          //*****************************************
            if        connect(sock: p_connto: addrlen) < 0 ;
              eval      msg = 'unable to connect to server|';
              dsply                   msg;
              callp     close(sock);
              return;
            else;
              msg = 'connect to host';
              dsply msg;
            endif;
          //****************************************
          //* Enviar mensaje
          //****************************************
            eval request = file;
            eval reqlen = %len(%trim(request));
            callp Translate(reqlen: request: 'QTCPASC');
            eval rc = send(sock: %addr(request): reqlen:0);
            if  rc < reqlen;
             eval msg='Unable to send entire request|';
             dsply  msg;
             return;
            endif;
            callp close(sock);
            *inlr = *on;
          /end-free

Lo primero que harémos es obtener el puerto del servicio que vamos a usar para la transferencia o escucha de datos:

	 eval      p_servent = getservbyname('as-netprt':'tcp');
	 struct servent * getservbyname(char *service_name:char *protocol_name)

La función getservbyname('nombre_servicio','tcp/udp') devuelve un puntero que apunta a una estructura de tipo servent (entrada de servicio), esta estructura esta definida de la siguiente manera, y la tenemos que definir en nuestro programa de la siguiente forma:


     D p_servent       S             *
     
     D servent         DS           based(p_servent)
     D   s_name                       *
     D   s_aliases                     *
     D   s_port                      10I 0
     D   s_proto                        *
     

Definimos las variables en RPG NO FREE, la ‘D’ inicial índica la sesión de datos, creamos un puntero p_servent S * (la S índica que el puntero es una variable, de scope y el asterisco * índica que es un puntero.

Definimos una estructura servent con DS y decimos que será apuntada por el puntero que hemos creado arriba p_servent con based(p_servent), debajo desglosamos los campos de la estructura servent, estos serán un puntero.

Aquí habría que convertir la estructura de C (en UNIX) de servent en estructura RPG. La estructura en C es:

	struct servent {
            char             *s_name;    
            char            **s_aliases;
            int               s_port;     
            char             *s_proto    
          };

Como vemos en C especificamos a que tipo de datos apuntara el puntero, en RPG esto no es necesario, y también tenemos un **s_aliases (el doble asterisco es una doble redirección y quiere decír lo siguiente: Es un puntero a una matriz. Esa matriz es una matriz de punteros. Cada elemento de esa matriz apunta a una cadena alfanumérica.

En RPG tampoco es necesario hacer una doble redirección, esto es solo para C.

La variable s_port índica el puerto y es un alfanúmerico de 10 posiciones y 0 decimales lo índicamos como 10 I (I de integer) y un 0 que índica que lleva 0 decimales.

Para poder usar la función getservbyname() de la API, tenemos que definirla en el area de variables de la siguiente forma:

    D getservbyname   PR         *   ExtProc('getservbyname')
    D   service_name             *   value options(*string)
    D   protocol_name            *   value options(*string)

Donde getservbyname PR índica que es un procedimiento, y ExtProc(nombre_del_procedimiento_externo) índica que lo exportaremos de fuera y el nombre del procedimiento, en este caso será importado de la librerías de UNIX IBM. Con * índicamos que tendremos un puntero a dicho procedimiento. Tenga en cuenta que todos los punteros que se pasan a los procedimientos de C siempre se pasan por "valor". La palabra clave "options(*string)" es una opción agregada a RPG para ayudar a que sea compatible con los programas de C.
Las cadenas en C terminan con un carácter "nulo" (x'00') que le permite reconocer el final de una cadena de longitud variable. Especificar options(*string) hace que RPG agregue automáticamente este nulo final cuando realiza la llamada.

Seguimos con el código:

	 eval      p_servent = getservbyname('as-netprt':'tcp');
     
     if   p_servent = *NULL;  // decimos que si p_servent esta inicializado  
     							 no apunta a nada, a NULL, es que no se ha devuelto el puntero a 
     							 dicha estructura del servicio correctamente
          eval      msg = 'Can''t find the http service|';  // eval es como un move, sirve para decir que 
          						se va a asignar valores con el operador = a una variable
          													   
          return; // el tipico return para salirnos del programa.
     endif;
 

Metemos el valor de la variable s_port en port, una variable local del programa.
La variable port creada será unsigned D port S 5U 0 de 5 posiciones, ya que es lo le tenemos que pasar despues a la estructura de conexión,
estas cosas las podemos mirar en la documentación, poniendo en google el nombre de la función y la palabra “Linux” de las funciones, que parametros recibe y que parametros devuelve.

El segundo paso será convertir la notación decimal con puntos en una dirección de red de 32 bits usada por los sistemas MAINFRAME.

Para ello usaremos la API: unsigned long inet_addr(char *address_string)

 	 D inet_addr       PR            10U 0 ExtProc('inet_addr')
     D  address_str                    *   value options(*string)
 

Para ver si es correcto el valor devuelto podemos comparar el valor de vuelta del procedimiento inet_addr con la siguiente constante, que creo que es un 0 en unsigned.

D INADDR_NONE     C                   CONST(4294967295)

Si es correcto, lo siguiente será llamar a la API: struct hostent *gethostbyname(char *host_name)

Se define como:

 	 D gethostbyname   PR       *   extproc('gethostbyname')
     D   host_name                     *   value options(*string)
 

Si observamos el "valor de retorno" de la API, nos indica que devuelve NULL (o, en RPG, *NULL) si hay un error, o devuelve un puntero a una estructura "hostent". La estructura hostent se describe con este formato:

     struct hostent {
            char   *h_name;
            char   **h_aliases;
            int    h_addrtype;
            int    h_length;
            char   **h_addr_list;
          };
    
          #define h_addr  h_addr_list[0]
 

Esto significa que la estructura de datos del host contiene 5 elementos diferentes: un puntero a una cadena de caracteres que contiene el nombre de host "correcto", un puntero a una matriz de punteros, cada elemento apunta a un "alias" del nombre de host, un entero que contiene el tipo de dirección, un entero que contiene la longitud de cada dirección y un puntero a una matriz de punteros, cada uno apunta a una cadena de caracteres.

La declaración '#define' nos dice que siempre que hagamos referencia a 'h_addr' en esta estructura, en realidad devolverá el primer puntero en la matriz h_addr_list.

Si continúa leyendo, debajo del valor "Return", se explica que ambas matrices a las que se apunta aquí son "listas terminadas en NULL". Esto significa que si recorre la matriz de punteros, sabrá que ha llegado al final cuando encuentre un "NULL" (o *NULL en RPG).

Además, le indica que cada elemento de la matriz h_addr_list en realidad apunta a una estructura de tipo 'in_addr', en lugar de una cadena de caracteres, como implica la definición de la estructura. ¿Por qué hace eso? Porque gethostbyname se puede utilizar con otros protocolos además de TCP/IP. En el caso de esos otros protocolos, podría apuntar a algo además de una estructura 'in_addr'. De hecho, podríamos utilizar el miembro 'h_addrtype' de la estructura para determinar qué tipo de dirección se devolvió, si así lo deseáramos. Sin embargo, este documento solo está destinado a trabajar con direcciones TCP/IP.

En RPG esta estructura se declara así:


	  D p_hostent       S               *
   	  D hostent         DS                  Based(p_hostent)
     	  D   h_name                        *
     	  D   h_aliases                     *
     	  D   h_addrtype                  10I 0
     	  D   h_length                    10I 0
     	  D   h_addr_list                   *
     	  D p_h_addr        S               *   Based(h_addr_list)
     	  
          D h_addr          S             10U 0 Based(p_h_addr)

La forma en que lo hacemos es llamando primero a 'inet_addr' para ver si el host es una dirección IP válida en formato decimal con puntos. Si no lo es, llamaremos a 'gethostbyname' para buscar la dirección mediante DNS o la tabla de host

La IP con la función gethostbyname la podemos obtener así: eval IP = h_addr;

Donde D IP              s             10U 0

 Si necesitamos trabajar con direcciones posteriores en la lista (lo cual es muy inusual, según mi experiencia), podemos hacerlo de la siguiente manera:

	     D addr_list       S               *   DIM(100) Based(h_addr_list)
         D p_oneaddr       S            *
         D oneaddr         S             10U 0 based(p_oneaddr)
    
         C                   do        100           x
         C                   if        addr_list(X) = *NULL
         C                   leave
         C                   endif
         C                   eval      p_oneaddr = addr_list(X)
         C***  oneaddr now contains the IP address of this
         C***   position of the array, use it now...
         C                   enddo

Hasta aquí hemos obtenido todos los datos para realizar la conexión, a continuación crearemos el socket y conectaremos con el sistema remoto:

Para crear un socket:

int socket(int address_family, int type, int protocol) --> Un ejemplo de socket sobre TCP/IP: sock = socket(AF_INET: SOCK_STREAM: IPPROTO_IP);

Valores posibles para address_family (son constantes): Para la familia de direcciones, el manual nos dice que debemos especificar un valor de 'AF_INET' si deseamos realizar programación de red en el 'dominio de Internet'. Por lo tanto, cuando especificamos un valor de 'AF_INET', lo que realmente le estamos diciendo a la API es que 'use el protocolo IP'. Al poner AF_INET (Adress family INET) lo que estamos diciendo es que crearemos un sockets para dominios de Internet.

Valores posibles para type (son constantes):

'SOCK_DGRAM' → Se utiliza para el protocolo UDP
'SOCK_SEQPACKET'
'SOCK_STREAM' → El protocolo TCP es el protocolo de streaming estándar para su uso sobre IP.
'SOCK_RAW'. → Se utiliza para escribir datagramas IP sin procesar.


Valores posibles para protocol (son constantes):

IPPROTO_TCP para TCP
IPPROTO_UDP para UDP
IPPROTO_IP para IP


Si investigas un poco en la biblioteca 'System Openness Includes' (creo que sería QSYSINC library), encontrarás que AF_INET está definido como '2', SOCK_STREAM está definido como '1' e IPPROTO_IP definido como '0'.  Definiremos constantes para pasarle estos valores a la función socket()

Recuerda que la funcion socket() devolvere un "descriptor de socket" que es un entero de longitud 10, y es semejante a un descriptor de fichero, o retornara -1 en caso de que no se cree correctamente.

A continuación crearemos la estructura de conexión para conectar el socket.
	  int connect(int socket_descriptor,
                     struct sockaddr *destination_address,
                     int address_length)
         
          D connect         PR            10I 0 ExtProc('connect')
         D  sock_desc                    10I 0 value
         D  dest_addr                      *   value
         D  addr_len                     10I 0 value
         
         struct sockaddr {
                        u_short sa_family;
                        char    sa_data[14];
                     };

El propósito de esta estructura es indicarle a la API a qué dirección IP y número de puerto conectarse. ¿Por qué, entonces, no contiene campos en los que podamos colocar la dirección y los números de puerto? Nuevamente, debemos recordar que las API de socket pueden funcionar con muchos protocolos de red diferentes. Cada protocolo tiene un formato completamente diferente para el funcionamiento de las direcciones. Por lo tanto, esta estructura "sockaddr" es una estructura genérica. Contiene un lugar para colocar la familia de direcciones de identificación, junto con un campo de "datos" genérico en el que se puede colocar la dirección, independientemente del formato de la dirección.

Aunque no está documentado en la página de IBM para la API connect(), en realidad existe una estructura diferente llamada 'sockaddr_in' que está diseñada especialmente para direcciones de Internet. La definición en C para sockaddr_in se puede encontrar en el archivo QSYSINC/NETINET, miembro IN, si tiene cargado el System Openness Includes.

	     struct sockaddr_in {            /* socket address (internet)   */
             short  sin_family;          /* address family (AF_INET)    */
           u_short  sin_port;            /* port number                 */
            struct  in_addr  sin_addr;   /* IP address                  */
              char  sin_zero[8];         /* reserved - must be 0x00's   */
         };

Para facilitar el uso de estas estructuras en RPG, me gusta hacer que estén basadas en la misma área de memoria. Esto significa que puedes ver los datos como un 'sockaddr', o los mismos datos como un 'sockaddr_in' sin mover los datos. Dicho esto, aquí está la definición que uso para las estructuras sockaddr y sockaddr_in:

	          D p_sockaddr      S               *
          D sockaddr        DS                  based(p_sockaddr)
          D   sa_family                    5I 0
          D   sa_data                     14A  
          D sockaddr_in     DS                  based(p_sockaddr)
          D   sin_family                   5I 0
          D   sin_port                     5U 0 
          D   sin_addr                    10U 0
          D   sin_zero                     8A  

Antes de poder llamar a la API connect(), debemos pedirle al sistema operativo algo de memoria en la que podamos almacenar nuestra estructura sockaddr. Luego, podemos completar la estructura sockaddr_in y llamar a la API connect(). De la siguiente manera:

 eval      addrlen = %size(sockaddr);
         p_connto = %ALLOC(addrlen);  → %ALLOC(lóngitud) reserva espacio de memoria igual a una longitud especificada.
         eval      p_sockaddr = p_connto;
         eval      sin_family = AF_INET;
         eval      sin_addr = IP;
         eval      sin_port = port;
         eval      sin_zero = *ALLx'00';   													 

*ALLx'00' se llama constantes figurativas y lo que hace es poner a x'00' todos los valores del campo a los que referencia. x'00' creo que es NULL en EBCDIC

Tabla valores EBCDIC - ASCII

Una vez definida y completada la estructura, llamamos a la función connect():

connect(sock: p_connto: addrlen)
Por último envíaremos los datos que queramos al servidor:

Lo primero que tenemos que hacer antes de envíar los datos es convertirlos de EBCDIC a ASCII si vamos a envíar a un sistema que no sea AS400.
La API que utilizaremos para ello se denomina QDCXLATE
El conjunto de API iconv() también puede ser usado para ello.
La API QDCXLATE toma los siguientes parámetros:

En el primer parametro pasamos la longitud de los datos a convertir, será un PACKED(5,0), en el segundo parametro le pasamos un puntero a los datos a convetir Char(*), y en el tercer parametro una constante que indicara la tabla de conversión, Char(10)

Y, dado que QDCXLATE es una API OPM (Modelo de programa original), en realidad la llamamos como un programa. Tradicionalmente, llamarías a una API OPM con la declaración 'CALL' de RPG, de esta manera:

		D translate       PR                  ExtPgm('QDCXLATE')
        D   length                         5P 0 const
        D   data                     32766A   options(*varsize)
        D   table                           10A   const

eval request = file; → FIle será un alfanúmerico que pasaremos por PARM para envíar el mensaje que queramos.
eval reqlen = %len(%trim(request)); → %len nos dice la longitud y %trim trimea, es decír quita los espacios a la cadena de texto.

callp Translate(reqlen: request: 'QTCPASC');

Hay dos tablas que utilizaremos en nuestros ejemplos, QTCPASC y QTCPEBC. Estas tablas son fáciles de recordar si simplemente tenemos en cuenta que el nombre de la tabla especifica el conjunto de caracteres al que queremos traducir los datos. En otras palabras, "QTCPEBC" es la tabla proporcionada por IBM para traducir TCP a EBCDIC (desde ASCII) y QTCPASC es la tabla proporcionada por IBM para traducir datos TCP a ASCII (desde EBCDIC).

Una vez traducido los datos de EBCDIC a ASCII podemos enviarlo con send():

	     int send(int socket_descriptor,
                  char *buffer,
                  int buffer_length,
                  int flags)
                   
         D send            PR            10I 0 ExtProc('send')
         D   sock_desc                   10I 0 value
         D   buffer                        *   value
         D   buffer_len                  10I 0 value
         D   flags                       10I 0 value

Es posible que hayas notado que para otras definiciones de 'char *', incluimos la palabra clave 'options(*string)' en nuestras especificaciones D, pero no lo hicimos esta vez. ¿Por qué? Porque la API send() no utiliza un carácter nulo final para determinar el final de los datos que se enviarán. En cambio, utiliza el parámetro buffer_length para determinar cuántos datos se enviarán.

Esta es una característica útil para nosotros, porque significa que podemos transmitir el carácter nulo (x'00') a través de la conexión de red así como también el resto de la cadena, si así lo deseamos.

El parámetro flags se utiliza para "datos fuera de banda" y para enviar datos "no enrutados". Casi nunca utilizará estos indicadores. ¿Por qué? Porque los "datos fuera de banda" nunca se han adoptado ampliamente. Muchas pilas TCP/IP ni siquiera lo implementan correctamente. De hecho, durante mucho tiempo, enviar datos "fuera de banda" a una máquina Windows hacía que esta se bloqueara. El popular programa llamado "winnuke" no hace nada más que enviar algunos datos fuera de banda a una máquina Windows. El otro indicador, "no enrutar", en realidad solo se utiliza al escribir aplicaciones de enrutamiento. En todas las demás situaciones, desea que sus paquetes se enruten. Por lo tanto, es muy raro que especifiquemos algo que no sea un 0 en el parámetro flags.

El valor de retorno de la API send() será la cantidad de bytes enviados o un número negativo si ocurrió un error.

eval rc = send(sock: %addr(request): reqlen:0);
%addr(request) es la dirección del buffer.

Para recibir datos a través de la red, usaremos la API recv(). Esta API es muy similar a send():

	       int recv(int socket_descriptor,
                     char *buffer,
                     int buffer_length,
                     int flags)

La diferencia entre send() y recv() es lo que hace el sistema con la memoria indicada por el parámetro 'buffer'.
Cuando se utiliza send(), los datos del buffer se escriben en la red. Cuando se utiliza recv(), los datos se leen desde la red y se escriben en el buffer.
Otra diferencia es la cantidad de datos que se procesan en cada llamada a estas API.
De manera predeterminada, cuando llama a la API send(), la llamada a la API no devolverá el control a su programa hasta que se haya escrito todo el búfer en la red. Por el contrario, la API recv() recibirá todos los datos que están esperando actualmente a su aplicación.
Tenemos que comprobar el código de retorno de la API de recv() para ver cuánto hemos recibido realmente.

Por último cerramos la conexión, cerrando el socket:
	D close           PR            10I 0 ExtProc('close')
    D  sock_desc                    10I 0 value
callp close(sock);

Colocaremos lo siguiente al final del código para índicar la salida del programa:

*inlr = *on;

La activación LR o el indicador LR o (Último registro) es simplemente una forma de indicarle al programa que se leyó el último registro y que cuando salgo del programa se pueden cerrar todos los archivos, es una forma también de decir que ahí finaliza el programa. Debería ser la última sentencia del programa. Hace un return y devuelve el control al programa que lo llamo. (es como el GOBACK en COBOL)

Contacto

Para más información, proponer o trabajar en algún proyecto conjunto:

Email: arxivelogic@proton.me