Remote Procedure Call Examples
This section contains the following RPC examples. Each example was referred to previously in this chapter:
 
note
If you are running on a Solaris system, note the following: When registering an RPC server on a program/version pair, Solaris checks that the user is allowed to register. If another user already registered a server for a program/version pair, Solaris will refuse the new registration. The owner of the pair must run rpcinfo with the -d option to delete the RPC service of the program/version pair before another user can register at that location. Use rpcinfo to check ownership.
Example Procedure Using CALL_UNIX with PV‑WAVE as Client
You can find the following listed file in:
$WAVE_DIR/util/rpc/wave_client.pro
; This PV-WAVE procedure tests an array of samples from a single 
; host or list of hosts. Each array will he a LONG array of length 
; LENGTH. TIMES specifies the number of times to call each host.
PRO samples, length=length, times=times, $ hostlist=hostlist
IF (N_ELEMENTS(times) eq 0) THEN times=100 
IF (N_ELEMENTS(length) eq 0 ) THEN length=1000 
IF (N_ELEMENTS (hostlist) eq 0) THEN hostlist= 'localhost'
; See if hostlist is a single host or a list of hosts
num_hosts = SIZE(hostlist)
; hostlist is a single hostname. Make it a string array of
; length 1
IF(num_hosts(0) EQ 0) THEN BEGIN
name = hostlist
hostlist = strarr(1) 
hostlist(0) = name 
num_hosts = 1
ENDIF ELSE BEGIN
; hostlist is an array of hosts. num_hosts is now number of
; hosts.
num_hosts = num_hosts(1)
ENDELSE
; connect is an array to store "unit" values
connect = LONARR(num_hosts)
; The server (external C routine) is expecting a LONG. It's 
; important to pass one, so that the SERVER will not "die", i.e.:
;      Segmentation Violation : (core dumped)
length = LONG(length)
xpos = 0
ypos = 0
xsize = 300
ysize = 300
; Open one window for each host to monitor
FOR i=0, num_hosts - 1 DO BEGIN
hostname = hostlist(i)
PRINT, 'Opening Window for '+hostname
WINDOW, i, colors=128, title= ' Data from $
'+hostname, xpos=xpos, ypos=ypos, $
xsize=xsize, ysize=ysize
  
xpos = xpos + xsize + 20
IF (xpos GT 800) THEN BEGIN
   xpos = 0
   ypos = ypos + ysize + 30
END
ENDFOR
; Draw the plots with color
IF(!d.n_colors GT 2) THEN BEGIN
!p.background = 12
!p.color = 127
LOADCT,4
ENDIF
; Loop through the server the correct number of times
FOR j=1, times - 1 DO BEGIN
; Call each host and save the connection "unit"
FOR i=0, num_hosts - 1 DO BEGIN
; plot in the correct window
WSET, i

; A temporary variable is needed because PV‑WAVE is not
; able to store into an expression
unit = connect (i)

; This is the call to the external routine/server
array = CALL_UNIX (length, hostname=hostlist (i), $
unit=unit)

; After the first call, connect (i) stays the same
connect (i) = unit
PLOT, array
  
; Empty the graphics buffer
  
