home *** CD-ROM | disk | FTP | other *** search
Text File | 1986-11-30 | 48.2 KB | 2,012 lines |
- .PL RIGHT
- .TL
- Remote Procedure Call
- .br
- Programming Guide
- .bp
- .NH
- Introduction
- .LP
- This document is intended for programmers
- who wish to write network applications
- using remote procedure calls (explained below),
- thus avoiding low-level system primitives based on sockets.
- The reader must be familiar with the C programming language,
- and should have a working knowledge of network theory.
- .LP
- Programs that communicate over a network
- need a paradigm for communication.
- A low-level mechanism might
- send a signal on the arrival of incoming packets,
- causing a network signal handler to execute.
- A high-level mechanism would be the Ada
- .LW rendezvous .
- The method used at Sun is the
- Remote Procedure Call (RPC) paradigm,
- in which a client communicates with a server.
- In this process,
- the client first calls a procedure to send a data packet to the server.
- When the packet arrives, the server calls a dispatch routine,
- performs whatever service is requested, sends back the reply,
- and the procedure call returns to the client.
- .NH 2
- Layers of RPC
- .LP
- The RPC interface is divided into three layers.
- The highest layer is totally transparent to the programmer.
- To illustrate,
- at this level a program can contain a call to
- .LW rnusers() ,
- which returns the number of users on a remote machine.
- You don't have to be aware that RPC is being used,
- since you simply make the call in a program,
- just as you would call
- .LW malloc() .
- .LP
- At the middle layer, the routines
- .LW registerrpc()
- and
- .LW callrpc()
- are used to make RPC calls:
- .LW registerrpc()
- obtains a unique system-wide number, while
- .LW callrpc()
- executes a remote procedure call.
- The
- .LW rnusers()
- call is implemented using these two routines.
- The middle-layer routines are designed for most common applications,
- and shield the user from knowing about sockets.
- .LP
- The lowest layer is for more sophisticated applications,
- such as altering the defaults of the routines.
- At this layer, you can explicitly manipulate
- sockets that transmit RPC messages.
- This level should be avoided if possible.
- .LP
- Section 2 of this manual illustrates use of the highest two layers
- while Section 3 presents the low-level interface.
- Section 4 of the manual discusses miscellaneous topics.
- The final section summarizes
- all the entry points into the RPC system.
- .LP
- Although this document only discusses the interface to C,
- remote procedure calls can be made from any language.
- Even though this document discusses RPC
- when it is used to communicate
- between processes on different machines,
- it works just as well for communication
- between different processes on the same machine.
- .NH 2
- The RPC Paradigm
- .LP
- Here is a diagram of the RPC paradigm:
- .LP
- .PL FULL
- .PS
- L1: arrow down 1i "client " rjust "program " rjust
- L2: line right 1.5i "\fLcallrpc()\fP" "function"
- move up 1.5i; line dotted down 6i; move up 4.5i
- arrow right 1i
- L3: arrow down 1i "execute " rjust "request " rjust
- L4: arrow right 1.5i "call" "service"
- L5: arrow down 1i " service" ljust " executes" ljust
- L6: arrow left 1.5i "\fLreturn\fP" "answer"
- L7: arrow down 1i "request " rjust "completed " rjust
- L8: line left 1i
- arrow left 1.5i "\fLreturn\fP" "reply"
- L9: arrow down 1i "program " rjust "continues " rjust
- line dashed down from L2 to L9
- line dashed down from L4 to L7
- line dashed up 1i from L3 "service " rjust "daemon " rjust
- arrow dashed down 1i from L8
- move right 1i from L3
- box invis "Machine B"
- move left 1.2i from L2; move down
- box invis "Machine A"
- .PE
- .FN "Network Communication with the Remote Procedure Call"
- .PL RIGHT
- .bp
- .NH
- Higher Layers of RPC
- .NH 2
- Highest Layer
- .LP
- Imagine you're writing a program that needs to know
- how many users are logged into a remote machine.
- You can do this by calling the library routine
- .LW rnusers() ,
- as illustrated below:
- .BS
- .LS
- #include <stdio.h>
- .sp.5
- main(argc, argv)
- int argc;
- char **argv;
- {
- unsigned num;
- .sp.5
- if (argc < 2) {
- fprintf(stderr, "usage: rnusers hostname\en");
- exit(1);
- }
- if ((num = rnusers(argv[1])) < 0) {
- fprintf(stderr, "error: rnusers\en");
- exit(-1);
- }
- printf("%d users on %s\en", num, argv[1]);
- exit(0);
- }
- .Lf
- .BE
- RPC library routines such as
- .LW rnusers()
- are in the RPC services library
- .LW librpcsvc.a .
- Thus, the program above should be compiled with
- .BS
- .LS
- % cc \fIprogram\fP.c -lrpcsvc
- .Lf
- .BE
- This routine, and other RPC library routines,
- are documented in section 3R of the
- .I "System Interface Manual for the Sun Workstation" .
- Here is a table of RPC service library routines
- available to the C programmer:
- .TN "RPC Service Library Routines"
- .TS
- box;
- cfBI s
- c c
- lfL l.
- .sp.5
- \s+2RPC Service Library Routines\s-2
- .sp.5
- _
- \fIroutine description\fP
- _
- rnusers() return number of users on remote machine
- rusers() return information about users on remote machine
- havedisk() determine if remote machine has disk
- rstat() get performance data from remote kernel
- rwall() write to specified remote machines
- getmaster() get name of YP master
- getrpcport() get RPC port number
- yppasswd() update user password in yellow pages
- .TE
- .LP
- The other RPC services \(em
- .LW ether ,
- .LW mount ,
- .LW rquota ,
- and
- .LW spray
- \(em are not available to the C programmer as library routines.
- They do, however,
- have RPC program numbers so they can be invoked with
- .LW callrpc() ,
- which will be discussed in the next section.
- .bp
- .NH 2
- Intermediate Layer
- .LP
- The simplest interface, which explicitly makes RPC
- calls, uses the functions
- .LW callrpc()
- and
- .LW registerrpc() .
- Using this method, another way to get the number of remote users is:
- .BS
- .LS
- #include <stdio.h>
- #include <rpcsvc/rusers.h>
- .sp.5
- main(argc, argv)
- int argc;
- char **argv;
- {
- unsigned long nusers;
- .sp.5
- if (argc < 2) {
- fprintf(stderr, "usage: nusers hostname\en");
- exit(-1);
- }
- if (callrpc(argv[1],
- RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM,
- xdr_void, 0, xdr_u_long, &nusers) != 0) {
- fprintf(stderr, "error: callrpc\en");
- exit(1);
- }
- printf("%d users on %s\en", nusers, argv[1]);
- exit(0);
- }
- .Lf
- .BE
- A program number, version number, and procedure number
- defines each RPC procedure.
- The program number defines a group
- of related remote procedures, each of which has a different
- procedure number.
- Each program also has a version number,
- so when a minor change is made to a remote service
- (adding a new procedure, for example),
- a new program number doesn't have to be assigned.
- When you want to call a procedure to
- find the number of remote users, you look up the appropriate
- program, version and procedure numbers
- in a manual, similar to when you look up the name of memory
- allocator when you want to allocate memory.
- .LP
- The simplest routine in the RPC library
- used to make remote procedure calls is
- .LW callrpc() .
- It has eight parameters.
- The first is the name of the remote machine.
- The next three parameters
- are the program, version, and procedure numbers.
- The following two parameters
- define the argument of the RPC call, and the final two parameters
- are for the return value of the call.
- If it completes successfully,
- .LW callrpc()
- returns zero, but nonzero otherwise.
- The exact meaning of the return codes is found in
- .LW <rpc/clnt.h> ,
- and is in fact an
- .LW "enum clnt_stat"
- cast into an integer.
- .LP
- Since data types may be represented differently on different machines,
- .LW callrpc()
- needs both the type of the RPC argument, as well as
- a pointer to the argument itself (and similarly for the result). For
- .LW RUSERSPROC_NUM ,
- the return value is an
- .LW "unsigned long" ,
- so
- .LW callrpc()
- has
- .LW xdr_u_long
- as its first return parameter, which says
- that the result is of type
- .LW "unsigned long" ,
- and
- .LW &nusers
- as its second return parameter,
- which is a pointer to where the long result will be placed. Since
- .LW RUSERSPROC_NUM
- takes no argument, the argument parameter of
- .LW callrpc()
- is
- .LW xdr_void .
- .LP
- After trying several times to deliver a message, if
- .LW callrpc()
- gets no answer, it returns with an error code.
- The delivery mechanism is UDP,
- which stands for User Datagram Protocol.
- Methods for adjusting the number of retries
- or for using a different protocol require you to use the lower
- layer of the RPC library, discussed later in this document.
- The remote server procedure
- corresponding to the above might look like this:
- .BS
- .LS
- char *
- nuser(indata)
- char *indata;
- {
- static int nusers;
- .sp.5
- /*
- * code here to compute the number of users
- * and place result in variable nusers
- */
- return((char *)&nusers);
- }
- .Lf
- .BE
- .LP
- It takes one argument, which is a pointer to the input
- of the remote procedure call (ignored in our example),
- and it returns a pointer to the result.
- In the current version of C,
- character pointers are the generic pointers,
- so both the input argument and the return value are cast to
- .LW "char *" .
- .LP
- Normally, a server registers all of the RPC calls it plans
- to handle, and then goes into an infinite loop waiting to service requests.
- In this example, there is only a single procedure
- to register, so the main body of the server would look like this:
- .BS
- .LS
- #include <stdio.h>
- #include <rpcsvc/rusers.h>
- .sp.5
- char *nuser();
- .sp.5
- main()
- {
- registerrpc(RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM,
- nuser, xdr_void, xdr_u_long);
- svc_run(); /* never returns */
- fprintf(stderr, "Error: svc_run returned!\en");
- exit(1);
- }
- .Lf
- .BE
- .LP
- The
- .LW registerrpc()
- routine establishes what C procedure
- corresponds to each RPC procedure number.
- The first three parameters,
- .LW RUSERPROG ,
- .LW RUSERSVERS ,
- and
- .LW RUSERSPROC_NUM
- are the program, version, and procedure numbers
- of the remote procedure to be registered;
- .LW nuser()
- is the name of the C procedure implementing it;
- and
- .LW xdr_void
- and
- .LW xdr_u_long
- are the types of the input to and output from the procedure.
- .LP
- Only the UDP transport mechanism can use
- .LW registerrpc() ;
- thus, it is always safe in conjunction with calls generated by
- .LW callrpc() .
- .LP
- Warning: the UDP transport mechanism can only deal with
- arguments and results less than 8K bytes in length.
- .NH 2
- Assigning Program Numbers
- .LP
- Program numbers are assigned in groups of 0x20000000 (536870912)
- according to the following chart:
- .BS
- .LS
- 0 - 1fffffff defined by sun
- 20000000 - 3fffffff defined by user
- 40000000 - 5fffffff transient
- 60000000 - 7fffffff reserved
- 80000000 - 9fffffff reserved
- a0000000 - bfffffff reserved
- c0000000 - dfffffff reserved
- e0000000 - ffffffff reserved
- .Lf
- .BE
- Sun Microsystems administers the first group of numbers,
- which should be identical for all Sun customers.
- If a customer develops an application that might be of general interest,
- that application should be given an assigned number in the first range.
- The second group of numbers is reserved for specific customer applications.
- This range is intended primarily for debugging new programs.
- The third group is reserved for applications that
- generate program numbers dynamically.
- The final groups are reserved for future use, and should not be used.
- .LP
- To register a protocol specification,
- send a request by network mail to
- .LW sun!rpc ,
- or write to:
- .DS
- RPC Administrator
- Sun Microsystems
- 2550 Garcia Ave.
- Mountain View, CA 94043
- .DE
- Please include a complete protocol specification,
- similar to those in this manual for NFS and YP.
- You will be given a unique program number in return.
- .NH 2
- Passing Arbitrary Data Types
- .LP
- In the previous example, the RPC call passes a single
- .LW "unsigned long" .
- RPC can handle arbitrary data structures, regardless of
- different machines' byte orders or structure layout conventions,
- by always converting them to a network standard called
- .I "eXternal Data Representation"
- (XDR) before
- sending them over the wire.
- The process of converting from a particular machine representation
- to XDR format is called
- .I serializing ,
- and the reverse process is called
- .I deserializing .
- The type field parameters of
- .LW callrpc()
- and
- .LW registerrpc()
- can be a built-in procedure like
- .LW xdr_u_long()
- in the previous example, or a user supplied one.
- XDR has these built-in type routines:
- .BS
- .LS
- xdr_int() xdr_u_int() xdr_enum()
- xdr_long() xdr_u_long() xdr_bool()
- xdr_short() xdr_u_short() xdr_string()
- .Lf
- .BE
- As an example of a user-defined type routine,
- if you wanted to send the structure
- .BS
- .LS
- struct simple {
- int a;
- short b;
- } simple;
- .Lf
- .BE
- then you would call
- .LW callrpc()
- as
- .BS
- .LS
- callrpc(hostname, PROGNUM, VERSNUM, PROCNUM,
- xdr_simple, &simple ...);
- .Lf
- .BE
- where
- .LW xdr_simple()
- is written as:
- .BS
- .LS
- #include <rpc/rpc.h>
- .sp.5
- xdr_simple(xdrsp, simplep)
- XDR *xdrsp;
- struct simple *simplep;
- {
- if (!xdr_int(xdrsp, &simplep->a))
- return (0);
- if (!xdr_short(xdrsp, &simplep->b))
- return (0);
- return (1);
- }
- .Lf
- .BE
- .LP
- An XDR routine returns nonzero (true in the sense of C)
- if it completes successfully, and zero otherwise.
- A complete description of XDR is in the
- .I "XDR Protocol Specification" ,
- so this section only gives a few examples of XDR implementation.
- .LP
- In addition to the built-in primitives,
- there are also the prefabricated building blocks:
- .BS
- .LS
- xdr_array() xdr_bytes()
- xdr_reference() xdr_union()
- .Lf
- .BE
- To send a variable array of integers,
- you might package them up as a structure like this
- .BS
- .LS
- struct varintarr {
- int *data;
- int arrlnth;
- } arr;
- .Lf
- .BE
- and make an RPC call such as
- .BS
- .LS
- callrpc(hostname, PROGNUM, VERSNUM, PROCNUM,
- xdr_varintarr, &arr...);
- .Lf
- .BE
- with
- .LW xdr_varintarr()
- defined as:
- .BS
- .LS
- xdr_varintarr(xdrsp, arrp)
- XDR *xdrsp;
- struct varintarr *arrp;
- {
- xdr_array(xdrsp, &arrp->data, &arrp->arrlnth, MAXLEN,
- sizeof(int), xdr_int);
- }
- .Lf
- .BE
- This routine takes as parameters the XDR handle,
- a pointer to the array, a pointer to the size of the array,
- the maximum allowable array size,
- the size of each array element,
- and an XDR routine for handling each array element.
- .LP
- If the size of the array is known in advance, then
- the following could also be used to send
- out an array of length
- .LW SIZE :
- .BS
- .LS
- int intarr[SIZE];
- .sp.5
- xdr_intarr(xdrsp, intarr)
- XDR *xdrsp;
- int intarr[];
- {
- int i;
- .sp.5
- for (i = 0; i < SIZE; i++) {
- if (!xdr_int(xdrsp, &intarr[i]))
- return (0);
- }
- return (1);
- }
- .Lf
- .BE
- .LP
- XDR always converts quantities to 4-byte multiples when deserializing.
- Thus, if either of the examples above involved characters
- instead of integers, each character would occupy 32 bits.
- That is the reason for the XDR routine
- .LW xdr_bytes() ,
- which is like
- .LW xdr_array()
- except that it packs characters;
- .LW xdr_bytes()
- has four parameters, similar to the first four parameters of
- .LW xdr_array() .
- For null-terminated strings, there is also the
- .LW xdr_string()
- routine, which is the same as
- .LW xdr_bytes()
- without the length parameter.
- On serializing it gets the string length from
- .LW strlen() ,
- and on deserializing it creates a null-terminated string.
- .LP
- Here is a final example that calls the previously written
- .LW xdr_simple()
- as well as the built-in functions
- .LW xdr_string()
- and
- .LW xdr_reference() ,
- which chases pointers:
- .BS
- .LS
- struct finalexample {
- char *string;
- struct simple *simplep;
- } finalexample;
- .sp.5
- xdr_finalexample(xdrsp, finalp)
- XDR *xdrsp;
- struct finalexample *finalp;
- {
- int i;
- .sp.5
- if (!xdr_string(xdrsp, &finalp->string, MAXSTRLEN))
- return (0);
- if (!xdr_reference(xdrsp, &finalp->simplep,
- sizeof(struct simple), xdr_simple);
- return (0);
- return (1);
- }
- .Lf
- .BE
- .bp
- .NH
- Lowest Layer of RPC
- .LP
- In the examples given so far,
- RPC takes care of many details automatically for you.
- In this section, we'll show you how you can change the defaults
- by using lower layers of the RPC library.
- It is assumed that you are familiar with sockets
- and the system calls for dealing with them.
- If not, consult the
- .I "IPC Primer" .
- .LP
- There are several occasions when you may need to use lower layers of RPC.
- First, you may need to use TCP.
- The higher layer uses UDP,
- which restricts RPC calls to 8K bytes of data.
- Using TCP permits calls to send long streams of data.
- For an example, see section 5.2 below.
- Second, you may want to allocate and free memory
- while serializing or deserializing with XDR routines.
- There is no call at the higher level to let you free memory explicitly.
- For more explanation, see section 3.2 below.
- Third, you may need to perform authentication
- on either the client or server side,
- by supplying credentials or verifying them.
- See the explanation in section 4.4 below.
- .NH 2
- More on the Server Side
- .LP
- The server for the
- .LW nusers
- program shown below does the same thing as the one using
- .LW registerrpc()
- above, but is written using a lower layer of the RPC package:
- .BS
- .LS no
- #include <stdio.h>
- #include <rpc/rpc.h>
- #include <rpcsvc/rusers.h>
- .sp.5
- main()
- {
- SVCXPRT *transp;
- int nuser();
- .sp.5
- transp = svcudp_create(RPC_ANYSOCK);
- if (transp == NULL){
- fprintf(stderr, "can't create an RPC server\en");
- exit(1);
- }
- pmap_unset(RUSERSPROG, RUSERSVERS);
- if (!svc_register(transp, RUSERSPROG, RUSERSVERS,
- nuser, IPPROTO_UDP)) {
- fprintf(stderr, "can't register RUSER service\en");
- exit(1);
- }
- svc_run(); /* never returns */
- fprintf(stderr, "should never reach this point\en");
- }
- .sp.5
- nuser(rqstp, tranp)
- struct svc_req *rqstp;
- SVCXPRT *transp;
- {
- unsigned long nusers;
- .sp.5
- switch (rqstp->rq_proc) {
- case NULLPROC:
- if (!svc_sendreply(transp, xdr_void, 0)) {
- fprintf(stderr, "can't reply to RPC call\en");
- exit(1);
- }
- return;
- case RUSERSPROC_NUM:
- /*
- * code here to compute the number of users
- * and put in variable nusers
- */
- if (!svc_sendreply(transp, xdr_u_long, &nusers) {
- fprintf(stderr, "can't reply to RPC call\en");
- exit(1);
- }
- return;
- default:
- svcerr_noproc(transp);
- return;
- }
- }
- .Lf
- .BE
- .LP
- First, the server gets a transport handle, which is used
- for sending out RPC messages.
- .LW registerrpc()
- uses
- .LW svcudp_create()
- to get a UDP handle.
- If you require a reliable protocol, call
- .LW svctcp_create()
- instead.
- If the argument to
- .LW svcudp_create()
- is
- .LW RPC_ANYSOCK ,
- the RPC library creates a socket
- on which to send out RPC calls.
- Otherwise,
- .LW svcudp_create()
- expects its argument to be a valid socket number.
- If you specify your own socket, it can be bound or unbound.
- If it is bound to a port by the user, the port numbers of
- .LW svcudp_create()
- and
- .LW clntudp_create()
- (the low-level client routine) must match.
- .LP
- When the user specifies
- .LW RPC_ANYSOCK
- for a socket or gives an unbound socket,
- the system determines port numbers in the following way:
- when a server starts up,
- it advertises to a port mapper demon on its local machine,
- which picks a port number for the RPC procedure
- if the socket specified to
- .LW svcudp_create()
- isn't already bound.
- When the
- .LW clntudp_create()
- call is made with an unbound socket,
- the system queries the port mapper on
- the machine to which the call is being made,
- and gets the appropriate port number.
- If the port mapper is not running
- or has no port corresponding to the RPC call,
- the RPC call fails.
- Users can make RPC calls
- to the port mapper themselves.
- The appropriate procedure
- numbers are in the include file
- .LW <rpc/pmap_prot.h> .
- .LP
- After creating an
- .LW SVCXPRT ,
- the next step is to call
- .LW pmap_unset()
- so that if the
- .LW nusers
- server crashed earlier,
- any previous trace of it is erased before restarting.
- More precisely,
- .LW pmap_unset()
- erases the entry for
- .LW RUSERSPROG
- from the port mapper's tables.
- .LP
- Finally, we associate the program number for
- .LW nusers
- with the procedure
- .LW nuser() .
- The final argument to
- .LW svc_register()
- is normally the protocol being used,
- which, in this case, is
- .LW IPPROTO_UDP .
- Notice that unlike
- .LW registerrpc() ,
- there are no XDR routines involved
- in the registration process.
- Also, registration is done on the program,
- rather than procedure, level.
- .LP
- The user routine
- .LW nuser()
- must call and dispatch the appropriate XDR routines
- based on the procedure number.
- Note that
- two things are handled by
- .LW nuser()
- that
- .LW registerrpc()
- handles automatically.
- The first is that procedure
- .LW NULLPROC
- (currently zero) returns with no arguments.
- This can be used as a simple test
- for detecting if a remote program is running.
- Second, there is a check for invalid procedure numbers.
- If one is detected,
- .LW svcerr_noproc()
- is called to handle the error.
- .LP
- The user service routine serializes the results and returns
- them to the RPC caller via
- .LW svc_sendreply() .
- Its first parameter is the
- .LW SVCXPRT
- handle, the second is the XDR routine,
- and the third is a pointer to the data to be returned.
- Not illustrated above is how a server
- handles an RPC program that passes data.
- As an example, we can add a procedure
- .LW RUSERSPROC_BOOL ,
- which has an argument
- .LW nusers ,
- and returns
- .LW TRUE
- or
- .LW FALSE
- depending on whether there are nusers logged on.
- It would look like this:
- .BS
- .LS
- case RUSERSPROC_BOOL: {
- int bool;
- unsigned nuserquery;
- .sp.5
- if (!svc_getargs(transp, xdr_u_int, &nuserquery) {
- svcerr_decode(transp);
- return;
- }
- /*
- * code to set nusers = number of users
- */
- if (nuserquery == nusers)
- bool = TRUE;
- else
- bool = FALSE;
- if (!svc_sendreply(transp, xdr_bool, &bool){
- fprintf(stderr, "can't reply to RPC call\en");
- exit(1);
- }
- return;
- }
- .Lf
- .BE
- .LP
- The relevant routine is
- .LW svc_getargs() ,
- which takes an
- .LW SVCXPRT
- handle, the XDR routine,
- and a pointer to where the input is to be placed as arguments.
- .NH 2
- Memory Allocation with XDR
- .LP
- XDR routines not only do input and output,
- they also do memory allocation.
- This is why the second parameter of
- .LW xdr_array()
- is a pointer to an array, rather than the array itself.
- If it is
- .LW NULL ,
- then
- .LW xdr_array()
- allocates space for the array and returns a pointer to it,
- putting the size of the array in the third argument.
- As an example, consider the following XDR routine
- .LW xdr_chararr1() ,
- which deals with a fixed array of bytes with length
- .LW SIZE :
- .BS
- .LS
- xdr_chararr1(xdrsp, chararr)
- XDR *xdrsp;
- char chararr[];
- {
- char *p;
- int len;
- .sp.5
- p = chararr;
- len = SIZE;
- return (xdr_bytes(xdrsp, &p, &len, SIZE));
- }
- .Lf
- .BE
- It might be called from a server like this,
- .BS
- .LS
- char chararr[SIZE];
- .sp.5
- svc_getargs(transp, xdr_chararr1, chararr);
- .Lf
- .BE
- where
- .LW chararr
- has already allocated space.
- If you want XDR to do the allocation,
- you would have to rewrite this routine in the following way:
- .BS
- .LS
- xdr_chararr2(xdrsp, chararrp)
- XDR *xdrsp;
- char **chararrp;
- {
- int len;
- .sp.5
- len = SIZE;
- return (xdr_bytes(xdrsp, charrarrp, &len, SIZE));
- }
- .Lf
- .BE
- Then the RPC call might look like this:
- .BS
- .LS
- char *arrptr;
- .sp.5
- arrptr = NULL;
- svc_getargs(transp, xdr_chararr2, &arrptr);
- /*
- * use the result here
- */
- svc_freeargs(transp, xdr_chararr2, &arrptr);
- .Lf
- .BE
- After using the character array, it can be freed with
- .LW svc_freeargs() .
- In the routine
- .LW xdr_finalexample()
- given earlier, if
- .LW finalp->string
- was
- .LW NULL
- in the call
- .BS
- .LS
- svc_getargs(transp, xdr_finalexample, &finalp);
- .Lf
- .BE
- then
- .BS
- .LS
- svc_freeargs(xdrsp, xdr_finalexample, &finalp);
- .Lf
- .BE
- frees the array allocated to hold
- .LW finalp->string ;
- otherwise, it frees nothing.
- The same is true for
- .LW finalp->simplep .
- .LP
- To summarize, each XDR routine is responsible
- for serializing, deserializing, and allocating memory.
- When an XDR routine is called from
- .LW callrpc() ,
- the serializing part is used.
- When called from
- .LW svc_getargs() ,
- the deserializer is used.
- And when called from
- .LW svc_freeargs() ,
- the memory deallocator is used.
- When building simple examples like those in this section,
- a user doesn't have to worry about the three modes.
- The XDR reference manual has examples of more
- sophisticated XDR routines that
- determine which of the three modes they are in
- to function correctly.
- .NH 2
- The Calling Side
- .LP
- When you use
- .LW callrpc() ,
- you have no control over the RPC delivery
- mechanism or the socket used to transport the data.
- To illustrate the layer of RPC that lets you adjust these
- parameters, consider the following code to call the
- .LW nusers
- service:
- .BS
- .LS no
- #include <stdio.h>
- #include <rpc/rpc.h>
- #include <rpcsvc/rusers.h>
- #include <sys/socket.h>
- #include <sys/time.h>
- #include <netdb.h>
- .sp.5
- main(argc, argv)
- int argc;
- char **argv;
- {
- struct hostent *hp;
- struct timeval pertry_timeout, total_timeout;
- struct sockaddr_in server_addr;
- int addrlen, sock = RPC_ANYSOCK;
- register CLIENT *client;
- enum clnt_stat clnt_stat;
- unsigned long nusers;
- .sp.5
- if (argc < 2) {
- fprintf(stderr, "usage: nusers hostname\en");
- exit(-1);
- }
- if ((hp = gethostbyname(argv[1])) == NULL) {
- fprintf(stderr, "can't get addr for %s\en",argv[1]);
- exit(-1);
- }
- pertry_timeout.tv_sec = 3;
- pertry_timeout.tv_usec = 0;
- addrlen = sizeof(struct sockaddr_in);
- bcopy(hp->h_addr, (caddr_t)&server_addr.sin_addr,
- hp->h_length);
- server_addr.sin_family = AF_INET;
- server_addr.sin_port = 0;
- if ((client = clntudp_create(&server_addr, RUSERSPROG,
- RUSERSVERS, pertry_timeout, &sock)) == NULL) {
- clnt_pcreateerror("clntudp_create");
- exit(-1);
- }
- total_timeout.tv_sec = 20;
- total_timeout.tv_usec = 0;
- clnt_stat = clnt_call(client, RUSERSPROC_NUM, xdr_void,
- 0, xdr_u_long, &nusers, total_timeout);
- if (clnt_stat != RPC_SUCCESS) {
- clnt_perror(client, "rpc");
- exit(-1);
- }
- clnt_destroy(client);
- }
- .Lf
- .BE
- The low-level version of
- .LW callrpc()
- is
- .LW clnt_call() ,
- which takes a
- .LW CLIENT
- pointer rather than a host name. The parameters to
- .LW clnt_call()
- are a
- .LW CLIENT
- pointer, the procedure number,
- the XDR routine for serializing the argument,
- a pointer to the argument,
- the XDR routine for deserializing the return value,
- a pointer to where the return value will be placed,
- and the time in seconds to wait for a reply.
- .LP
- The
- .LW CLIENT
- pointer is encoded with the transport mechanism.
- .LW callrpc()
- uses UDP, thus it calls
- .LW clntudp_create()
- to get a
- .LW CLIENT
- pointer. To get TCP (Transport Control Protocol), you would use
- .LW clnttcp_create() .
- .LP
- The parameters to
- .LW clntudp_create()
- are the server address, the length of the server address,
- the program number, the version number,
- a timeout value (between tries), and a pointer to a socket.
- The final argument to
- .LW clnt_call()
- is the total time to wait for a response.
- Thus, the number of tries is the
- .LW clnt_call()
- timeout divided by the
- .LW clntudp_create()
- timeout.
- .LP
- There is one thing to note when using the
- .LW clnt_destroy()
- call.
- It deallocates any space associated with the
- .LW CLIENT
- handle, but it does not close the socket associated with it,
- which was passed as an argument to
- .LW clntudp_create() .
- The reason is that if
- there are multiple client handles using the same socket,
- then it is possible to close one handle
- without destroying the socket that other handles
- are using.
- .LP
- To make a stream connection, the call to
- .LW clntudp_create()
- is replaced with a call to
- .LW clnttcp_create() .
- .BS
- .LS
- clnttcp_create(&server_addr, prognum, versnum, &socket,
- inputsize, outputsize);
- .Lf
- .BE
- There is no timeout argument; instead, the receive and send buffer
- sizes must be specified. When the
- .LW clnttcp_create()
- call is made, a TCP connection is established.
- All RPC calls using that
- .LW CLIENT
- handle would use this connection.
- The server side of an RPC call using TCP has
- .LW svcudp_create()
- replaced by
- .LW svctcp_create() .
- .bp
- .NH
- Other RPC Features
- .LP
- This section discusses some other aspects of RPC
- that are occasionally useful.
- .NH 2
- Select on the Server Side
- .LP
- Suppose a process is processing RPC requests
- while performing some other activity.
- If the other activity involves periodically updating a data structure,
- the process can set an alarm signal before calling
- .LW svc_run() .
- But if the other activity
- involves waiting on a a file descriptor, the
- .LW svc_run()
- call won't work.
- The code for
- .LW svc_run()
- is as follows:
- .BS
- .LS
- void
- svc_run()
- {
- int readfds;
- .sp.5
- for (;;) {
- readfds = svc_fds;
- switch (select(32, &readfds, NULL, NULL, NULL)) {
- .sp.5
- case -1:
- if (errno == EINTR)
- continue;
- perror("rstat: select");
- return;
- case 0:
- break;
- default:
- svc_getreq(readfds);
- }
- }
- }
- .Lf
- .BE
- .LP
- You can bypass
- .LW svc_run()
- and call
- .LW svc_getreq()
- yourself.
- All you need to know are the file descriptors
- of the socket(s) associated with the programs you are waiting on.
- Thus you can have your own
- .LW select()
- that waits on both the RPC socket,
- and your own descriptors.
- .NH 2
- Broadcast RPC
- .LP
- The
- .I portmapper
- is a daemon that converts RPC program numbers
- into DARPA protocol port numbers; see
- .LW portmap (8).
- You can't do broadcast RPC without the portmapper,
- .LW pmap ,
- in conjunction with standard RPC protocols.
- Here are the main differences between
- broadcast RPC and normal RPC calls:
- .IP 1.
- Normal RPC expects one answer, whereas
- broadcast RPC expects many answers
- (one or more answer from each responding machine).
- .IP 2.
- Broadcast RPC can only be supported by packet-oriented (connectionless)
- transport protocols like UPD/IP.
- .IP 3.
- The implementation of broadcast RPC
- treats all unsuccessful responses as garbage by filtering them out.
- Thus, if there is a version mismatch between the
- broadcaster and a remote service,
- the user of broadcast RPC never knows.
- .IP 4.
- All broadcast messages are sent to the portmap port.
- Thus, only services that register themselves with their portmapper
- are accessible via the broadcast RPC mechanism.
- .NH 3
- Broadcast RPC Synopsis
- .LP
- .BS
- .LS
- #include <rpc/pmap_clnt.h>
- .sp.5
- enum clnt_stat clnt_stat;
- .sp.5
- clnt_stat =
- clnt_broadcast(prog, vers, proc, xargs, argsp, xresults,
- resultsp, eachresult)
- u_long prog; /* program number */
- u_long vers; /* version number */
- u_long proc; /* procedure number */
- xdrproc_t xargs; /* xdr routine for args */
- caddr_t argsp; /* pointer to args */
- xdrproc_t xresults; /* xdr routine for results */
- caddr_t resultsp; /* pointer to results */
- bool_t (*eachresult)(); /* call with each result gotten */
- .Lf
- .BE
- The procedure
- .LW eachresult()
- is called each time a valid result is obtained.
- It returns a boolean that indicates
- whether or not the client wants more responses.
- .BS
- .LS
- bool_t done;
- .sp.5
- done =
- eachresult(resultsp, raddr)
- caddr_t resultsp;
- struct sockaddr_in *raddr; /* addr of responding machine */
- .Lf
- .BE
- If
- .LW done
- is
- .LW TRUE ,
- then broadcasting stops and
- .LW clnt_broadcast()
- returns successfully.
- Otherwise, the routine waits for another response.
- The request is rebroadcast
- after a few seconds of waiting.
- If no responses come back,
- the routine returns with
- .LW RPC_TIMEDOUT .
- To interpret
- .LW clnt_stat
- errors, feed the error code to
- .LW clnt_perrno() .
- .NH 2
- Batching
- .LP
- The RPC architecture is designed so that clients send a call message,
- and wait for servers to reply that the call succeeded.
- This implies that clients do not compute
- while servers are processing a call.
- This is inefficient if the client does not want or need
- an acknowledgement for every message sent.
- It is possible for clients to continue computing
- while waiting for a response,
- using RPC batch facilities.
- .LP
- RPC messages can be placed in a ``pipeline'' of calls
- to a desired server; this is called batching.
- Batching assumes that:
- 1) each RPC call in the pipeline requires no response from the server,
- and the server does not send a response message; and
- 2) the pipeline of calls is transported on a reliable
- byte stream transport such as TCP/IP.
- Since the server does not respond to every call,
- the client can generate new calls in parallel
- with the server executing previous calls.
- Furthermore, the TCP/IP implementation can buffer up
- many call messages, and send them to the server in one
- .LW write()
- system call. This overlapped execution
- greatly decreases the interprocess communication overhead of
- the client and server processes,
- and the total elapsed time of a series of calls.
- .LP
- Since the batched calls are buffered,
- the client should eventually do a legitimate call
- in order to flush the pipeline.
- .LP
- A contrived example of batching follows.
- Assume a string rendering service (like a window system)
- has two similar calls: one renders a string and returns void results,
- while the other renders a string and remains silent.
- The service (using the TCP/IP transport) may look like:
- .BS
- .LS no
- #include <stdio.h>
- #include <rpc/rpc.h>
- #include <rpcsvc/windows.h>
- .sp.5
- void windowdispatch();
- .sp.5
- main()
- {
- SVCXPRT *transp;
- .sp.5
- transp = svctcp_create(RPC_ANYSOCK, 0, 0);
- if (transp == NULL){
- fprintf(stderr, "can't create an RPC server\en");
- exit(1);
- }
- pmap_unset(WINDOWPROG, WINDOWVERS);
- if (!svc_register(transp, WINDOWPROG, WINDOWVERS,
- windowdispatch, IPPROTO_TCP)) {
- fprintf(stderr, "can't register WINDOW service\en");
- exit(1);
- }
- svc_run(); /* never returns */
- fprintf(stderr, "should never reach this point\en");
- }
- .sp.5
- void
- windowdispatch(rqstp, transp)
- struct svc_req *rqstp;
- SVCXPRT *transp;
- {
- char *s = NULL;
- .sp.5
- switch (rqstp->rq_proc) {
- case NULLPROC:
- if (!svc_sendreply(transp, xdr_void, 0)) {
- fprintf(stderr, "can't reply to RPC call\en");
- exit(1);
- }
- return;
- case RENDERSTRING:
- if (!svc_getargs(transp, xdr_wrapstring, &s)) {
- fprintf(stderr, "can't decode arguments\en");
- /*
- * tell caller he screwed up
- */
- svcerr_decode(transp);
- break;
- }
- /*
- * call here to render the string s
- */
- if (!svc_sendreply(transp, xdr_void, NULL)) {
- fprintf(stderr, "can't reply to RPC call\en");
- exit(1);
- }
- break;
- case RENDERSTRING_BATCHED:
- if (!svc_getargs(transp, xdr_wrapstring, &s)) {
- fprintf(stderr, "can't decode arguments\en");
- /*
- * we are silent in the face of protocol errors
- */
- break;
- }
- /*
- * call here to render string s, but send no reply!
- */
- break;
- default:
- svcerr_noproc(transp);
- return;
- }
- /*
- * now free string allocated while decoding arguments
- */
- svc_freeargs(transp, xdr_wrapstring, &s);
- }
- .Lf
- .BE
- Of course the service could have one procedure
- that takes the string and a boolean
- to indicate whether or not the procedure should respond.
- .LP
- In order for a client to take advantage of batching,
- the client must perform RPC calls on a TCP-based transport
- and the actual calls must have the following attributes:
- 1) the result's XDR routine must be zero
- .LW NULL ), (
- and 2) the RPC call's timeout must be zero.
- .LP
- Here is an example of a client that uses batching
- to render a bunch of strings;
- the batching is flushed when the client gets a null string:
- .BS
- .LS no
- #include <stdio.h>
- #include <rpc/rpc.h>
- #include <rpcsvc/windows.h>
- #include <sys/socket.h>
- #include <sys/time.h>
- #include <netdb.h>
- .sp.5
- main(argc, argv)
- int argc;
- char **argv;
- {
- struct hostent *hp;
- struct timeval pertry_timeout, total_timeout;
- struct sockaddr_in server_addr;
- int addrlen, sock = RPC_ANYSOCK;
- register CLIENT *client;
- enum clnt_stat clnt_stat;
- char buf[1000], *s = buf;
- .sp.5
- /* initial as in example 3.3
- */
- if ((client = clnttcp_create(&server_addr,
- WINDOWPROG, WINDOWVERS, &sock, 0, 0)) == NULL) {
- perror("clnttcp_create");
- exit(-1);
- }
- total_timeout.tv_sec = 0;
- total_timeout.tv_usec = 0;
- while (scanf("%s", s) != EOF) {
- clnt_stat = clnt_call(client, RENDERSTRING_BATCHED,
- xdr_wrapstring, &s, NULL, NULL, total_timeout);
- if (clnt_stat != RPC_SUCCESS) {
- clnt_perror(client, "batched rpc");
- exit(-1);
- }
- }
- /* now flush the pipeline
- */
- total_timeout.tv_sec = 20;
- clnt_stat = clnt_call(client, NULLPROC, xdr_void, NULL,
- xdr_void, NULL, total_timeout);
- if (clnt_stat != RPC_SUCCESS) {
- clnt_perror(client, "rpc");
- exit(-1);
- }
- clnt_destroy(client);
- }
- .Lf
- .BE
- Since the server sends no message,
- the clients cannot be notified of any of the failures that may occur.
- Therefore, clients are on their own when it comes to handling errors.
- .LP
- The above example was completed to render
- all of the (2000) lines in the file
- .I /etc/termcap .
- The rendering service did nothing but throw the lines away.
- The example was run in the following four configurations:
- 1) machine to itself, regular RPC;
- 2) machine to itself, batched RPC;
- 3) machine to another, regular RPC; and
- 4) machine to another, batched RPC.
- The results are as follows:
- 1) 50 seconds;
- 2) 16 seconds;
- 3) 52 seconds;
- 4) 10 seconds.
- Running
- .LW fscanf()
- on
- .I /etc/termcap
- only requires six seconds.
- These timings show the advantage of protocols
- that allow for overlapped execution,
- though these protocols are often hard to design.
- .NH 2
- Authentication
- .LP
- In the examples presented so far,
- the caller never identified itself to the server,
- and the server never required an ID from the caller.
- Clearly, some network services, such as a network filesystem,
- require stronger security than what has been presented so far.
- .LP
- In reality, every RPC call is authenticated by
- the RPC package on the server, and similarly,
- the RPC client package generates and sends authentication parameters.
- Just as different transports (TCP/IP or UDP/IP)
- can be used when creating RPC clients and servers,
- different forms of authentication can be associated with RPC clients;
- the default authentication type used as a default is type
- .I none .
- .LP
- The authentication subsystem of the RPC package is open ended.
- That is, numerous types of authentication are easy to support.
- However, this section deals only with
- .I unix
- type authentication, which besides
- .I none
- is the only supported type.
- .NH 3
- The Client Side
- .LP
- When a caller creates a new RPC client handle as in:
- .BS
- .LS
- clnt = clntudp_create(address, prognum, versnum,
- wait, sockp)
- .Lf
- .BE
- the appropriate transport instance defaults
- the associate authentication handle to be
- .BS
- .LS
- clnt->cl_auth = authnone_create();
- .Lf
- .BE
- The RPC client can choose to use
- .I unix
- style authentication by setting
- .LW clnt->cl_auth
- after creating the RPC client handle:
- .BS
- .LS
- clnt->cl_auth = authunix_create_default();
- .Lf
- .BE
- This causes each RPC call associated with
- .LW clnt
- to carry with it the following authentication credentials structure:
- .BS
- .LS
- /*
- * Unix style credentials.
- */
- struct authunix_parms {
- u_long aup_time; /* credentials creation time */
- char *aup_machname; /* host name where client is */
- int aup_uid; /* client's UNIX effective uid */
- int aup_gid; /* client's current group id */
- u_int aup_len; /* element length of aup_gids */
- int *aup_gids; /* array of groups user is in */
- };
- .Lf
- .BE
- These fields are set by
- .LW authunix_create_default()
- by invoking the appropriate system calls.
- Since the RPC user created this new style of authentication,
- the user is responsible for destroying it with:
- .BS
- .LS
- auth_destroy(clnt->cl_auth);
- .Lf
- .BE
- This should be done in all cases, to conserve memory.
- .NH 3
- The Server Side
- .LP
- Service implementors have a harder time dealing with authentication issues
- since the RPC package passes the service dispatch routine a request
- that has an arbitrary authentication style associated with it.
- Consider the fields of a request handle passed to a service dispatch routine:
- .BS
- .LS
- /*
- * An RPC Service request
- */
- struct svc_req {
- u_long rq_prog; /* service program number */
- u_long rq_vers; /* service protocol vers num */
- u_long rq_proc; /* desired procedure number */
- struct opaque_auth
- rq_cred; /* raw credentials from wire */
- caddr_t rq_clntcred; /* credentials (read only) */
- };
- .Lf
- .BE
- The
- .LW rq_cred
- is mostly opaque, except for one field of interest:
- the style of authentication credentials:
- .BS
- .LS
- /*
- * Authentication info. Mostly opaque to the programmer.
- */
- struct opaque_auth {
- enum_t oa_flavor; /* style of credentials */
- caddr_t oa_base; /* address of more auth stuff */
- u_int oa_length; /* not to exceed MAX_AUTH_BYTES */
- };
- .Lf
- .BE
- The RPC package guarantees the following
- to the service dispatch routine:
- .IP 1.
- That the request's
- .LW rq_cred
- is well formed. Thus the service implementor may inspect the request's
- .LW rq_cred.oa_flavor
- to determine which style of authentication the caller used.
- The service implementor may also wish to inspect the other fields of
- .LW rq_cred
- if the style is not one of the styles supported by the RPC package.
- .IP 2.
- That the request's
- .LW rq_clntcred
- field is either
- .LW NULL
- or points to a well formed structure
- that corresponds to a supported style of authentication credentials.
- Remember that only
- .I unix
- style is currently supported, so (currently)
- .LW rq_clntcred
- could be cast to a pointer to an
- .LW authunix_parms
- structure. If
- .LW rq_clntcred
- is
- .LW NULL ,
- the service implementor may wish to inspect the other (opaque) fileds of
- .LW rq_cred
- in case the service knows about a new type of authentication
- that the RPC package does not know about.
- .LP
- Our remote users service example can be extended so that
- it computes results for all users except UID 16:
- .BS
- .LS no
- nuser(rqstp, tranp)
- struct svc_req *rqstp;
- SVCXPRT *transp;
- {
- struct authunix_parms *unix_cred;
- int uid;
- unsigned long nusers;
- .sp.5
- /*
- * we don't care about authentication for null proc
- */
- if (rqstp->rq_proc == NULLPROC) {
- if (!svc_sendreply(transp, xdr_void, 0)) {
- fprintf(stderr, "can't reply to RPC call\en");
- exit(1);
- }
- return;
- }
- /*
- * now get the uid
- */
- switch (rqstp->rq_cred.oa_flavor) {
- case AUTH_UNIX:
- unix_cred = (struct authunix_parms *)rqstp->rq_clntcred;
- uid = unix_cred->aup_uid;
- break;
- case AUTH_NULL:
- default:
- svcerr_weakauth(transp);
- return;
- }
- switch (rqstp->rq_proc) {
- case RUSERSPROC_NUM:
- /*
- * make sure caller is allowed to call this proc
- */
- if (uid == 16) {
- svcerr_systemerr(transp);
- return;
- }
- /*
- * code here to compute the number of users
- * and put in variable nusers
- */
- if (!svc_sendreply(transp, xdr_u_long, &nusers) {
- fprintf(stderr, "can't reply to RPC call\en");
- exit(1);
- }
- return;
- default:
- svcerr_noproc(transp);
- return;
- }
- }
- .Lf
- .BE
- A few things should be noted here.
- First, it is customary not to check
- the authentication parameters associated with the
- .LW NULLPROC
- (procedure number zero).
- Second, if the authentication parameter's type is not suitable
- for your service, you should call
- .LW svcerr_weakauth() .
- And finally, the service protocol itself should return status
- for access denied; in the case of our example, the protocol
- does not have such a status, so we call the service primitive
- .LW svcerr_systemerr()
- instead.
- .LP
- The last point underscores the relation between
- the RPC authentication package and the services;
- RPC deals only with authentication and not with
- individual services' access control.
- The services themselves must implement their own access control policies
- and reflect these policies as return statuses in their protocols.
- .NH 2
- Using Inetd
- .LP
- An RPC server can be started from
- .LW inetd .
- The only difference
- from the usual code is that
- .LW svcudp_create()
- should be called as
- .BS
- .LS
- transp = svcudp_create(0);
- .Lf
- .BE
- since
- .LW inet
- passes a socket as file descriptor 0.
- Also,
- .LW svc_register()
- should be called as
- .BS
- .LS
- svc_register(transp, PROGNUM, VERSNUM, service, 0);
- .Lf
- .BE
- with the final flag as 0,
- since the program would already be registered by
- .LW inetd .
- Remember that if you want to exit
- from the server process and return control to
- .LW inet ,
- you need to explicitly exit, since
- .LW svc_run()
- never returns.
- .LP
- The format of entries in /etc/servers for RPC services is
- .BS
- .LS
- rpc udp \fIserver \0program \0version\fP
- .Lf
- .BE
- where
- .I server
- is the C code implementing the server,
- and
- .I program
- and
- .I version
- are the program and version numbers of the service.
- The key word
- .LW udp
- can be replaced by
- .LW tcp
- for TCP-based RPC services.
- .LP
- If the same program handles multiple versions,
- then the version number can be a range,
- as in this example:
- .BS
- .LS
- rpc udp /usr/etc/rstatd 100001 1-2
- .Lf
- .BE
- .bp
- .NH
- More Examples
- .NH 2
- Versions
- .LP
- By convention, the first version number of program
- .LW PROG
- is
- .LW PROGVERS_ORIG
- and the most recent version is
- .LW PROGVERS .
- Suppose there is a new version of the
- .LW user
- program that returns an
- .LW "unsigned short"
- rather than a
- .LW long .
- If we name this version
- .LW RUSERSVERS_SHORT ,
- then a server that wants to support both versions
- would do a double register.
- .BS
- .LS
- if (!svc_register(transp, RUSERSPROG, RUSERSVERS_ORIG,
- nuser, IPPROTO_TCP)) {
- fprintf(stderr, "can't register RUSER service\en");
- exit(1);
- }
- if (!svc_register(transp, RUSERSPROG, RUSERSVERS_SHORT,
- nuser, IPPROTO_TCP)) {
- fprintf(stderr, "can't register RUSER service\en");
- exit(1);
- }
- .Lf
- .BE
- Both versions can be handled by the same C procedure:
- .BS
- .LS no
- nuser(rqstp, tranp)
- struct svc_req *rqstp;
- SVCXPRT *transp;
- {
- unsigned long nusers;
- unsigned short nusers2
- .sp.5
- switch (rqstp->rq_proc) {
- case NULLPROC:
- if (!svc_sendreply(transp, xdr_void, 0)) {
- fprintf(stderr, "can't reply to RPC call\en");
- exit(1);
- }
- return;
- case RUSERSPROC_NUM:
- /*
- * code here to compute the number of users
- * and put in variable nusers
- */
- nusers2 = nusers;
- if (rqstp->rq_vers != RUSERSVERS_ORIG)
- return;
- if (!svc_sendreply(transp, xdr_u_long, &nusers) {
- fprintf(stderr, "can't reply to RPC call\en");
- exit(1);
- } else
- if (!svc_sendreply(transp, xdr_u_short, &nusers2) {
- fprintf(stderr, "can't reply to RPC call\en");
- exit(1);
- }
- return;
- default:
- svcerr_noproc(transp);
- return;
- }
- }
- .Lf
- .BE
- .NH 2
- TCP
- .LP
- Here is an example that is essentially
- .LW rcp .
- The initiator of the RPC
- .LW snd()
- call takes its standard input and sends it to the server
- .LW rcv() ,
- which prints it on standard output.
- The RPC call uses TCP.
- This also illustrates an XDR procedure that behaves differently
- on serialization than on deserialization.
- .BS
- .LS no
- /*
- * The xdr routine:
- * on decode, read from wire, write onto fp
- * on encode, read from fp, write onto wire
- */
- #include <stdio.h>
- #include <rpc/rpc.h>
- .sp.5
- xdr_rcp(xdrs, fp)
- XDR *xdrs;
- FILE *fp;
- {
- unsigned long size;
- char buf[BUFSIZ], *p;
- .sp.5
- if (xdrs->x_op == XDR_FREE)/* nothing to free */
- return 1;
- while (1) {
- if (xdrs->x_op == XDR_ENCODE) {
- if ((size = fread(buf, sizeof(char), BUFSIZ,
- fp)) == 0 && ferror(fp)) {
- fprintf(stderr, "can't fread\en");
- exit(1);
- }
- }
- p = buf;
- if (!xdr_bytes(xdrs, &p, &size, BUFSIZ))
- return 0;
- if (size == 0)
- return 1;
- if (xdrs->x_op == XDR_DECODE) {
- if (fwrite(buf, sizeof(char), size,
- fp) != size) {
- fprintf(stderr, "can't fwrite\en");
- exit(1);
- }
- }
- }
- }
- .sp.5
- /*
- * The sender routines
- */
- #include <stdio.h>
- #include <netdb.h>
- #include <rpc/rpc.h>
- #include <sys/socket.h>
- #include <sys/time.h>
- .sp.5
- main(argc, argv)
- int argc;
- char **argv;
- {
- int err;
- .sp.5
- if (argc < 2) {
- fprintf(stderr, "usage: %s servername\en", argv[0]);
- exit(-1);
- }
- if ((err = callrpctcp(argv[1], RCPPROG, RCPPROC_FP,
- RCPVERS, xdr_rcp, stdin, xdr_void, 0) != 0)) {
- clnt_perrno(err);
- fprintf(stderr, "can't make RPC call\en");
- exit(1);
- }
- }
- .sp.5
- callrpctcp(host, prognum, procnum, versnum,
- inproc, in, outproc, out)
- char *host, *in, *out;
- xdrproc_t inproc, outproc;
- {
- struct sockaddr_in server_addr;
- int socket = RPC_ANYSOCK;
- enum clnt_stat clnt_stat;
- struct hostent *hp;
- register CLIENT *client;
- struct timeval total_timeout;
- .sp.5
- if ((hp = gethostbyname(host)) == NULL) {
- fprintf(stderr, "can't get addr for '%s'\en", host);
- exit(-1);
- }
- bcopy(hp->h_addr, (caddr_t)&server_addr.sin_addr,
- hp->h_length);
- server_addr.sin_family = AF_INET;
- server_addr.sin_port = 0;
- if ((client = clnttcp_create(&server_addr, prognum,
- versnum, &socket, BUFSIZ, BUFSIZ)) == NULL) {
- perror("rpctcp_create");
- exit(-1);
- }
- total_timeout.tv_sec = 20;
- total_timeout.tv_usec = 0;
- clnt_stat = clnt_call(client, procnum,
- inproc, in, outproc, out, total_timeout);
- clnt_destroy(client)
- return (int)clnt_stat;
- }
- .sp.5
- /*
- * The receiving routines
- */
- #include <stdio.h>
- #include <rpc/rpc.h>
- .sp.5
- main()
- {
- register SVCXPRT *transp;
- .sp.5
- if ((transp = svctcp_create(RPC_ANYSOCK,
- BUFSIZ, BUFSIZ)) == NULL) {
- fprintf("svctcp_create: error\en");
- exit(1);
- }
- pmap_unset(RCPPROG, RCPVERS);
- if (!svc_register(transp,
- RCPPROG, RCPVERS, rcp_service, IPPROTO_TCP)) {
- fprintf(stderr, "svc_register: error\en");
- exit(1);
- }
- svc_run(); /* never returns */
- fprintf(stderr, "svc_run should never return\en");
- }
- .sp.5
- rcp_service(rqstp, transp)
- register struct svc_req *rqstp;
- register SVCXPRT *transp;
- {
- switch (rqstp->rq_proc) {
- case NULLPROC:
- if (svc_sendreply(transp, xdr_void, 0) == 0) {
- fprintf(stderr, "err: rcp_service");
- exit(1);
- }
- return;
- case RCPPROC_FP:
- if (!svc_getargs(transp, xdr_rcp, stdout)) {
- svcerr_decode(transp);
- return;
- }
- if (!svc_sendreply(transp, xdr_void, 0)) {
- fprintf(stderr, "can't reply\en");
- return;
- }
- exit(0);
- default:
- svcerr_noproc(transp);
- return;
- }
- }
- .Lf
- .BE
- .NH 2
- Callback Procedures
- .LP
- Occasionally, it is useful to have a server become a client,
- and make an RPC call back the process which is its client.
- An example is remote debugging,
- where the client is a window system program,
- and the server is a debugger running on the remote machine.
- Most of the time,
- the user clicks a mouse button at the debugging window,
- which converts this to a debugger command,
- and then makes an RPC call to the server
- (where the debugger is actually running),
- telling it to execute that command.
- However, when the debugger hits a breakpoint, the roles are reversed,
- and the debugger wants to make an rpc call to the window program,
- so that it can inform the user that a breakpoint has been reached.
-