CPS 214 Programming Project 4 - Fall 1997
Due: Friday, December 12 (midnight)
25 points
Remote Procedure Call
This assignment serves as an introduction to the communication
paradigm called remote procedure call (RPC). The idea
underlying RPC is to make applications completely unaware that they
are using a network by hiding the details of remote service invocation
behind the familiar procedure call programming construct. In
contrast, conventional distributed applications know that they use a
network, communicating with remote servers by explicitly sending
messages and waiting for responses. With RPC, an application invokes
a remote service by calling a local stub procedure using
normal subroutine calling conventions. All details about network
communication are hidden within the stub; the caller has no way of
knowing whether a procedure is executed locally or remotely.
RPC is especially well suited for client-server (e.g., query-response)
interaction. Conceptually, the client and server do not both execute
at the same time. Instead, the flow of control alternates between the
caller (e.g., client) and callee (e.g., server).
In this assignment, you will write client and server programs that
communicate using the Sun RPC library. The Sun RPC library defines an
RPC language and provides a compiler (called rpcgen) that
automatically generates source code for the client and server stub
routines from a programmer-provided specification of the remote
procedure. The input to rpcgen defines each remote procedure
together with the types and numbers of its parameter and return values.
Application Program
You are to write a program that maintains a simple database of names
and (attribute, value) pairs. All entries (names, attributes and
values) are stored as strings in the database. Your program should
support the following operations: (your program must implement
these procedures as described):
- int AddEntry(char *sbName, char *sbAttribute, char *sbValue)
- Add a (name, attribute, value) tuple to the database. Treat
adding a duplicate (name, attribute) as an error. AddEntry's
return value indicates success or failure.
- int DeleteEntry(char *sbName, char *sbAttribute)
- Remove the (name,
attribute) pair (together with its value) from the database.
Delete's return value indicates success or failure.
- char *FetchByNameAndAttribute(char *sbName, char *sbAttribute)
-
Retrieve the value associated with the specified (name, attribute)
pair.
- struct NameVal *FetchAllByAttribute(char *sbAttribute)
- Fetch the
list of (name, value) pairs giving all entries in which name has an
attribute sbAttribute. The idea is to return all (name, value) pairs
for which the sbAttribute attribute is defined.
- struct AttVal *FetchAllByName(char *sbName)]
- Return a
list of (attribute, value) pairs listing all attributes (and values)
associated with entry sbName.
You can manage your database using any data structure your choose.
For return values, you may again select any reasonable way of
returning a variable-sized list. For example, the following
definitions could be used for returning values via the above
Fetch operations:
struct NameVal { /* Name/attribute value pairs */
char *sbName; /* Entry having requested attribute */
char *sbValue; /* Entry's corresponding valued */
struct NameVal *pNameVal; /* Next pair in list */
};
struct AttVal { /* Attribute Value Pairs */
char *sbAttribute; /* Attribute name */
char *sbValue; /* Attribute's associated value */
struct AttVal *pAttVal; /* Next pair in list */
};
Your program reads commands from standard input, invokes the
appropriate database routine, and prints the corresponding result.
Your program should accept the following commands:
- add name attribute value
- Add the specified (name,
attribute, value) tuple to the database.
- delete name attribute
- Delete the specified (name,
attribute) pair (and its corresponding value) from
the database.
- fetch name attribute
- Fetch the value for the specified
(name, attribute) pair.
- fetchall name
- Fetch all (attribute, value) pairs
associated with entry name.
- attribute name
-
Fetch all (name, value) pairs for which
Attribute name exists.
Programming with RPCs
The application portion of this program was described above. The RPC
part of this program is to divide the above application into two
halves: a server program that provides access to the database through
RPCs, and a client that reads commands from standard input and invokes
the corresponding server routines. The server implements the routines
AddEntry, DeleteEntry, FetchByNameAndAttribute,
FetchAllByAttribute, and FetchAllByName.
Mapping Layer Routines
To migrate the application to an RPC environment, you should create
two sets of intermediate mapping layers (see
the Figure below). The client mapping
layer consists of routines that have the same interface as the
database routines in the server. For each remote procedure provided by the server,
the client mapping layer provides a corresponding routine having the
same name, the same numbers and the same types of arguments and return
values as the routines implemented on by the server. The application
calls the client mapping layer routines, which in turn call the
corresponding RPC stub routines used to actually invoke server
operations. The handling of error conditions related to RPC (e.g., no
server available) are handled in the client mapping layer.
The mapping layer routines reside between the application and RPC
stubs, so that the application layers need not know the details of the
RPC interface.
On the server, a server mapping layer maps
incoming RPC requests into the corresponding database operation. The
motivation for having client and server mapping layers is to allow the
client and server routines to be used with or without RPC without
requiring source code changes. (Sun's RPC library only allows RPC
routines to take a single argument; when more arguments are needed,
they must be put into a structure, and the single structure can then
be used as an argument.)
Using Rpcgen
The stub routines that handle the details of invoking remote
operations are generated by the rpcgen utility. Rpcgen
takes a description of the procedures and their arguments and
generates the client and server stub routines along with the routines
that convert arguments into a standard network representation for
transfer across the network.
A remote network service consists of a remote program (run
as one Unix process) that executes one or more related procedures.
All procedures of a remote program share global variables, and only a
single procedure executes at any one time (e.g., the effect is similar
to that of a monitor). A remote program is identified by a unique
program number that describes the service, and a unique
version number that refers to a particular implementation
of a program. Finally, each exported procedure is given its own
procedure number. Thus, a remote procedure is uniquely
identified by a <program number, version number, procedure
number> tuple. Consult the rpcgen
Programming Guide of Sun's Network Programming
documentation for details and sample applications.
Given the specification file db.x, rpcgen generates the
following files:
% ls
db.x
% rpcgen db.x
% ls
db.h db.x db_clnt.c db_svc.c db_xdr.c
The generated files contain the following:
- db.h:
- A header file containing C preprocessor
definitions for the interface routines, and remote program and version
numbers.
Note that the generated function definitions return a pointer to a
remote procedure call's return value, rather than the value itself.
Indeed, all parameters (and return values) of remote procedures are
by reference; you must pass a pointer to the
argument when making a call to the remote procedure. See
Sun's rpcgen Programming Guide for details.
The way Sun RPC handles arguments and return values illustrates the
motivation for a mapping layer. On the client side, for example, the
mapping layer takes the arguments (which are often passed by value),
converts them into the format expected by the stub routines, and then
calls the client stub routines to actually invoke the remote
operation.
- db_clnt.c:
- A file containing C code for the client stub
routines. By convention, rpcgen appends _clnt.c to the
basename of its input file, in this case producing db_clnt.c.
- db_svc.c:
-
A file containing C code for the server stub
routines. By convention, rpcgen appends _svc.c to the
basename of its input file, in this case producing db_svc.c.
The generated server stub routines include a definition for the
procedure main() and a dispatch routine that handles the
details of actually invoking the particular procedure invoked by the
client.
- db_xdr.c:
- A set of External Data Representation (XDR)
routines that convert the argument and return values between their
local machine representation and the network standard representation.
By convention, rpcgen appends _xdr.c to the basename of
its input file, in this case producing db_xdr.c.
Details
You are to write client and server programs as described above. You
should proceed in three steps:
- First, get the application program working without using
RPC. Tackling RPC right from the start complicates your task, but
doesn't shorten it. To make the later steps easier, isolate code into
two files: client.c deals with reading commands and invoking the
database operations, whereas the database code should be placed in a
separate file (e.g., database.c). (Note that database.c will
eventually be used by the sever, not the client.)
- Once the application works, add the client and server mapping
layers, but continue running both parts as one program (e.g., as one
process). This involves creating a description of the routines and
their arguments, and using rpcgen to generate the stub
routines. You can continue to run the client and server routines as
one program by linking the server and client stubs together in one
executable as follows (note that we have omitted the client and server
stub routines here):
% cc client.c database.c db_xdr.c -o dbclient
-
Once you are confident that both the client and server procedures are
working, separate the client and server modules and link them with
their respective stub routines to produce separate executables for the
client and server.
The server program can be run on any machine. Thus, the client
program should take a single command line argument specifying what
machine the remote server actually resides on. If no machine is
specified, use the host name ``localhost,'' which means the same
machine that the client is running on.
For the final version, your Makefile should create both the client and
server executables (e.g., in response to ``make'' or ``make all'').
Your client program should be called client, and your server
should be called server.
The application programmer selects the program, version, and procedure
numbers via the procedure specification given as input to
rpcgen. Remote program numbers are either pre-defined (for standard
system services) or chosen by a programmer (user-defined services).
Sun RPC allows a programmer to choose a program number from the range
0x20000000 - 0x40000000.
To distinguish the remote service provided by one student from that
provided by another, select program numbers as follows: a person
having student id number XXX-YY-ZZZZ and age AA would use a program
number of 0xAAYYZZZZ. For example, a student whose student id
is 911 23 4567 and who is 25 years old, would use program
number 0x25234567. (Those over 40 years old may lie about their
true age.)
Hints
Read Chapter 3 of Sun's Network Programming Guide . The
following manual pages may also be useful: clnt_create(3),
rpc_clnt_calls(3), and rpcgen.