home *** CD-ROM | disk | FTP | other *** search
- Date: Tue, 2 Apr 85 23:44:55 pst
- From: decvax!sun!pumpkinseed!blyon (Bob Lyon)
- Subject: Sun RPC part 3 of 10
-
- echo x - rpc.prog
- sed 's/^X//' >rpc.prog <<'!Funky!Stuff!'
- X.OH 'RPC Programming''Page \\\\n(PN'
- X.EH 'Page \\\\n(PN''RPC Programming'
- X.OF 'Sun Microsystems''Release 2.0'
- X.EF 'Release 2.0''Sun Microsystems'
- X.RP
- X.rm DY
- X.TL
- X.ps 20
- Remote Procedure Call
- X.sp.5
- Programming Guide
- X.
- X.H 1 "Introduction"
- X.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
- X.L 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.
- X.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
- X.L 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
- X.L malloc() .
- X.LP
- At the middle layer, the routines
- X.L registerrpc()
- and
- X.L callrpc()
- are used to make RPC calls:
- X.L registerrpc()
- obtains a unique system-wide number, while
- X.L callrpc()
- executes a remote procedure call.
- The
- X.L 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.
- X.LP
- The lowest layer is used for more sophisticated applications,
- which may want to alter the defaults of the routines.
- At this layer, you can explicitly manipulate
- sockets used for transmitting RPC messages.
- This level should be avoided if possible.
- X.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.
- X.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.
- X.bp
- X.
- X.H 1 "Introductory Examples"
- X.H 2 "Highest Layer"
- X.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
- X.L rnusers() ,
- as illustrated below:
- X.LS
- #include <stdio.h>
- X.sp.5
- main(argc, argv)
- int argc;
- char **argv;
- {
- unsigned num;
- X.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);
- }
- X.LE
- RPC library routines such as
- X.L rnusers()
- are included in the C library
- X.L libc.a .
- Thus, the program above could be compiled with
- X.LS
- % cc \fIprogram.c\fP
- X.LE
- Some other library routines are
- X.L rstat()
- to gather remote performance statistics, and
- X.L ypmatch()
- to glean information from the yellow pages (YP).
- The YP library routines are documented on the manual page
- X.I ypclnt (3N).
- X.bp
- X.
- X.H 2 "Intermediate Layer"
- X.LP
- The simplest interface, which explicitly makes RPC
- calls, uses the functions
- X.L callrpc()
- and
- X.L registerrpc() .
- Using this method, another way to get the number of remote users is:
- X.LS
- #include <stdio.h>
- #include <rpcsvc/rusers.h>
- X.sp.5
- main(argc, argv)
- int argc;
- char **argv;
- {
- unsigned long nusers;
- X.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("number of users on %s is %d\en", argv[1], nusers);
- exit(0);
- }
- X.LE
- 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.
- X.LP
- The simplest routine in the RPC library
- used to make remote procedure calls is
- X.L 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,
- X.L callrpc()
- returns zero, but nonzero otherwise.
- The exact meaning of the return codes is found in
- X.L <rpc/clnt.h> ,
- and is in fact an
- X.L "enum clnt_stat"
- cast into an integer.
- X.LP
- Since data types may be represented differently on different machines,
- X.L callrpc()
- needs both the type of the RPC argument, as well as
- a pointer to the argument itself (and similarly for the result).
- For RUSERSPROC_NUM, the return value is an
- X.L "unsigned long" ,
- so
- X.L callrpc()
- has
- X.L xdr_u_long
- as its first return parameter, which says
- that the result is of type
- X.L "unsigned long",
- and
- X.L &nusers
- as its second return parameter,
- which is a pointer to where the long result will be placed.
- Since RUSERSPROC_NUM takes no argument, the argument parameter of
- X.L callrpc()
- is
- X.L xdr_void .
- X.LP
- After trying several times to deliver a message, if
- X.L 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:
- X.LS
- char *
- nuser(indata)
- char *indata;
- {
- static int nusers;
- X.sp.5
- /*
- * code here to compute the number of users
- * and place result in variable nusers
- */
- return ((char *)&nusers);
- }
- X.LE
- X.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
- X.L "char *" .
- X.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:
- X.LS
- #include <stdio.h>
- #include <rpcsvc/rusers.h>
- X.sp.5
- char *nuser();
- X.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);
- }
- X.LE
- X.LP
- The
- X.L registerrpc()
- routine establishes what C procedure
- corresponds to each RPC procedure number.
- The first three parameters,
- RUSERPROG, RUSERSVERS, and RUSERSPROC_NUM
- are the program, version, and procedure numbers
- of the remote procedure to be registered;
- X.L nuser
- is the name of the C procedure implementing it;
- and
- X.L xdr_void
- and
- X.L xdr_u_long
- are the types of the input to and output from the procedure.
- X.LP
- Only the UDP transport mechanism can use
- X.L registerrpc() ;
- thus, it is always safe in conjunction with calls generated by
- X.L callrpc() .
- X.LP
- Warning: the UDP transport mechanism can only deal with
- arguments and results less than 8K bytes in length.
- X.
- X.H 2 "Assigning Program Numbers"
- X.LP
- Program numbers are assigned in groups of 0x20000000 (536870912)
- according to the following chart:
- X.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
- X.LE
- 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.
- X.LP
- The exact registration process for Sun defined numbers is yet
- to be established.
- X.
- X.H 2 "Passing Arbitrary Data Types"
- X.LP
- In the previous example, the RPC call passes a single
- X.L "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
- X.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
- X.I serializing ,
- and the reverse process is called
- X.I deserializing .
- The type field parameters of
- X.L callrpc()
- and
- X.L registerrpc()
- can be a built-in procedure like
- X.L xdr_u_long()
- in the previous example, or a user supplied one.
- XXDR has these built-in type routines:
- X.LS
- xdr_int() xdr_u_int() xdr_enum()
- xdr_long() xdr_u_long() xdr_bool()
- xdr_short() xdr_u_short() xdr_string()
- X.LE
- As an example of a user-defined type routine,
- if you wanted to send the structure
- X.LS
- struct simple {
- int a;
- short b;
- } simple;
- X.LE
- then you would call
- X.L callrpc
- as
- X.LS
- callrpc(hostname, PROGNUM, VERSNUM, PROCNUM, xdr_simple, &simple ...);
- X.LE
- where
- X.L xdr_simple()
- is written as:
- X.LS
- #include <rpc/rpc.h>
- X.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);
- }
- X.LE
- X.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
- X.I "XDR Protocol Specification" ,
- so this section only gives a few examples of XDR implementation.
- X.LP
- In addition to the built-in primitives,
- there are also the prefabricated building blocks:
- X.LS
- xdr_array() xdr_bytes()
- xdr_reference() xdr_union()
- X.LE
- To send a variable array of integers,
- you might package them up as a structure like this
- X.LS
- struct varintarr {
- int *data;
- int arrlnth;
- } arr;
- X.LE
- and make an RPC call such as
- X.LS
- callrpc(hostname, PROGNUM, VERSNUM, PROCNUM, xdr_varintarr, &arr...);
- X.LE
- with
- X.L xdr_varintarr()
- defined as:
- X.LS
- xdr_varintarr(xdrsp, varintarr)
- XDR *xdrsp;
- struct varintarr *arrp;
- {
- xdr_array(xdrsp, &arrp->data, &arrp->arrlnth, MAXLEN,
- sizeof(int), xdr_int);
- }
- X.LE
- 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.
- X.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 SIZE:
- X.LS
- int intarr[SIZE];
- X.sp.5
- xdr_intarr(xdrsp, intarr)
- XDR *xdrsp;
- int intarr[];
- {
- int i;
- X.sp.5
- for (i = 0; i < SIZE; i++) {
- if (!xdr_int(xdrsp, &intarr[i]))
- return (0);
- }
- return (1);
- }
- X.LE
- X.LP
- XXDR 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
- X.L xdr_bytes() ,
- which is like
- X.L xdr_array()
- except that it packs characters.
- It has four parameters,
- the same as the first four parameters of
- X.L xdr_array() .
- For null-terminated strings, there is also the
- X.L xdr_string()
- routine, which is the same as
- X.L xdr_bytes()
- without the length parameter.
- On serializing it gets the string length from
- X.L strlen() ,
- and on deserializing it creates a null-terminated string.
- X.LP
- Here is a final example that calls the previously written
- X.L xdr_simple()
- as well as the built-in functions
- X.L xdr_string()
- and
- X.L xdr_reference() ,
- which chases pointers:
- X.LS
- struct finalexample {
- char *string;
- struct simple *simplep;
- } finalexample;
- X.LE
- X.LS
- xdr_finalexample(xdrsp, finalp)
- XDR *xdrsp;
- struct finalexample *finalp;
- {
- int i;
- X.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);
- }
- X.LE
- X.bp
- X.
- X.H 1 "Lower Layers of RPC"
- X.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
- X.I "The IPC Tutorial" .
- X.H 2 "More on the Server Side"
- X.LP
- There are a number of assumptions built into
- X.L registerrpc() .
- One is that you are using the UDP datagram protocol.
- Another is that
- you don't want to do anything unusual while deserializing,
- since the deserialization process happens automatically
- before the user's server routine is called.
- The server for the
- X.L nusers
- program shown below
- is written using a lower layer of the RPC package,
- which does not make these assumptions.
- X.LS
- #include <stdio.h>
- #include <rpc/rpc.h>
- #include <rpcsvc/rusers.h>
- X.sp.5
- int nuser();
- X.sp.5
- main()
- {
- SVCXPRT *transp;
- X.sp.5
- transp = svcudp_create(RPC_ANYSOCK);
- if (transp == NULL){
- fprintf(stderr, "couldn't create an RPC server\en");
- exit(1);
- }
- pmap_unset(RUSERSPROG, RUSERSVERS);
- if (!svc_register(transp, RUSERSPROG, RUSERSVERS, nuser,
- IPPROTO_UDP)) {
- fprintf(stderr, "couldn't register RUSER service\en");
- exit(1);
- }
- svc_run(); /* never returns */
- fprintf(stderr, "should never reach this point\en");
- }
- X.LE
- X.LS
- nuser(rqstp, tranp)
- struct svc_req *rqstp;
- SVCXPRT *transp;
- {
- unsigned long nusers;
- X.sp.5
- switch (rqstp->rq_proc) {
- case NULLPROC:
- if (!svc_sendreply(transp, xdr_void, 0)) {
- fprintf(stderr, "couldn'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, "couldn't reply to RPC call\en");
- exit(1);
- }
- return;
- default:
- svcerr_noproc(transp);
- return;
- }
- }
- X.LE
- X.LP
- First, the server gets a transport handle, which is used
- for sending out RPC messages.
- X.L registerrpc()
- uses
- X.L svcudp_create()
- to get a UDP handle.
- If you require a reliable protocol, call
- X.L svctcp_create()
- instead.
- If the argument to
- X.L svcudp_create()
- is RPC_ANYSOCK,
- the RPC library creates a socket
- on which to send out RPC calls.
- Otherwise,
- X.L 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
- X.L svcudp_create()
- and
- X.L clntudp_create()
- (the low-level client routine) must match.
- X.LP
- When the user specifies 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
- X.L svcudp_create()
- isn't already bound.
- When the
- X.L 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
- X.L <rpc/pmap_prot.h> .
- X.LP
- After creating an SVCXPRT, the next step is to call
- X.L pmap_unset()
- so that if the
- X.L nusers
- server crashed earlier,
- any previous trace of it is erased before restarting.
- More precisely,
- X.L pmap_unset()
- erases the entry for RUSERS from the port mapper's tables.
- X.LP
- Finally, we associate the program number for
- X.L nusers
- with the procedure
- X.L nuser() .
- The final argument to
- X.L svc_register()
- is normally the protocol being used,
- which, in this case, is IPPROTO_UDP.
- Notice that unlike
- X.L registerrpc() ,
- there are no XDR routines involved
- in the registration process.
- Also, registration is done on the program,
- rather than procedure, level.
- X.LP
- The user routine
- X.L nuser()
- must call and dispatch the appropriate XDR routines
- based on the procedure number.
- Note that
- two things are handled by
- X.L nuser()
- that
- X.L registerrpc()
- handles automatically.
- The first is that procedure 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,
- X.L svcerr_noproc()
- is called to handle the error.
- X.LP
- The user service routine serializes the results and returns
- them to the RPC caller via
- X.L svc_sendreply() .
- Its first parameter is the 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 RUSERSPROC_BOOL,
- which has an argument
- X.L nusers ,
- and returns TRUE or FALSE depending on
- whether there are nusers logged on.
- It would look like this:
- X.LS
- case RUSERSPROC_BOOL: {
- int bool;
- unsigned nuserquery;
- X.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, "couldn't reply to RPC call\en");
- exit(1);
- }
- return;
- }
- X.LE
- X.LP
- The relevant routine is
- X.L svc_getargs() ,
- which takes an SVCXPRT handle, the XDR routine,
- and a pointer to where the input is to be placed as arguments.
- X.H 2 "Memory Allocation with XDR"
- X.LP
- XXDR routines not only do input and output,
- they also do memory allocation.
- This is why the second parameter of
- X.L xdr_array()
- is a pointer to an array, rather than the array itself.
- If it is NULL, then
- X.L 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
- X.L xdr_chararr1() ,
- which deals with a fixed array of bytes with length SIZE:
- X.LS
- xdr_chararr1(xdrsp, chararr)
- XDR *xdrsp;
- char chararr[];
- {
- char *p;
- int len;
- X.sp.5
- p = chararr;
- len = SIZE;
- return (xdr_bytes(xdrsp, &p, &len, SIZE));
- }
- X.LE
- It might be called from a server like this,
- X.LS
- char chararr[SIZE];
- X.sp.5
- svc_getargs(transp, xdr_chararr1, chararr);
- X.LE
- where
- X.L chararr
- has already allocated space.
- If you want XDR to do the allocation,
- you would have to rewrite this routine in the following way:
- X.LS
- xdr_chararr2(xdrsp, chararrp)
- XDR *xdrsp;
- char **chararrp;
- {
- int len;
- X.sp.5
- len = SIZE;
- return (xdr_bytes(xdrsp, charrarrp, &len, SIZE));
- }
- X.LE
- Then the RPC call might look like this:
- X.LS
- char *arrptr;
- X.sp.5
- arrptr = NULL;
- svc_getargs(transp, xdr_chararr2, &arrptr);
- /*
- * use the result here
- */
- svc_freeargs(xdrsp, xdr_chararr2, &arrptr);
- X.LE
- After using the character array, it can be freed with
- X.L svc_freeargs() .
- In the routine
- X.L xdr_finalexample()
- given earlier, if
- X.L finalp->string
- was NULL in the call
- X.LS
- svc_getargs(transp, xdr_finalexample, &finalp);
- X.LE
- then
- X.LS
- svc_freeargs(xdrsp, xdr_finalexample, &finalp);
- X.LE
- frees the array allocated to hold
- X.L finalp->string ;
- otherwise, it frees nothing.
- The same is true for
- X.L finalp->simplep .
- X.LP
- To summarize, each XDR routine is responsible
- for serializing, deserializing, and allocating memory.
- When an XDR routine is called from
- X.L callrpc() ,
- the serializing part is used.
- When called from
- X.L svc_getargs() ,
- the deserializer is used.
- And when called from
- X.L 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.
- X.
- X.H 2 "The Calling Side"
- X.LP
- When you use
- X.L 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
- X.L nusers
- service:
- X.LS
- #include <stdio.h>
- #include <rpc/rpc.h>
- #include <rpcsvc/rusers.h>
- #include <sys/socket.h>
- #include <sys/time.h>
- #include <netdb.h>
- X.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;
- X.sp.5
- if (argc < 2) {
- fprintf(stderr, "usage: nusers hostname\en");
- exit(-1);
- }
- if ((hp = gethostbyname(argv[1])) == NULL) {
- fprintf(stderr, "cannot 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) {
- perror("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);
- }
- X.LE
- The low-level version of
- X.L callrpc()
- is
- X.L clnt_call() ,
- which takes a CLIENT pointer rather than a host name.
- The parameters to
- X.L clnt_call()
- are a 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.
- X.LP
- The CLIENT pointer is encoded with
- the transport mechanism.
- X.L callrpc()
- uses UDP, thus it calls
- X.L clntudp_create()
- to get a CLIENT pointer.
- To get TCP (Transport Control Protocol), you would use
- X.L clnttcp_create() .
- X.LP
- The parameters to
- X.L 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
- X.L clnt_call()
- is the total time to wait for a response.
- Thus, the number of tries is the
- X.L clnt_call()
- timeout divided by the
- X.L clntudp_create()
- timeout.
- X.LP
- There is one thing to note when using the
- X.L clnt_destroy()
- call.
- It deallocates any space associated with the CLIENT handle,
- but it does not close the socket associated with it,
- which was passed as an argument to
- X.L 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.
- X.LP
- To make a stream connection, the call to
- X.L clntudp_create()
- is replaced with a call to
- X.L clnttcp_create().
- X.LS
- clnttcp_create(&server_addr, prognum, versnum, &socket, inputsize,
- outputsize);
- X.LE
- There is no timeout argument; instead, the receive and send buffer
- sizes must be specified. When the
- X.L clnttcp_create()
- call is made, a TCP connection is established.
- All RPC calls using that CLIENT handle would use this connection.
- The server side of an RPC call using TCP has
- X.L svcudp_create()
- replaced by
- X.L svctcp_create() .
- X.bp
- X.
- X.H 1 "Other RPC Features"
- X.LP
- This section discusses some other aspects of RPC
- that are occasionally useful.
- X.
- X.H 2 "Select on the Server Side"
- X.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
- X.L svc_run() .
- But if the other activity
- involves waiting on a a file descriptor, the
- X.L svc_run()
- call won't work.
- The code for
- X.L svc_run()
- is as follows:
- X.LS
- void
- svc_run()
- {
- int readfds;
- X.sp.5
- for (;;) {
- readfds = svc_fds;
- switch (select(32, &readfds, NULL, NULL, NULL)) {
- X.sp.5
- case -1:
- if (errno == EINTR)
- continue;
- perror("rstat: select");
- return;
- case 0:
- break;
- default:
- svc_getreq(readfds);
- }
- }
- }
- X.LE
- X.LP
- You can bypass
- X.L svc_run()
- and call
- X.L 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
- X.L select()
- that waits on both the RPC socket,
- and your own descriptors.
- X.
- X.H 2 "Broadcast RPC"
- X.LP
- The
- X.L pmap
- and RPC protocols implement broadcast RPC.
- Here are the main differences between broadcast RPC
- and normal RPC calls:
- X.IP 1)
- Normal RPC expects one answer, whereas
- broadcast RPC expects many answers
- (one or more answer from each responding machine).
- X.IP 2)
- Broadcast RPC can only be supported by packet-oriented (connectionless)
- transport protocols like UPD/IP.
- X.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.
- X.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.
- X.
- X.H 3 "Broadcast RPC Synopsis"
- X.LP
- X.LS
- #include <rpc/pmap_clnt.h>
- X.sp.5
- enum clnt_stat clnt_stat;
- X.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 obtained */
- X.LE
- The procedure
- X.L eachresult()
- is called each time a valid result is obtained.
- It returns a boolean that indicates
- whether or not the client wants more responses.
- X.LS
- bool_t done;
- X.sp.5
- done =
- eachresult(resultsp, raddr)
- caddr_t resultsp;
- struct sockaddr_in *raddr; /* address of machine that sent response */
- X.LE
- If
- X.L done
- is TRUE, then broadcasting stops and
- X.L 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 RPC_TIMEDOUT.
- To interpret
- X.L clnt_stat
- errors, feed the error code to
- X.L clnt_perrno() .
- X.
- X.H 2 "Batching"
- X.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.
- X.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
- X.L 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.
- X.LP
- Since the batched calls are buffered,
- the client should eventually do a legitimate call
- in order to flush the pipeline.
- X.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:
- X.LS
- #include <stdio.h>
- #include <rpc/rpc.h>
- #include <rpcsvc/windows.h>
- X.sp.5
- void windowdispatch();
- X.sp.5
- main()
- {
- SVCXPRT *transp;
- X.sp.5
- transp = svctcp_create(RPC_ANYSOCK, 0, 0);
- if (transp == NULL){
- fprintf(stderr, "couldn't create an RPC server\en");
- exit(1);
- }
- pmap_unset(WINDOWPROG, WINDOWVERS);
- if (!svc_register(transp, WINDOWPROG, WINDOWVERS, windowdispatch,
- IPPROTO_TCP)) {
- fprintf(stderr, "couldn't register WINDOW service\en");
- exit(1);
- }
- svc_run(); /* never returns */
- fprintf(stderr, "should never reach this point\en");
- }
- X.LE
- X.LS no
- void
- windowdispatch(rqstp, transp)
- struct svc_req *rqstp;
- SVCXPRT *transp;
- {
- char *s = NULL;
- X.sp.5
- switch (rqstp->rq_proc) {
- case NULLPROC:
- if (!svc_sendreply(transp, xdr_void, 0)) {
- fprintf(stderr, "couldn't reply to RPC call\en");
- exit(1);
- }
- return;
- case RENDERSTRING:
- if (!svc_getargs(transp, xdr_wrapstring, &s)) {
- fprintf(stderr, "couldn't decode arguments\en");
- svcerr_decode(transp); /* tell caller he screwed up */
- break;
- }
- /*
- * call here to to render the string s
- */
- if (!svc_sendreply(transp, xdr_void, NULL)) {
- fprintf(stderr, "couldn't reply to RPC call\en");
- exit(1);
- }
- break;
- case RENDERSTRING_BATCHED:
- if (!svc_getargs(transp, xdr_wrapstring, &s)) {
- fprintf(stderr, "couldn't decode arguments\en");
- /*
- * we are silent in the face of protocol errors
- */
- break;
- }
- /*
- * call here to to render the string s,
- * but sends no reply!
- */
- break;
- default:
- svcerr_noproc(transp);
- return;
- }
- /*
- * now free string allocated while decoding arguments
- */
- svc_freeargs(transp, xdr_wrapstring, &s);
- }
- X.LE
- Of course the service could have one procedure
- that takes the string and a boolean
- to indicate whether or not the procedure should respond.
- X.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 (NULL), and
- 2) the RPC call's timeout must be zero.
- X.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:
- X.LS
- #include <stdio.h>
- #include <rpc/rpc.h>
- #include <rpcsvc/windows.h>
- #include <sys/socket.h>
- #include <sys/time.h>
- #include <netdb.h>
- X.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];
- char *s = buf;
- X.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);
- }
- X.LE
- 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.
- X.LP
- The above example was completed to render
- all of the (2000) lines in the file
- X.I /etc/termcap .
- The rendering service did nothing but to 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
- X.L fscanf()
- on
- X.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.
- X.
- X.H 2 "Authentication"
- X.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.
- X.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
- X.I none .
- X.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
- X.I unix
- type authentication, which besides
- X.I none
- is the only supported type.
- X.
- X.H 3 "The Client Side"
- X.LP
- When a caller creates a new RPC client handle as in:
- X.LS
- clnt = clntudp_create(address, prognum, versnum, wait, sockp)
- X.LE
- the appropriate transport instance defaults
- the associate authentication handle to be
- X.LS
- clnt->cl_auth = authnone_create();
- X.LE
- The RPC client can choose to use
- X.I unix
- style authentication by setting
- X.L clnt->cl_auth
- after creating the RPC client handle:
- X.LS
- clnt->cl_auth = authunix_create_default();
- X.LE
- This causes each RPC call associated with
- X.L clnt
- to carry with it the following authentication credentials structure:
- X.LS
- /*
- * Unix style credentials.
- */
- struct authunix_parms {
- u_long aup_time; /* credentials creation time */
- char *aup_machname; /* host name of where the client is calling */
- int aup_uid; /* client's UNIX effective uid */
- int aup_gid; /* client's current UNIX group id */
- u_int aup_len; /* the element length of aup_gids array */
- int *aup_gids; /* array of 4.2 groups to which user belongs */
- };
- X.LE
- These fields are set by
- X.L authunix_create_default()
- by invoking the appropriate system calls.
- X.LP
- Since the RPC user created this new style of authentication,
- he is responsible for destroying it with:
- X.LS
- auth_destroy(clnt->cl_auth);
- X.LE
- X.
- X.H 3 "The Server Side"
- X.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:
- X.LS
- /*
- * An RPC Service request
- */
- struct svc_req {
- u_long rq_prog; /* service program number */
- u_long rq_vers; /* service protocol version number*/
- u_long rq_proc; /* the desired procedure number*/
- struct opaque_auth rq_cred; /* raw credentials from the ``wire'' */
- caddr_t rq_clntcred; /* read only, cooked credentials */
- };
- X.LE
- The
- X.L rq_cred
- is mostly opaque, except for one field of interest:
- the style of authentication credentials:
- X.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 */
- };
- X.LE
- The RPC package guarantees the following
- to the service dispatch routine:
- X.IP 1)
- That the request's
- X.L rq_cred
- is well formed. Thus the service implementor may inspect the request's
- X.L 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
- X.L rq_cred
- if the style is not one of the styles supported by the RPC package.
- X.IP 2)
- That the request's
- X.L rq_clntcred
- field is either NULL or points to a well formed structure
- that corresponds to a supported style of authentication credentials.
- Remember that only
- X.I unix
- style is currently supported, so (currently)
- X.L rq_clntcred
- could be cast to a pointer to an
- X.L authunix_parms
- structure. If
- X.L rq_clntcred
- is NULL, the service implementor may wish to inspect
- the other (opaque) fileds of
- X.L rq_cred
- in case the service knows about a new type of authentication
- that the RPC package does not know about.
- X.LP
- Our remote users service example can be extended so that
- it computes results for all users except UID 16:
- X.LS
- nuser(rqstp, tranp)
- struct svc_req *rqstp;
- SVCXPRT *transp;
- {
- struct authunix_parms *unix_cred;
- int uid;
- unsigned long nusers;
- X.sp.5
- /*
- * we don't care about authentication for the null procedure
- */
- if (rqstp->rq_proc == NULLPROC) {
- if (!svc_sendreply(transp, xdr_void, 0)) {
- fprintf(stderr, "couldn'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 the caller is allow to call this procedure.
- */
- 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, "couldn't reply to RPC call\en");
- exit(1);
- }
- return;
- default:
- svcerr_noproc(transp);
- return;
- }
- }
- X.LE
- A few things should be noted here.
- First, it is customary not to check the authentication parameters
- associated with the NULLPROC (procedure number zero).
- Second, if the authentication parameter's type is not suitable
- for your service, you should call
- X.L 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
- X.L svcerr_systemerr()
- instead.
- X.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.
- X.
- X.H 2 "Using Inetd"
- X.LP
- An RPC server can be started from
- X.L inetd .
- The only difference
- from the usual code is that
- X.L svcudp_create()
- should be called as
- X.LS
- transp = svcudp_create(0);
- X.LE
- since
- X.L inet
- passes a socket as file descriptor 0.
- Also,
- X.L svc_register()
- should be called as
- X.LS
- svc_register(PROGNUM, VERSNUM, service, transp, 0);
- X.LE
- with the final flag as 0,
- since the program would already be registered by
- X.L inetd .
- Remember that if you want to exit
- from the server process and return control to
- X.L inet ,
- you need to explicitly exit, since
- X.L svc_run()
- never returns.
- X.LP
- The format of entries in /etc/servers for RPC services is
- X.LS
- rpc udp \fIserver \0program \0version\fP
- X.LE
- where
- X.I server
- is the C code implementing the server,
- and
- X.I program
- and
- X.I version
- are the program and version numbers of the service.
- The key word
- X.L udp
- can be replaced by
- X.L tcp
- for TCP-based RPC services.
- X.LP
- If the same program handles multiple versions,
- then the version number can be a range,
- as in this example:
- X.LS
- rpc udp /usr/etc/rstatd 100001 1-2
- X.LE
- X.bp
- X.
- X.H 1 "More Examples"
- X.H 2 "Versions"
- X.LP
- By convention, the first version number of program FOO is
- FOOVERS_ORIG and the most recent version is FOOVERS.
- Suppose there is a new version of the
- X.L user
- program that returns an
- X.L "unsigned short"
- rather than a
- X.L long .
- If we name this version
- RUSERSVERS_SHORT, then
- a server that wants to support both versions
- would do a double register.
- X.LS
- X.sp.5
- if (!svc_register(transp, RUSERSPROG, RUSERSVERS_ORIG, nuser,
- IPPROTO_TCP)) {
- fprintf(stderr, "couldn't register RUSER service\en");
- exit(1);
- }
- if (!svc_register(transp, RUSERSPROG, RUSERSVERS_SHORT, nuser,
- IPPROTO_TCP)) {
- fprintf(stderr, "couldn't register RUSER service\en");
- exit(1);
- }
- X.LE
- X.bp
- Both versions can be handled by the same C procedure:
- X.LS 0
- nuser(rqstp, tranp)
- struct svc_req *rqstp;
- SVCXPRT *transp;
- {
- unsigned long nusers;
- unsigned short nusers2
- X.sp.5
- switch (rqstp->rq_proc) {
- case NULLPROC:
- if (!svc_sendreply(transp, xdr_void, 0)) {
- fprintf(stderr, "couldn'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)
- if (!svc_sendreply(transp, xdr_u_long, &nusers) {
- fprintf(stderr, "couldn't reply to RPC call\en");
- exit(1);
- }
- else
- if (!svc_sendreply(transp, xdr_u_short, &nusers2) {
- fprintf(stderr, "couldn't reply to RPC call\en");
- exit(1);
- return;
- default:
- svcerr_noproc(transp);
- return;
- }
- }
- X.LE
- X.H 2 "TCP"
- X.LP
- Here is an example that is essentially
- X.L rcp .
- The initiator of the RPC
- X.L snd()
- call takes its standard input and sends it to the server
- X.L 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.
- X.LS 0
- /*
- * 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>
- X.sp.5
- xdr_rcp(xdrs, fp)
- XDR *xdrs;
- FILE *fp;
- {
- unsigned long size;
- char buf[MAXCHUNK], *p;
- X.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), MAXCHUNK, fp))
- == 0 && ferror(fp)) {
- fprintf(stderr, "couldn't fread\en");
- exit(1);
- }
- }
- p = buf;
- if (!xdr_bytes(xdrs, &p, &size, MAXCHUNK))
- return 0;
- if (size == 0)
- return 1;
- if (xdrs->x_op == XDR_DECODE) {
- if (fwrite(buf, sizeof(char), size, fp) != size) {
- fprintf(stderr, "couldn't fwrite\en");
- exit(1);
- }
- }
- }
- }
- X.LE
- X.LS 0
- /*
- * The sender routines
- */
- #include <stdio.h>
- #include <netdb.h>
- #include <rpc/rpc.h>
- #include <sys/socket.h>
- #include <sys/time.h>
- X.sp.5
- main(argc, argv)
- int argc;
- char **argv;
- {
- int err;
- X.sp.5
- if (argc < 2) {
- fprintf(stderr, "usage: %s server-name\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, " couldn't make RPC call\en");
- exit(1);
- }
- }
- X.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;
- X.sp.5
- if ((hp = gethostbyname(host)) == NULL) {
- fprintf(stderr, "cannot 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;
- }
- X.LE
- X.LS 0
- /*
- * The receiving routines
- */
- #include <stdio.h>
- #include <rpc/rpc.h>
- X.sp.5
- main()
- {
- register SVCXPRT *transp;
- X.sp.5
- if ((transp = svctcp_create(RPC_ANYSOCK, 1024, 1024)) == 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");
- }
- X.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;
- }
- }
- X.LE
- X.H 2 "Callback Procedures"
- X.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.
- X.LP
- In order to do an RPC callback,
- you need a program number to make the RPC call on.
- Since this will be a dynamically generated program number,
- it should be in the transient range, 0x40000000 - 0x5fffffff.
- The routine
- X.L gettransient()
- returns a valid program number in the transient range,
- and registers it with the portmapper.
- It only talks to the portmapper running on the same machine as the
- X.L gettransient()
- routine itself.
- The call to
- X.L pmap_set()
- is a test and set operation,
- in that it indivisibly tests whether a program number
- has already been registered,
- and if it has not, then reserves it.
- On return, the
- X.L sockp
- argument will contain a socket that can be used
- as the argument to an
- X.L svcudp_create()
- or
- X.L svctcp_create()
- call.
- X.LS
- #include <stdio.h>
- #include <rpc/rpc.h>
- #include <sys/socket.h>
- X.sp.5
- gettransient(proto, vers, sockp)
- int *sockp;
- {
- static int prognum = 0x40000000;
- int s, len, socktype;
- struct sockaddr_in addr;
- X.sp.5
- switch(proto) {
- case IPPROTO_UDP:
- socktype = SOCK_DGRAM;
- break;
- case IPPROTO_TCP:
- socktype = SOCK_STREAM;
- break;
- default:
- fprintf(stderr, "unknown protocol type\en");
- return 0;
- }
- if (*sockp == RPC_ANYSOCK) {
- if ((s = socket(AF_INET, socktype, 0)) < 0) {
- perror("socket");
- return (0);
- }
- *sockp = s;
- }
- else
- s = *sockp;
- addr.sin_addr.s_addr = 0;
- addr.sin_family = AF_INET;
- addr.sin_port = 0;
- len = sizeof(addr);
- /*
- * may be already bound, so don't check for err
- */
- bind(s, &addr, len);
- if (getsockname(s, &addr, &len)< 0) {
- perror("getsockname");
- return (0);
- }
- while (pmap_set(prognum++, vers, proto, addr.sin_port) == 0)
- continue;
- return (prognum-1);
- }
- X.LE
- The following pair of programs illustrate how to use the
- X.L gettransient()
- routine.
- The client makes an RPC call to the server,
- passing it a transient program number.
- Then the client waits around to receive a callback
- from the server at that program number.
- The server registers the program EXAMPELPROG,
- so that it can receive the RPC call
- informing it of the callback program number.
- Then at some random time (on receiving an ALRM signal in this example),
- it sends a callback RPC call,
- using the program number it received earlier.
- X.LS
- /*
- * client
- */
- #include <stdio.h>
- #include <rpc/rpc.h>
- X.sp.5
- int callback();
- char hostname[256];
- X.sp.5
- main(argc, argv)
- char **argv;
- {
- int x, ans, s;
- SVCXPRT *xprt;
- X.sp.5
- gethostname(hostname, sizeof(hostname));
- s = RPC_ANYSOCK;
- x = gettransient(IPPROTO_UDP, 1, &s);
- fprintf(stderr, "client gets prognum %d\en", x);
-
- if ((xprt = svcudp_create(s)) == NULL) {
- fprintf(stderr, "rpc_server: svcudp_create\en");
- exit(1);
- }
- (void)svc_register(xprt, x, 1, callback, 0);
-
- ans = callrpc(hostname, EXAMPLEPROG, EXAMPLEPROC_CALLBACK,
- EXAMPLEVERS, xdr_int, &x, xdr_void, 0);
- if (ans != 0) {
- fprintf(stderr, "call: ");
- clnt_perrno(ans);
- fprintf(stderr, "\en");
- }
- svc_run();
- fprintf(stderr, "Error: svc_run shouldn't have returned\en");
- }
- X.LE
- X.LS
- callback(rqstp, transp)
- register struct svc_req *rqstp;
- register SVCXPRT *transp;
- {
- switch (rqstp->rq_proc) {
- case 0:
- if (svc_sendreply(transp, xdr_void, 0) == FALSE) {
- fprintf(stderr, "err: rusersd\en");
- exit(1);
- }
- exit(0);
- case 1:
- if (!svc_getargs(transp, xdr_void, 0)) {
- svcerr_decode(transp);
- exit(1);
- }
- fprintf(stderr, "client got callback\en");
- if (svc_sendreply(transp, xdr_void, 0) == FALSE) {
- fprintf(stderr, "err: rusersd");
- exit(1);
- }
- }
- }
- X.LE
- X.LS
- /*
- * server
- */
- #include <stdio.h>
- #include <rpc/rpc.h>
- #include <sys/signal.h>
- X.sp.5
- char *getnewprog();
- char hostname[256];
- int docallback();
- int pnum; /*program number for callback routine */
- X.sp.5
- main(argc, argv)
- char **argv;
- {
- gethostname(hostname, sizeof(hostname));
- registerrpc(EXAMPLEPROG, EXAMPLEPROC_CALLBACK, EXAMPLEVERS,
- getnewprog, xdr_int, xdr_void);
- fprintf(stderr, "server going into svc_run\en");
- alarm(10);
- signal(SIGALRM, docallback);
- svc_run();
- fprintf(stderr, "Error: svc_run shouldn't have returned\en");
- }
- X.sp.5
- char *
- getnewprog(pnump)
- char *pnump;
- {
- pnum = *(int *)pnump;
- return NULL;
- }
- X.sp.5
- docallback()
- {
- int ans;
- X.sp.5
- ans = callrpc(hostname, pnum, 1, 1, xdr_void, 0, xdr_void, 0);
- if (ans != 0) {
- fprintf(stderr, "server: ");
- clnt_perrno(ans);
- fprintf(stderr, "\en");
- }
- }
- X.LE
- !Funky!Stuff!
-
- exit
-