DOC HOME SITE MAP MAN PAGES GNU INFO SEARCH PRINT BOOK
 
Developing distributed applications using ONC RPC and XDR

Creating portable data with XDR

This section shows two programs, writer and reader, that lend themselves well to using XDR.

writer

   #include <stdio.h>
   

main() /* writer.c */ { long i;

for (i = 0; i < 8; i++) { if (fwrite((char *)&i, sizeof(i), 1, stdout) != 1) { fprintf(stderr, "failed!\n"); exit(1); } } }

reader

   #include <stdio.h>
   

main() /* reader.c */ { long i, j;

for (j = 0; j < 8; j++) { if (fread((char *)&i, sizeof (i), 1, stdin) != 1) { fprintf(stderr, "failed!\n"); exit(1); } printf("%ld ", i); } printf("\n"); }

The two programs appear to be portable for the following reasons: The behavior of these two programs can be verified by piping the output of the writer program to the reader program. For example, these programs should produce identical results when run on both an SCO OpenServer system and a Sun workstation, as shown in the sample runs below.
   nix% writer | reader
   0 1 2 3 4 5 6 7
   nix%
   sun% writer | reader
   0 1 2 3 4 5 6 7
   sun%
With the advent of local area networks came the concept of network pipes, in which a process on one machine produces the data, and a second process on a different machine consumes the data. A network pipe can be constructed with writer and reader. Below is an example of a network pipe in which an SCO OpenServer system produces data and a Sun workstation consumes the data.
   nix% writer | rcmd sun reader
   0 16777216 33554432 50331648 67108864 83886080 100663296 117440512
   nix%
If the machine on which each program is run is changed, the results will be the same. These results occur because the byte ordering of long integers differs between these machines. Other data types can have varying sizes, byte orderings, representations, and alignments, depending on the underlying hardware of the machine. For example, the number 01234567 is stored on an SCO OpenServer system as follows:

byte contents  
0 67  
1 45  
2 23  
3 01  
A Sun stores the same number in the following way:

byte contents  
0 01  
1 23  
2 45  
3 67  
Note that 16777216 is 2[24]; when four bytes are reversed, the 1 winds up in the 24th bit.

This example shows the need for portable data, a need which exists whenever data is shared by two or more machine types. Programs can be made data-portable by replacing the read() and write() calls with calls to an XDR library routine xdr_long(). This routine is a filter that knows the standard representation of a long integer in its external form.

The following programs show writer and reader revised to include xdr_long().

writer

   #include <stdio.h>
   #include <rpc/rpc.h> /*xdr is a sub-library of the rpc library*/
   

main() /* writer.c */ { XDR xdrs; long i;

xdrstdio_create(&xdrs, stdout, XDR_ENCODE); for (i = 0; i < 8; i++) { if (! xdr_long(&xdrs, &i)) { fprintf(stderr, "failed!\n"); exit(1); } } }

reader

   #include <stdio.h>
   #include <rpc/rpc.h> /* xdr is a sub-library of the rpc library */
   

main() /* reader.c */ { XDR xdrs; long i, j;

xdrstdio_create(&xdrs, stdin, XDR_DECODE); for (j = 0; j < 8; j++) { if (! xdr_long(&xdrs, &i)) { fprintf(stderr, "failed!\n"); exit(1); } printf("%ld ", i); } printf("\n"); }

Here are the results from executing the new programs in three different ways: both programs on an SCO OpenServer system, both programs on a Sun workstation, and one program on each machine:
   nix% writer | reader
   0 1 2 3 4 5 6 7
   nix%
   sun% writer | reader
   0 1 2 3 4 5 6 7
   sun%
   xenix% writer | rcmd sun reader
   0 1 2 3 4 5 6 7
   xenix%
Dealing with integers is only a small part of portable data. Arbitrary data structures present portability problems, particularly with respect to alignment and pointers. Alignment on word boundaries may cause the size of a structure to vary from machine to machine. Pointers are convenient to use, but have no meaning outside the machine where they are defined.

