Skip Navigation Links | |
Exit Print View | |
ONC+ Developer's Guide Oracle Solaris 11.1 Information Library |
1. Introduction to ONC+ Technologies
4. Programmer's Interface to RPC
Client Side of Simplified Interface
Server Side of the Simplified Interface
Client Side of the Top-Level Interface
Client Side of the Intermediate-Level Interface
Server Side of the Intermediate-Level Interface
Client Side of the Expert-Level Interface
Server Side of the Expert-Level Interface
Client Side of the Bottom-Level Interface
Server Side of the Bottom-Level Interface
Testing Programs Using Low-Level Raw RPC
Connection-Oriented Transports
5. Advanced RPC Programming Techniques
6. Porting From TS-RPC to TI-RPC
7. Multithreaded RPC Programming
8. Extensions to the Oracle Solaris RPC Library
The simplified interface is the level to use if you do not require the use of any other RPC routines. This level also limits control of the underlying communications mechanisms. You can rapidly develop a program at this level, and the development is directly supported by the rpcgen compiler. For most applications, rpcgen and its facilities are sufficient.
Some RPC services are not available as C functions, but they are available as RPC programs. The simplified interface library routines provide direct access to the RPC facilities for programs that do not require fine levels of control. Routines such as rusers() are in the RPC services library librpcsvc. The following code example is a program that displays the number of users on a remote host. It calls the RPC library routine rusers().
Example 4-1 rusers Program
#include <rpc/rpc.h> #include <rpcsvc/rusers.h> #include <stdio.h> /* * a program that calls the * rusers() service */ main(argc, argv) int argc; char **argv; { int num; if (argc != 2) { fprintf(stderr, "usage: %s hostname\n", argv[0]); exit(1); } if ((num = rnusers(argv[1])) < 0) { fprintf(stderr, "error: rusers\n"); exit(1); } fprintf(stderr, "%d users on %s\n", num, argv[1] ); exit(0); }
Compile the program in Example 4-1 by typing:
cc program.c -lrpcsvc -lnsl
Just one function exists on the client side of the simplified interface: rpc_call(). It has nine parameters:
int 0 or error code rpc_call ( char *host /* Name of server host */ rpcprog_t prognum /* Server program number */ rpcvers_t versnum /* Server version number */ rpcproc_t procnum /* Server procedure number */ xdrproc_t inproc /* XDR filter to encode arg */ char *in /* Pointer to argument */ xdr_proc_t outproc /* Filter to decode result */ char *out /* Address to store result */ char *nettype /*For transport selection */ );
The rpc_call() function calls the procedure specified by prognum, versum, and procnum on the host. The argument to be passed to the remote procedure is pointed to by the in parameter, and inproc is the XDR filter to encode this argument. The out parameter is an address where the result from the remote procedure is to be placed. outproc is an XDR filter that decodes the result and places it at this address.
The client blocks on rpc_call() until it receives a reply from the server. If the server accepts, it returns RPC_SUCCESS with the value of zero. The server returns a non-zero value if the call was unsuccessful. You can cast this value to the type clnt_stat, an enumerated type defined in the RPC include files and interpreted by the clnt_sperrno() function. This function returns a pointer to a standard RPC error message corresponding to the error code.
In the example, all “visible” transports listed in /etc/netconfig are tried. Adjusting the number of retries requires use of the lower levels of the RPC library.
Multiple arguments and results are handled by collecting them in structures.
The following code example changes the code in Example 4-1 to use the simplified interface.
Example 4-2 rusers Program Using Simplified Interface
#include <stdio.h> #include <utmpx.h> #include <rpc/rpc.h> #include <rpcsvc/rusers.h> /* A program that calls the RUSERSPROG RPC program */ main(argc, argv) int argc; char **argv; { unsigned int nusers; enum clnt_stat cs; if (argc != 2) { fprintf(stderr, "usage: rusers hostname\n"); exit(1); } if( cs = rpc_call(argv[1], RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, xdr_void, (char *)0, xdr_u_int, (char *)&nusers, "visible") != RPC_SUCCESS ) { clnt_perrno(cs); exit(1); } fprintf(stderr, "%d users on %s\n", nusers, argv[1] ); exit(0); }
Data types can be represented differently on different machines. Therefore, rpc_call() needs both the type of the RPC argument and a pointer to it. rpc_call() also needs this information for the result. For RUSERSPROC_NUM, the return value is an unsigned int, so the first return parameter of rpc_call() is xdr_u_int, which is for an unsigned int, and the second return parameter is &nusers, which points to unsigned int storage. Because RUSERSPROC_NUM has no argument, the XDR encoding function of rpc_call() is xdr_void() and its argument is NULL.
The server program using the simplified interface is straightforward. The server calls rpc_reg() to register the procedure to be called. It then calls svc_run(), the RPC library's remote procedure dispatcher, to wait for requests to arrive.
rpc_reg() has the following arguments:
rpc_reg ( rpcprog_t prognum /* Server program number */ rpcvers_t versnum /* Server version number */ rpcproc_t procnum /* server procedure number */ char *procname /* Name of remote function */ xdrproc_t inproc /* Filter to encode arg */ xdrproc_t outproc /* Filter to decode result */ char *nettype /* For transport selection */ );
svc_run() invokes service procedures in response to RPC call messages. The dispatcher in rpc_reg() decodes remote procedure arguments and encodes results, using the XDR filters specified when the remote procedure was registered. Some notes about the server program include:
Most RPC applications follow the naming convention of appending a _1 to the function name. The sequence _n is added to the procedure names to indicate the version number n of the service.
The argument and result are passed as addresses. This is true for all functions that are called remotely. Passing NULL as a result of a function means that no reply is sent to the client, because NULL indicates that there is no reply to send.
The result must exist in static data space because its value is accessed after the actual procedure has exited. The RPC library function that builds the RPC reply message accesses the result and sends the value back to the client.
Only a single argument is allowed. If there are multiple elements of data, they should be wrapped inside a structure that can then be passed as a single entity.
The procedure is registered for each transport of the specified type. If the type parameter is (char *)NULL, the procedure is registered for all transports specified in NETPATH.
You can sometimes implement faster or more compact code than can rpcgen. rpcgen handles the generic code-generation cases. The following program is an example of a hand-coded registration routine. It registers a single procedure and enters svc_run() to service requests.
#include <stdio.h> #include <rpc/rpc.h> #include <rpcsvc/rusers.h> void *rusers(); main() { if(rpc_reg(RUSERSPROG, RUSERSVERS, RUSERSPROC_NUM, rusers, xdr_void, xdr_u_int, "visible") == -1) { fprintf(stderr, "Couldn't Register\n"); exit(1); } svc_run(); /* Never returns */ fprintf(stderr, "Error: svc_run returned!\n"); exit(1); }
rpc_reg() can be called as many times as is needed to register different programs, versions, and procedures.
Data types passed to and received from remote procedures can be any of a set of predefined types, or can be programmer-defined types. RPC handles arbitrary data structures, regardless of the byte orders or structure layout conventions of different machines. RPC always converts these structures to a standard transfer format called external data representation (XDR) before sending them over the transport. The conversion from a machine representation to XDR is called serializing, and the reverse process is called deserializing.
The translator arguments of rpc_call() and rpc_reg() can specify an XDR primitive procedure, like xdr_u_int(), or a programmer-supplied routine that processes a complete argument structure. Argument processing routines must take only two arguments: a pointer to the result and a pointer to the XDR handle.
The XDR Primitive Type Routines are:
|
The fixed-width integer types found in int_types.h, the routines xdr_char(), xdr_short(), xdr_int(), and xdr_hyper() (and the unsigned versions of each) have equivalent functions with names familiar to ANSI C, as indicated in the following table.
Table 4-1 Primitive Type Equivalences
|
The nonprimitive xdr_string(), which takes more than two parameters, is called from xdr_wrapstring().
The following example of a programmer-supplied routine contains the calling arguments of a procedure.
struct simple { int a; short b; } simple;
The XDR routine xdr_simple() translates the argument structure as shown in the following code example.
Example 4-3 xdr_simple Routine
#include <rpc/rpc.h> #include "simple.h" bool_t xdr_simple(xdrsp, simplep) XDR *xdrsp; struct simple *simplep; { if (!xdr_int(xdrsp, &simplep->a)) return (FALSE); if (!xdr_short(xdrsp, &simplep->b)) return (FALSE); return (TRUE); }
rpcgen can automatically generate an equivalent routine.
An XDR routine returns nonzero (a C TRUE) if it completes successfully, and zero otherwise. A complete description of XDR is provided in Appendix C, XDR Protocol Specification.
The following list shows prefabricated routines:
|
For example, to send a variable-sized array of integers, the routine is packaged in a structure containing the array and its length:
struct varintarr { int *data; int arrlnth; } arr;
Translate the array with xdr_varintarr(), as shown in the following code example.
Example 4-4 xdr_varintarr Syntax Use
bool_t xdr_varintarr(xdrsp, arrp) XDR *xdrsp; struct varintarr *arrp; { return(xdr_array(xdrsp, (caddr_t)&arrp->data, (u_int *)&arrp->arrlnth, MAXLEN, sizeof(int), xdr_int)); }
The arguments of xdr_array() are the XDR handle, a pointer to the array, a pointer to the size of the array, the maximum array size, the size of each array element, and a pointer to the XDR routine to translate each array element. If the size of the array is known in advance, use xdr_vector(), as shown in the following code example.
Example 4-5 xdr_vector Syntax Use
int intarr[SIZE]; bool_t xdr_intarr(xdrsp, intarr) XDR *xdrsp; int intarr[]; { return (xdr_vector(xdrsp, intarr, SIZE, sizeof(int), xdr_int)); }
XDR converts quantities to 4-byte multiples when serializing. For arrays of characters, each character occupies 32 bits. xdr_bytes() packs characters. It has four parameters similar to the first four parameters of xdr_array().
Null-terminated strings are translated by xdr_string(), which is like xdr_bytes() with no length parameter. On serializing xdr_string() gets the string length from strlen(), and on deserializing it creates a null-terminated string.
The following example calls the built-in functions xdr_string() and xdr_reference(), which translates pointers to pass a string, and struct simple from previous examples.
Example 4-6 xdr_reference Syntax Use
struct finalexample { char *string; struct simple *simplep; } finalexample; bool_t xdr_finalexample(xdrsp, finalp) XDR *xdrsp; struct finalexample *finalp; { if (!xdr_string(xdrsp, &finalp->string, MAXSTRLEN)) return (FALSE); if (!xdr_reference( xdrsp, &finalp->simplep, sizeof(struct simple), xdr_simple)) return (FALSE); return (TRUE); }
Note that xdr_simple() could have been called here instead of xdr_reference().