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.
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.
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.
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 - ASCIIUna 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.
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