EMPTY
ENDFOR
ENDFOR
; This is the last time; close all units.
FOR i=0, num_hosts - 1 DO BEGIN
WSET, i
unit = connect (i)
; connect(i) is closed after this call
array = CALL_UNIX (length, hostname= $ 
hostlist (i), unit=unit, /close)
PLOT, array
EMPTY
ENDFOR
END
Example External C Routine as a Server
You can find the following listed file in:
$WAVE_DIR/util/rpc/samples.c
#include "wave_rpc_extern.h"
/***************************************
This is an example of a simple server. It does little error 
checking and thus can core dump. If, however, PV-WAVE always
sends the correct parameters, there is no problem.
This program is intended to respond to the PV-WAVE statement:
  ARRAY = CALL_UNIX(num_samples)
  where num_samples is a LONG. The PV-WAVE procedure SAMPLES.PRO
  gives an example of how to access this server.
*****************************************/
#define MAX_SAMPLES 100000
main (argc, argv)
int argc;
char *argv[];
{
 int i, id;
 char *user, *proc;
 long num_samples, samples[MAX_SAMPLES];
/* If no program number is specified, 0 is the default */
 if (argv[1] != NULL) {
   id = atoi (argv[1]);
   }
 else {
   id = 0;
   }
/* Forever or until "kill -9" */
 while (1) {
/* Listen for PV-WAVE to call via
  "CALL_UNIX(params)" */
  
   w_listen (id, &user, &proc);
/* WAVE_LONG is a macro that evaluates to:
  *(long *)w_get_par(0, TYP_LONG);
w_get_param will return NULL if the parameter is not a long.
The NULL pointer could cause a segmentation violation and
cause a core dump. It is ok, however, as long as PV-WAVE passes
expected values. */
  
num_samples = WAVE_LONG;
 /* Generate the samples. rand() returns a random number */
for (i = 0; i < num_samples; i++)
  samples[i] = rand();
/* Reply to PV-WAVE with the array "samples". Only "num_samples"
are used. PV-WAVE will receive a LONG array num_samples in
length. W_SMPL_REPLY() does not return the passed parameters
to PV-WAVE. Since this routine didn't modify any of the
parameters there is no need to return them */
  
w_smpl_reply (TYP_LONG, num_samples, samples);
 }
}
Example C Routine with CALL_WAVE as a Client
You can find the following listed file in:
$WAVE_DIR/src/rpc/test_client.c
#include "wave_rpc_extern.h"
/*****************************************
This is an example of a client C program that calls a PV‑WAVE
server. First, a LONG array is generated with "rand ()". The
array is then sent to PV‑WAVE along with a smoothing factor
and a plot title. The following PV‑WAVE procedure is used to
accept the information, smooth the array, and return it as the
result of "CALL_WAVE()". Optionally, PV‑WAVE will plot the
smoothed array.
This program can be started in two fashions: (program is the name
of the executable version of this program)
program
or
program program_number hostname
The program number is needed if the PV‑WAVE SERVER was started
with a program number other than the default value of 0. The
hostname is needed if the server is not running on
"localhost".
*******************************************/
/* Length of the array to be smoothed */
#define ARRAY_LENGTH 200
/* Window to use for smoothing */
#define SMOOTH_FACTOR 5
main (argc, argv)
int argc;
char *argv[];
{
 int make_single_array ();
/* Code to create a UT_VAR; follows main() */
 int i, id, j, unit, status, times;
 char *title;
 UT_VAR *retval;
/* Variables are passed to WAVE using "UT_VAR"s; see
wave_rpc_extern.h for definition. While only 3 UT_VARs are
used in this example, up to 30 variables may be passed.
ut_arr[3] and ut_arr[4] are not used in this example and are
not necessary and are there for convenience. */
static UT_VAR ut_arr[5];
/* "call_wave" expects an array of UT_VAR pointers */
 static UT_VAR *argptr[] = {
 &ut_arr[0],
 &ut_arr[1],
 &ut_arr[2],
 &ut_arr[3],
 &ut_arr[4]};
/* While the actual user of "procedure" and "user" are actually
determined by the SERVER, it is suggested that the "procedure"
parameter be used to select a particular function inside the
SERVER and the "user" parameter be used as site-specific
security. */
char *user, *procedure, *hostname;
long num_neighbors, long_arr[ARRAY_LENGTH];
/* If no pargram number was specified, use 0 */
 if (argc >= 2) {
 id = atoi (argv[1]);
 }
 else {
 id = 0;
 }
/* If no hostname was specified, use "localhost" */
 if (argc >= 3) {
 hostname = argv[2];
 }
 else {
 hostname = "localhost";
 }
/* Fake user data; generally used for site-specific security */
 user = "Test Client User Data";
/* Function inside the server that this CLIENT wishes to access */
 procedure = "SMOOTH";
/* Title for plot */
 title = "Smoothed Random Numbers";
/* Window to use for smoothing */
 num_neighbors = SMOOTH_FACTOR;
/* Figure how many times to call the server */
 printf("Number of times to call SERVER %s :", hostname);
 scanf ("%d", &times);
 for (j = 0; j < times; j++) {
/* Generate a random array */
 for (i = 0; i < ARRAY_LENGTH; i++) {
  long_arr[i] = rand ();
 }
/* Create a UT_VAR with smoothing factor */
 status = make_single_array (argptr[0],
 TYP_LONG, 1, &num_neighbors);
 if (status < 0) {
  sprintf(stderr,"Error building UT_VAR\n");
 exit (1);
 }
/* Create UT_VAR with long array */
 status = make_single_array (argptr[1],
  TYP_LONG | TYP_ARRAY, ARRAY_LENGTH,
  long_arr);
 if (status < 0) {
 sprintf (stderr, "Error building UT_VAR\n");
 exit (1);
 }
/* Make UT_VAR with title */
 status = make_single_array (argptr[2],
 TYP_STRING, 1, &title);
 if (status < 0) {
  sprintf(stderr,"Error building UT_VAR\n");
 exit (1);
 }
 
 
/******************************************
 This is the call to the SERVER. The SERVER’s response will be
pointed to by "retval".
CALL_WAVE() is used in by :
RETURN_VALUE = CALL_WAVE(number of UT_VARs in parameter array,
parameter array (UT_VAR *array[]), name of host to call, unit
(NULL means don’t care) close (if 1, close specified unit),
procedure name, user data, timeout in secs (0 means use default))
The unit parameter provides a way to maintain a connection
between the SERVER and CLIENT. If unit is non-null and points
to an integer = 0, then once the connection is established it
is left open. Further calls using that unit will use the
existing connection instead of creating a new one. If close
is 1, then the connection is closed after the call.
Using the unit parameter causes extra file descriptors to be left
open inside the UNDERTOE CLIENT. Since there is a limit on the
number of open file descriptors, don’t leave too many units
open. Using the unit parameter is useful when establishing the
connection takes a long time. Tests on several machines have
shown that this time is generally much less than a second.
That delay is only a significant portion of the total transfer
time if the total data transfer is < 10,000 bytes and the
SERVER’s computation time is negligible. So to avoid some
added housekeeping, use NULL
******************************************/
retval = (UT_VAR *) call_wave (3, argptr, hostname,
 NULL, 0, id, procedure, user, 0);
/* Check the type of the returned parameter */
/* If it is not an error and it’s an array, print it */
 if (retval->type == (TYP_LONG | TYP_ARRAY)) 
{
for (i = 0; i < ARRAY_LENGTH; i++) {
 /* value.array is defined as (char *) so cast to the appropriate type. */
 printf ("%d ", ((long *) 
  retval->value.array)[i]);
 }
 }
 else {
 printf("SERVER did not return \n"); 
 printf("expected value\n");
 printf ("Returned from call_wave \n");
 
 
 printf ("retval type is %d \n", 
  retval->type);
}
}
}
Example C Function to Load a UT_VAR
You can find the following listed file in:
$WAVE_DIR/src/rpc/test_client.c
int make_single_array (ut_ptr, type, length, array_ptr)
unsigned char type; /* type of UT_VAR */
long length; /* length of array */
char *array_ptr; /* pointer to data */
UT_VAR *ut_ptr; /* pointer to UT_VAR */
{
/* Sample routine to build UT_VARs. This routine will only build
UT_VARs that are single dimensioned. To build a
multi-dimensional array set n_dim and dim[], i.e.:
  
A three dimensional array that is 4 by 5 by 6 would be:
ut_ptr->n_dim = 3;
ut_ptr->dim[0] = 4;
ut_ptr->dim[1] = 5;
ut_ptr->dim[2] = 6;
*/
/* switch on type ignoring the TYP_ARRAY bit */
 switch (type & SIMPLE_MASK) {
 case TYP_BYTE:
 ut_ptr->type = TYP_BYTE;
 ut_ptr->element_size = sizeof (char);
 ut_ptr->n_dim = 1;
 ut_ptr->dim[0] = length;
 if (type & TYP_ARRAY) {
 ut_ptr->type = ut_ptr->type | TYP_ARRAY;
 ut_ptr->value.array = array_ptr;
 }
 else {
 /* array_ptr is a (char *), cast to proper type */
 ut_ptr->value.c = *(unsigned char *) array_ptr;
 }
 break;
 case TYP_INT:
 ut_ptr->type = TYP_INT;
 ut_ptr->element_size = sizeof (short);
 ut_ptr->n_dim = 1;
 ut_ptr->dim[0] = length;
 if (type & TYP_ARRAY) {
 ut_ptr->type = ut_ptr->type | TYP_ARRAY;
 ut_ptr->value.array = array_ptr;
 }
 else {
 /* array_ptr is a (char *), cast to proper type */
 ut_ptr->value.i = *(short *) array_ptr;
 }
 break;
case TYP_INT32:
ut_ptr -> type = TYP_INT32;
ut_ptr -> element_size = sizeof (int);
ut_ptr -> n_dim = 1;
ut_ptr -> dim[0] = length;
if (type & TYP_ARRAY) {
ut_ptr -> type = ut_ptr -> type | TYP_ARRAY;
ut_ptr -> value.array = array_ptr;
}
else {
/* Cast array_ptr to proper type since it's a (char *) */
ut_ptr -> value.i32 = *(int *) array_ptr;
}
 break;
case TYP_LONG:
 ut_ptr->type = TYP_LONG;
 ut_ptr->element_size = sizeof (long);
 ut_ptr->n_dim = 1;
 ut_ptr->dim[0] = length;
 if (type & TYP_ARRAY) {
 ut_ptr->type = ut_ptr->type | TYP_ARRAY;
 ut_ptr->value.array = array_ptr;
 }
 else {
 /* array_ptr is a (char *), cast to proper type */
 ut_ptr->value.l = *(long *) array_ptr;
 }
 break;
 case TYP_DOUBLE:
 ut_ptr->type = TYP_DOUBLE;
 ut_ptr->element_size = sizeof (double);
 ut_ptr->n_dim = 1;
 ut_ptr->dim[0] = length;
 if (type & TYP_ARRAY) {
 ut_ptr->type = ut_ptr->type | TYP_ARRAY;
 ut_ptr->value.array = array_ptr;
 }
 else {
 /* array_ptr is a (char *), cast to proper type */
 ut_ptr->value.d = *(double *) array_ptr;
 }
 break;
 case TYP_FLOAT:
 ut_ptr->type = TYP_FLOAT;
 ut_ptr->element_size = sizeof (float);
 ut_ptr->n_dim = 1;
 ut_ptr->dim[0] = length;
 if (type & TYP_ARRAY) {
 ut_ptr->type = ut_ptr->type | TYP_ARRAY;
 ut_ptr->value.array = array_ptr;
 }
 else {
 /* array_ptr is a (char *), cast to proper type */
 ut_ptr->value.f = *(float *) array_ptr;
 }
 break;
 case TYP_COMPLEX:
 ut_ptr->type = TYP_COMPLEX;
 ut_ptr->element_size = sizeof (COMPLEX);
 ut_ptr->n_dim = 1;
 ut_ptr->dim[0] = length;
 if (type & TYP_ARRAY) {
 ut_ptr->type = ut_ptr->type | TYP_ARRAY;
 ut_ptr->value.array = array_ptr;
 }
 else {
/* see wave_rpc_extern.h for definition of COMPLEX array_ptr
 * is a (char *), cast to proper type */
 ut_ptr->value.cmp.r = ((COMPLEX *) array_ptr)->r;
 ut_ptr->value.cmp.i = ((COMPLEX *) array_ptr)->i;
 }
 break;
 case TYP_STRING:
 ut_ptr->type = TYP_STRING;
 ut_ptr->element_size = sizeof(char);
 ut_ptr->n_dim = 1;
 ut_ptr->dim[0] = length;
 if (type & TYP_ARRAY) {
/* a string array is an array of (char *) */
ut_ptr->element_size=sizeof (char*);
ut_ptr->type = ut_ptr->type | TYP_ARRAY;
ut_ptr->value.array = array_ptr;
 }
 else {
/* array_ptr is a (char *), cast to proper type */
 ut_ptr->value.string = *(char **) array_ptr;
 }
 break;
 default:
 /* unsupported types: TYP_STRUCTURE is not supported */
 return (-1);
 break;
 }
}
Example Procedure Using UNIX_LISTEN and UNIX_REPLY with PV‑WAVE as a Server
You can find the following listed file in:
$WAVE_DIR/util/rpc/example_server.pro
; This is an example of using PV-WAVE as the SERVER. Times is the 
; number of times to loop through the SERVER. Show specifies that 
; the resulting array should be plotted.
PRO example_server, Times=times, Show=show
@UT_COMMON
prog = 0
; Default is to loop through only once
IF (n_elements(times) EQ 0) then times = 1
WHILE(1) DO BEGIN
FOR i = 1, times DO BEGIN
; Listen for a call using the default program number
; zero. Return the received "procedure" string into the
; variable "proc"
n = UNIX_LISTEN(program = prog, procedure = $ proc)
; Make sure the proc is SMOOTH and we have the correct number of
; parameters
IF (n EQ 3) and (proc EQ 'SMOOTH') THEN BEGIN
   ;Smooth the array
   array = SMOOTH(ut_param1, ut_param0)
   ; Return the array to the caller but do not return
   ; the parameters. To return the parameters, the call
   ; would be:
   ;    status = UNIX_REPLY(array, /Return)
   status = UNIX_REPLY(array)
   ; Only plot when told to do so
   IF (keyword_set(show)) THEN BEGIN
      !P.title = ut_param2
      PLOT, array
      EMPTY
   ENDIF
ENDIF ELSE BEGIN
   ; The server did not get the correct proc or
   ; did not get the correct number of parameters
   PRINT, 'EXAMPLE_SERVER MISMATCH: ' + $
     'PROC = ',proc, ' N_PARAMS = ', n
   status = UNIX_REPLY(-1)
ENDELSE
ENDFOR
ENDWHILE
END