|
|
This section shows two programs, writer and reader,
that lend themselves well to using XDR.
#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); } } }
#include <stdio.h>The two programs appear to be portable for the following reasons: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"); }
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 |
byte | contents | |
---|---|---|
0 | 01 | |
1 | 23 | |
2 | 45 | |
3 | 67 |
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().
#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); } } }
#include <stdio.h> #include <rpc/rpc.h> / xdr is a sub-library of the rpc library /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: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"); }
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.
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.
The xdr_long() primitive is characteristic of most XDR library primitives and all client XDR routines:
xdr_xxx(xdrs, fp) XDR xdrs; xxx fp; { }
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 0Using these conventions, xdr_gnumbers() can be rewritten as follows:#define enum_t int / enum_t's are used for generic enum's /
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.