Studyclientserver Using RPC
Studyclientserver Using RPC
What Is RPC
RPC is a powerful technique for constructing distributed, client-server based
applications. It is based on extending the notion of conventional, or local procedure
calling, so that the called procedure need not exist in the same address space as the
calling procedure. The two processes may be on the same system, or they may be
on different systems with a network connecting them. By using RPC, programmers
of distributed applications avoid the details of the interface with the network. The
transport independence of RPC isolates the application from the physical and
logical elements of the data communications mechanism and allows the application
to use a variety of transports.
RPC makes the client/server model of computing more powerful and easier to
program. When combined with the ONC RPCGEN protocol compiler (Chapter 33)
clients transparently make remote calls through a local procedure interface.
We use UNIX to run a remote shell and execute the command this way. There are
some problems with this method:
For the protocol you must identify the name of the service procedures, and data
types of parameters and return arguments.
The protocol compiler reads a definitio and automatically generates client and
server stubs.
rpcgen uses its own language (RPC language or RPCL) which looks very similar to
preprocessor directives.
rpcgen exists as a standalone executable compiler that reads special files denoted
by a .x prefix.
The external data representation (XDR) is an data abstraction needed for machine
independent communication. The client and server need not be machines of the
same type.
Defining Client and Server Application Code
We must now write the the client and application code. They must communicate
via procedures and data types specified in the Protocol.
The service side will have to register the procedures that may be called by the
client and receive and return any data required for processing.
The client application call the remote procedure pass any required data and will
receive the retruned data.
There are several levels of application interfaces that may be used to develop RPC
applications. We will briefly disuss these below before exapnading thhe most
common of these in later chapters.
Assume the the client program is called rpcprog.c, the service program
is rpcsvc.c and that the protocol has been defined in rpcprog.x and that rpcgen has
been used to produce the stub and filter files: rpcprog_clnt.c, rpcprog_svc.c,
rpcprog_xdr.c, rpcprog.h.
The simplified interfaces are used to make remote procedure calls to routines on
other machines, and specify only the type of transport to use. The routines at this
level are used for most applications. Descriptions and code samples can be found
in the section, Simplified Interface @ 3-2.
rpc_call() -- Remote calls the specified procedure on the specified remote host.
rpcb_set() -- Calls rpcbind to set a map between an RPC service and a network
address.
svc_reg() -- Associates the specified program and version number pair with the
specified dispatch routine.
Simplified Interface
The simplified interface is the easiest level to use because it does not require the
use of any other RPC routines. It also limits control of the underlying
communications mechanisms. Program development at this level can be rapid, and
is directly supported by the rpcgen compiler. For most applications, rpcgen and its
facilities are sufficient. Some RPC services are not available as C functions, but
they are available as RPC programs. The simplified interface library routines
provide direct access to the RPC facilities for programs that do not require fine
levels of control.
Routines such as rusers are in the RPC services library librpcsvc. rusers.c,
below, is a program that displays the number of users on a remote host. It calls the
RPC library routine, rusers.
/*
* a program that calls the
* rusers() service
*/
main(int argc,char **argv)
{
int num;
if (argc != 2) {
fprintf(stderr, "usage: %s hostname\n",
argv[0]);
exit(1);
}
There is just one function on the client side of the simplified interface rpc_call().
This function calls the procedure specified by prognum, versum, and procnum on the
host. The argument to be passed to the remote procedure is pointed to by
the in parameter, and inproc is the XDR filter to encode this argument.
The out parameter is an address where the result from the remote procedure is to
be placed. outproc is an XDR filter which will decode the result and place it at this
address.
The client blocks on rpc_call() until it receives a reply from the server. If the
server accepts, it returns RPC_SUCCESS with the value of zero. It will return a non-
zero value if the call was unsuccessful. This value can be cast to the
type clnt_stat, an enumerated type defined in the RPC include files (<rpc/rpc.h>)
and interpreted by the clnt_sperrno() function. This function returns a pointer to a
standard RPC error message corresponding to the error code. In the example, all
"visible" transports listed in /etc/netconfig are tried. Adjusting the number of
retries requires use of the lower levels of the RPC library. Multiple arguments and
results are handled by collecting them in structures.
{
unsigned long nusers;
enum clnt_stat cs;
if (argc != 2) {
fprintf(stderr, "usage: rusers hostname\n");
exit(1);
}
The server program using the simplified interface is very straightforward. It simply
calls rpc_reg() to register the procedure to be called, and then it calls svc_run(),
the RPC library's remote procedure dispatcher, to wait for requests to come in.
void *rusers();
main()
{
if(rpc_reg(RUSERSPROG, RUSERSVERS,
RUSERSPROC_NUM, rusers,
xdr_void, xdr_u_long,
"visible") == -1) {
fprintf(stderr, "Couldn't Register\n");
exit(1);
}
svc_run(); /* Never returns */
fprintf(stderr, "Error: svc_run returned!\n");
exit(1);
}
The nonprimitive xdr_string(), which takes more than two parameters, is called
from xdr_wrapstring().
{
if (!xdr_int(xdrsp, &simplep->a))
return (FALSE);
if (!xdr_short(xdrsp, &simplep->b))
return (FALSE);
return (TRUE);
}
An equivalent routine can be generated automatically by rpcgen (See Chapter 33).
For more complex data structures use the XDR prefabricated routines:
xdr_array() xdr_bytes() xdr_reference()
xdr_vector() xdr_union() xdr_pointer()
xdr_string() xdr_opaque()
{
return(xdr_array(xdrsp, (caddr_t)&arrp->data,
(u_int *)&arrp->arrlnth, MAXLEN, sizeof(int), xdr_int));
}
The arguments of xdr_array() are the XDR handle, a pointer to the array, a pointer
to the size of the array, the maximum array size, the size of each array element, and
a pointer to the XDR routine to translate each array element. If the size of the array
is known in advance, use xdr_vector() instread as is more efficient:
int intarr[SIZE];
Let us first consider how we would write a local directory reader. We have seem
how to do this already in Chapter 19.
Clearly we need to share the size between the files. Later when we develop
RPC versions more information will need to be added to this file.
We can can use simple NULL-terminated strings for passing and receivong the
directory name and directory contents. Furthermore, we can embed the passing of
these parameters directly in the client and server code.
We therefore need to specify the program, procedure and version numbers for
client and servers. This can be done automatically using rpcgen or relying on
prdefined macros in the simlified interface. Here we will specify them manually.
The server and client must agree ahead of time what logical adresses thney will
use (The physical addresses do not matter they are hidden from the application
developer)
We will simply choose a user deifnined value for our program number. The
version and procedure numbers are set according to standard practice.
We still have the DIR_SIZE definition required from the local version as the size of
the directory buffer is rewquired by bith client and server programs.
#include "rls.h"
We can use the original read_dir.c file. All we need to do is register the procedure
and start the server.
main()
{
extern bool_t xdr_dir();
extern char * read_dir();
svc_run();
}
At the client side we simply need to call the remote procedure. The
function callrpc() does this. It is prototyped as follows:
callrpc(char *host /* Name of server host */,
u_long prognum /* Server program number */,
u_long versnum /* Server version number */,
char *in /* Pointer to argument */,
xdrproc_t inproc /* XDR filter to encode arg */,
char *out /* Address to store result */
xdr_proc_t outproc /* Filter to decode result */);
We call a local function read_dir() which uses callrpc() to call the remote
procedure that has been registered READDIR at the server.
exit(0);
}
read_dir(host, dir)
char *dir, *host;
{
extern bool_t xdr_dir();
enum clnt_stat clnt_stat;