Simplified Interface

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().

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.

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 unsignedint, so the first return parameter of rpc_call() is xdr_u_int, which is for
an unsignedint, and the second return parameter is &nusers, which points
to unsignedint storage. Because RUSERSPROC_NUM has no argument, the XDR encoding function of
rpc_call() is xdr_void() and its argument is NULL.

Server Side of the Simplified Interface

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.

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 nof 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.

Hand-Coded Registration Routine

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.

rpc_reg() can be called as many times as is needed to register different
programs, versions, and procedures.

Passing Arbitrary Data Types

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:

xdr_int()

xdr_double()

xdr_netobj()

xdr_u_short()

xdr_u_long()

xdr_wrapstring()

xdr_enum()

xdr_char()

xdr_long()

xdr_quadruple()

xdr_float()

xdr_u_char()

xdr_u_int()

xdr_void()

xdr_bool()

xdr_hyper()

xdr_short()

xdr_u_hyper()

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

Function

Equivalent

xdr_char()

xdr_int8_t()

xdr_u_char()

xdr_u_int8_t()

xdr_short()

xdr_int16_t()

xdr_u_short()

xdr_u_int16_t()

xdr_int()

xdr_int32_t()

xdr_u_int()

xdr_u_int32_t()

xdr_hyper()

xdr_int64_t()

xdr_u_hyper()

xdr_u_int64_t()

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.

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.

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 structsimple from previous examples.