JavaScript is required to for searching.
Skip Navigation Links
Exit Print View
ONC+ Developer's Guide     Oracle Solaris 11.1 Information Library
search filter icon
search icon

Document Information

Preface

1.  Introduction to ONC+ Technologies

2.  Introduction to TI-RPC

3.  rpcgen Programming Guide

4.  Programmer's Interface to RPC

Simplified Interface

Client Side of Simplified Interface

Server Side of the Simplified Interface

Hand-Coded Registration Routine

Passing Arbitrary Data Types

Standard Interfaces

Top-Level Interface

Client Side of the Top-Level Interface

Intermediate-Level Interface

Client Side of the Intermediate-Level Interface

Server Side of the Intermediate-Level Interface

Expert-Level Interface

Client Side of the Expert-Level Interface

Server Side of the Expert-Level Interface

Bottom-Level Interface

Client Side of the Bottom-Level Interface

Server Side of the Bottom-Level Interface

Server Caching

Low-Level Data Structures

Testing Programs Using Low-Level Raw RPC

Connection-Oriented Transports

Memory Allocation With XDR

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

A.  XDR Technical Note

B.  RPC Protocol and Language Specification

C.  XDR Protocol Specification

D.  RPC Code Examples

E.  portmap Utility

Glossary

Index

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

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

Client Side of Simplified Interface

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.

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.

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:

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.

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

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.

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:

xdr_array()
xdr_bytes()
xdr_reference()
xdr_vector()
xdr_union()
xdr_pointer()
xdr_string()
xdr_opaque()

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