The XDR library package solves data portability problems. It allows you to write and read arbitrary C constructs in a consistent, specified, well-documented manner. Thus, it makes sense to use the library even when the data is not shared among machines on a network.

The XDR library has filter routines for many subjects, including strings (null-terminated arrays of bytes), structures, unions, and arrays, to name a few. Using more primitive routines, you can write your own specific XDR routines to describe arbitrary data structures, including elements of arrays, arms of unions, or objects pointed at from other structures. The structures themselves may contain arrays of arbitrary elements or pointers to other structures.

The rest of this section examines the two programs more closely.

Implementation details

A family of XDR stream-creation routines exists in which each member treats the stream of bits differently. In the example given, data is manipulated using standard I/O routines, so xdrstdio_create() is used. The parameters to XDR stream-creation routines vary according to their function.

In the example, xdrstdio_create() takes a pointer to an XDR structure that it initializes, a pointer to a FILE that the input or output is performed on, and the operation. The operation may be XDR_ENCODE for serializing in the writer program, or XDR_DECODE for deserializing in the reader program.


NOTE: RPC clients never need to create XDR streams. The RPC system itself creates these streams, which are then passed to the clients.

The xdr_long() primitive is characteristic of most XDR library primitives and all client XDR routines:

In this case, xxx is long, and the corresponding XDR routine is the primitive xdr_long. The client could also define an arbitrary structure xxx, in which case the client would also supply the routine xdr_xxx, describing each field by calling XDR routines of the appropriate type. In all cases, the first parameter, xdrs, can be treated as an opaque handle and passed to the primitive routines.

XDR routines are direction independent; that is, the same routines are called to serialize or deserialize data. This feature is critical to software engineering of portable data. The intention is to call the same routine for either operation; this almost guarantees that serialized data can also be deserialized. One routine is used by both producer and consumer of networked data. This is implemented by always passing the address of an object rather than the object itself; only in the case of deserialization is the object modified. This feature is not shown in the example, but its value becomes obvious when nontrivial data structures are passed among machines. If needed, the direction of the XDR operation can be obtained.

Consider a slightly more complicated example. Assume that a person's gross assets and liabilities are to be exchanged among processes. Also assume that these values are important enough to warrant their own data type:

   struct gnumbers {
           long g_assets;
           long g_liabilities;
   };
The corresponding XDR routine describing this structure would be:
   bool_t                  /* TRUE is success, FALSE is failure */
   xdr_gnumbers(xdrs, gp)
           XDR *xdrs;
           struct gnumbers *gp;
   {
           if (xdr_long(xdrs, &gp->g_assets) &&
               xdr_long(xdrs, &gp->g_liabilities))
                   return(TRUE);
           return(FALSE);
   }
The parameter xdrs is never inspected or modified; it is only passed on to the subcomponent routines. It is imperative to inspect the return value of each XDR routine call, and to give up immediately and return FALSE if the subroutine fails.

This example also shows that the type bool_t is declared as an integer whose only values are TRUE (1) and FALSE (0). This section uses the following definitions:

   #define bool_t   int
   #define TRUE       1
   #define FALSE      0
   

#define enum_t int /* enum_t's are used for generic enum's */

Using these conventions, xdr_gnumbers() can be rewritten as follows:
   xdr_gnumbers(xdrs, gp)
           XDR *xdrs;
           struct gnumbers *gp;
   {
           return (xdr_long(xdrs, &gp->g_assets) &&
                   xdr_long(xdrs, &gp->g_liabilities));
   }
This section uses both coding styles.

Next topic: XDR library primitives
Previous topic: Compiling programs that contain XDR routines

© 2003 Caldera International, Inc. All rights reserved.
SCO OpenServer Release 5.0.7 -- 11 February 2003