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:
  1. 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.)
  2. 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
  3. 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.