Remote Procedure Call
On this page, we consider the general issue of remote procedure call. An
RPC is a procedure call across processes, whether they are located on the
same or different hosts. (Sometimes the term local RPC is used to
indicate an RPC within a hosts).
RPCs have several advantages over sockets-level programming:
-
Procedure calls are a convenient abstraction for the request of remote
services.
-
RPCs are type safe, and transparently manage host differences in datatypes
and layouts
-
RPC seperates the interface from the implementation, yielding more readable
designs.
Type safety
Computer architecture-Operating Systems differ from one another in several
aspects of datatype representation. These include:
-
alignment
-
endianess
-
floating point format
The first two issues are because there are multibyte primitive data types
(such as 4-byte integers), yet memory is byte addressible. The last issue
is because there still exists some legacy floating point formats, although
all microprocessor originated designs are based on IEEE floating point
format.
Alignment
High performance computers require multibyte primitive to be aligned. That
is, a k byte primitive must have a starting address which is divisible
by k in an aligned architecture.
For example, the structure:
struct {
char a;
int i;
}
in an aligned architecture requires 8 bytes (a is followed by
three bytes of padding), since newly allocated storage is always at the
highest alignment of the architecture: in an unaligned archtecture it would
require 5 bytes.
A more subtle example is:
struct {
char a;
int i;
char b;
}
At first glance, this might seem to require only 9 bytes in an aligned
archecture (data elements cannot be reordered in C). However, this requires
12 bytes (three bytes of padding at the end) since the size of a array
element is the same as the size of an object. (Note that an array of characters
needs no internal padding since the requirement is only that the size of
an object must be a multiple of its largest primitive object).
Endianess
Architectures can either be:
-
big endian: most significant byte at lowest address
-
little endian: least signifcant byte at lowest address.
Network standard traffic is big endian. (htonl and htons
do this endian conversion, if necessary).
Floating point
Before IEEE floating point standard in the 1980's each computer manufacturer
designed its own floating point system. Of those, perhaps the two most
important are Cray and IBM.
Between IEEE floating point, issues of byte order are still important
but the size and meaning of mantissa and exponent have been standardized.
ONC RPC
RPC Interface Definition Language (IDL)
| definition-list |
:= |
definition ;| |
|
|
definition ; definition-list |
| definition |
:= |
const-definition | |
|
|
enum-definition | |
|
|
struct-definition | |
|
|
union-definition | |
|
|
typedef-definition | |
|
|
program-definition |
|
|
|
| const-definition |
:= |
const ident = integer |
|
|
|
| enum-definition |
:= |
enum ident { enum-value-list } |
|
|
|
| enum-value-list |
:= |
enum-value | |
|
|
enum-value , enum-value-list |
| enum-value |
:= |
ident = value | |
|
|
ident |
| struct-definition |
:= |
struct ident { declaration-list } |
| declaration-list |
:= |
declaration ; | |
|
|
declaration ; declaration-list |
| union-definition |
:= |
union ident switch ( declaration ) { case-list
} |
| case-list |
:= |
case value : declaration ; case-list | |
|
|
case value : declaration ; | |
|
|
default : declaration ; |
| typedef-definition |
:= |
typedef declaration |
| program-definition |
:= |
program ident { version-list } = program-number |
| version-list |
:= |
version-decl ;| |
|
|
version-decl ; version-list |
| version-decl |
:= |
version ident { procedure-list } = version-number |
| procedure-list |
:= |
procedure ; | |
|
|
procedure ; procedure-list |
| procedure |
:= |
type ident ( type ) = procedure-number |
| declaration |
:= |
simple-declaration | |
|
|
fixed-array-declaration | |
|
|
variable-array-declaration | |
|
|
pointer-declaration |
| simple-declaration |
:= |
type ident |
| fixed-array-declaration |
:= |
type ident [ integer ] |
| variable-array-declaration |
:= |
type ident < integer > | |
|
|
type ident < > |
| pointer-declaration |
:= |
type * ident |
Notes:
-
union-definition is a descriminated union, meaning that there is
a tag that gives the type
-
program-definition describes just the interfaces, but none of the
code.
-
union-declaration must specify a value associated with each type.
-
program-number is assigned as follows:
-
0x00000000 - 0x1fffffff defined by SUN
-
0x20000000 - 0x3fffffff defined by user
-
0x40000000 - 0x5fffffff transient
-
0x60000000 - 0xffffffff reserved
Use a program number in the defined by user range (adding in last 4 digits
of your social security number).
-
ONC-RPC allows only a single parameter and a single result (these can each
be void).
-
The procedures defined by the IDL are obtained by converting the procedure
to lower case and appending "_" and the version number. This allows an
RPC definition to support multiple version of procedure calls.
-
procedure-number should start from 1 (0 is reserved for the ping
(or null) procedure which can be used to test if the server is alive).
-
variable-array-declaration specified either a maximum size (if there
is an integer between angle brackets) or does
not specify a maximum size.
-
pointer-declaration pointers across processes are not very useful.
Pointer types are used to copy over sparse structures such as linked lists
from server to client.
rpcgen
The RPC IDL (also called RPCL) needs to be compiled into C so that it can
be used in a program. Assume the IDL is stored inprog.x. Then:
rpcgen prog.x
Running rpcgen creates three files:
-
prog.h: which contains common information for the server and client.
-
prog_svc.c: C code to be linked with the server.
-
prog_cli.c: C code to be linked with the client.
Compiling on solaris
Then you can compile as follows:
gcc client.c prog_cli.c -o client -lnsl
gcc server.c prog_svc.c -o server -lnsl
client.c and server.c should include:
#include < rpc/rpc.h >
#include < netconfig.h >
#include "prog.h"
Calling remote procedures
Remote procedure proc, version v is called with the name
proc_v. The parameters and the return value for the procedure each
have an exra level of indirection. For example:
-
procedure p
-
version 2
-
declared as int p(char)
The procedure is a bit different on the client and server side:
-
On the client side it would be written in C as: int *p_2(char *,
CLIENT *)
where the CLIENT * argument specifies the target of the RPC.
-
On the server side it would be written in C as: int *p_2(char *)
In addtion, the value returned (if declared within the procedure) must
be declared static.
RPC data types in C program
Most data types are directly mappable into C. The exceptions include:
-
Variable size arrays
-
Unions
-
Pointers
Pointers cannot be easily used across process boundaries, so the primary
reason for using pointers is to copy over sparse structures (such as linked
lists or trees), between processes. ONC-RPC provides for pointer-chasing
to do this automatically, but this is beyond the scope of the notes. Both
of the remaining types, Variable size arrays and Uninos, are described
by structures.
Variable size arrays
Consider the variable size array vararray whose components
are of type int.
typedef int vararray<100>;
generates the structure:
typedef struct {
u_int vararray_len;
int *vararray_val;
} vararray;
Where the type vararray is a structure in C with two components,
a length field vararray_len and a pointer to the arrayvararray_val
which is of course type int.
Unions
Consider the RPC specification:
union retval switch (int desc) {
case 0: headline hl;
default: void;
};
This gets compiled into the following C structure.
struct retval {
int desc;
union {
headline hl;
} retval_u;
};
To use this structure, the C programmer must set both the descriminator
(desc) to 0 (or something outside the range of defined values),
and the union component to the appropriate value.
Using RPC
You need to write client.c and server.c code.
client
The client will need initialize the RPC
CLIENT *cl;
if (( cl = clnt_create(server, PROG_NUMBER, PROG_VERS, "tcp")) == NULL) {
/* error, could not open server */
}
and to close down the connection:
clnt_destroy(cl);
server
The server does not have a main (this is supplied by the stub). It need
only implement the remote procedures.