HPlogo DCE for the HP e3000 > Chapter 7 Programming with RPC 1.2.1 on MPE/iX

Chapter 7 Programming with RPC 1.2.1 on MPE/iX

MPE documents

Complete PDF
Table of Contents
Index

Table of Contents
Compiling Multithreaded Application
This chapter explains the RPC application programming with a small example. The example consists of server and client components. The client makes a RPC request to the server and asks the server to sleep for a specified amount of time. The server serves this request from the client by going to sleep for the time given by the client.

/*                                         client.c
 ************************************************************************************
 * This is the sleeper client program.It takes two arguments, a hostname to contact *
 * for the server and a number of seconds to sleep. The client locates the server   *
 * using the hostname provided and the endpoint mapper on the server's host -- the  *
 * client does not contact the name service for server location information. The    *
 * client uses the explicit binding method, so it uses th hostname argument to      *
 * construct a binding handle (rpc_binding_handle_t).The client passes this binding *
 * handle to the invocation of the remote procedure.                                *
 ************************************************************************************
 */

/*
 * (c) Copyright 1992, 1993, 1994 Hewlett-Packard Co.
 */
/*
 * @(#)HP DCE/3000 @(#)Module: client.c
 */

#include          <stdlib.h>                    /* Standard POSIX defines */
#include          <strings.h>                   /* str*() routines */
#include          <stdio.h>                     /* Standard IO library */
#include          <dce/dce_error.h>             /* DCE error facility */
#include          <pthread.h>                   /* DCE Pthread facility */
#include          "common.h"                    /* Common defs for this app */
#include          "sleeper.h"                   /* Output from sleeper.idl */

#ifdef    TRACING
tr_handle_t       *tr_handle = NULL;            /* Initialize for client */
#endif                                          /* TRACING */

void main(int argc, char *argv[])
{
    rpc_binding_handle_tbh;                     /* "points" to the server */
    error_status_t          st, _ignore;        /* returned by DCE calls */
    dce_error_string_t      dce_err_string;     /* text describing error code */
    ndr_char                *string_binding;    /* used to create binding */
    unsigned long           sleep_time;         /* seconds server will sleep */
    unsigned_char_t         *netaddr;           /* network address of server */

#ifdef    TRACING
    /* tr_init() --
     *
     * The tr_init call initializes the trace facility.  The first parameter
     * is the name of an environment variable to consult to determine the
     * values for the selector levels, output filename, etc.  These values
     * can have defaults assigned in the second and third parameters, but
     * this sample application does not choose to do this.  The trace_name
     * parameter is a prefix string that will appear on each line of output
     * to distinguish tracing from this application from other applications.
     */

    if (tr_handle == NULL) {
        tr_handle = tr_init("TR_SLEEPER",       /* environment variable name */
                             NULL,              /* selector level defaults */
                             NULL,              /* filename for output */
                             trace_name);       /* prefix string in output */
        if (tr_handle == NULL) {
    /*
     * Still NULL -- unable to initialize tracing.  This may cause
     * the following tr_printmsg calls (via PRINT_FUNC) to fail.
     */
            fprintf(stderr, "Unable to initialize tracing interface!\n");
        }
    }
#endif /* TRACING */

    if (argc != 3) {
        ifprintf(stderr, "Usage: %s hostname sleep_time\n", argv[0]);
        exit(1);
    } else {
        netaddr = (unsigned_char_t *)argv[1];
        sleep_time = atoi(argv[2]);
    }

    /*    rpc_string_binding_compose() --
     *
     * Create a string binding using the command line hostname parameter.  A
     * string binding must be converted into a binding handle, required by
     * the DCE runtime, before it can be used.
     *
     * The first parameter is an optional object UUID.  This application does
     * not use multiple object UUIDs, so none is supplied.  The second
     * parameter is the protocol sequence to use to establish a connection;
     * the "ip" parameter selects the UDP/IP protocol.  The third parameter is
     * the network address of the server; this was specified on the command
     * line either as a hostname or as an IP address.
     *
     * The fourth parameter is an endpoint value (IP port number) to use; you
     * should only specify this when creating a string binding if the
     * endpoint is well-known.  Most servers use a dynamic endpoint, chosen
     * when the server starts up; so specify a value of NULL to cause the
     * RPC runtime to determine the value during the RPC setup.  The fifth
     * parameter is for network options.
     *
     * The sixth parameter is the return argument where the string binding
     * will be stored.  New memory will be allocated for this return value;
     * it must be freed later by this application.  The final parameter is a
     * DCE return status which will be checked for errors.
     */
    rpc_string_binding_compose(NULL,           /* no object UUID */
                            (unsigned_char_t *)"ip",  /* protocol to use */
                             netaddr,          /* network addr of server */
                             NULL,             /* use a dynamic endpoint */
                             NULL,             /* misc. network options */
                             &string_binding,  /* returned string binding */
                             &st);             /* error status for this call */
    if (st != rpc_s_ok) {
           /*       dce_error_inq_text() --
            *
            * Inquire about the error status returned by the previous DCE call.
            *
            * The first parameter to this call is a DCE error_status_t presumed
            * to have been returned by a preceeding DCE call.  The second
            * parameter is a string long enough to hold the longest possible
            * DCE error string -- the data type dce_error_string_t is defined
            * to be a character array of this length.  The third parameter is
            * another dce error status; this call is unlikely to fail so its
            * status is ignored.
            */
           dce_error_inq_text(st, dce_err_string, (int *)&_ignore);
           PRINT_FUNC(PRINT_HANDLE, "Cannot compose string binding: %s\n",
                 dce_err_string);
           exit(1);
    }
    /*    rpc_binding_from_string_binding() --
     * Create a binding handle structure from the string binding.  The
     * client stub function needs a binding handle; it cannot use the string
     * binding form created above.
     *
     * The first parameter to this call is the string binding generated
     * earlier.  The second parameter is an RPC binding handle structure;
     * a new binding handle will be allocated and stored here -- this
     * application must free the storage when it is done with it.  The third
     * parameter is the DCE return status.
     */
    
    rpc_binding_from_string_binding(string_binding, /* created above */
                                  &bh,              /* allocated and returned */
                                  &st);             /* error status for this call */
    if (st != rpc_s_ok) {
        dce_error_inq_text(st, dce_err_string, (int *)&_ignore);
        PRINT_FUNC(PRINT_HANDLE, "Cannot get a binding handle: %s\n",
           dce_err_string);
        exit(1);
    }
    /*
     * At this point the application is not actually connected to any
     * server, but it has all the information needed to establish a
     * connection to the remote server.  Connection establishment happens in
     * the client stub function called below.
     */

    PRINT_FUNC(PRINT_HANDLE, "Bound to %s\n", string_binding);

    /*    rpc_string_free() --
     *
     * Free a string allocated by the RPC runtime.  The first parameter is
     * the address of a string which was previously allocated (or is NULL).
     * It will be free()d, and the space returned to the system for use in
     * the future.  The second parameter is the DCE return status.
     */

    rpc_string_free(&string_binding,                   /* DCE string to free */
                    &_ignore);                         /* DCE return status */
    PRINT_FUNC(PRINT_HANDLE, "Calling remote_sleep(%d)\n", sleep_time);

    /*    TRY --
     *
     * The macro TRY is used to wrap a call which may result in a DCE
     * exception being raised.  Any call to an RPC client stub can result in
     * an exception being raised if something goes wrong.  Examples of what
     * can go wrong include: there is no server listening on the remote
     * host; there is a data error in a client or server stub; the server
     * raises an exception while executing the procedure call.  If an
     * exception is raised and there is no TRY/CATCH block surrounding the
     * call, the exception will cause the process to abort and dump core.
     * Since this is typically not very helpful, we prefer to catch the
     * exception.  In the string_conv and later sample applications the
     * client will do something intelligent with the exception.
     */

    TRY {
        /*
         * Call the remote procedure passing in the number of seconds to sleep,
         * as defined in the .idl file.  A binding handle parameter is required
         * since this client uses the explicit binding method.
         */

         remote_sleep(bh, sleep_time);
    /*   CATCH_ALL --
     *
     * The CATCH_ALL macro denotes the end of a TRY block.  If an exception
     * occurs in any of the calls within the TRY block, control will pass to
     * the CATCH_ALL block where the exception is dealt with.  This client
     * will simply inform you that something went wrong; in the string_conv
     * and later sample applications the client will do something
     * intelligent with the exception.
     */
    } CATCH_ALL {
    /*
     * We caught an exception in the client stub code.  Inform the user.
     */
        PRINT_FUNC(PRINT_HANDLE, "Caught an exception!\n");
        exit(1);
    }

    /*   ENDTRY --
     *
     * The ENDTRY macro is required by the exception implementation to
     * terminate a TRY block.
     */

    ENDTRY;
    /*
     * No status information was passed back.  If the call failed, the RPC
     * runtime will have raised an exception and caused an exit.
     */

    PRINT_FUNC(PRINT_HANDLE, "Returned from remote_sleep(%d)\n", sleep_time);
    exit(0);
}


/*                             manager.c
 ***************************************************************************
 * This is the server-side RPC manager function; this is the function that *
 * actually implements the remote procedure defined in the .idl file.  The *
 * server stub (called by the RPC runtime) calls this function when an RPC *
 * request comes in for this interface.                                    *
 *
 * The manager function takes the arguments defined in the .idl file,      *
 * performs its function and returns results as defined in the .idl file.  *
 * This particular manager function does not return any results (it does   *
 * not have any [out] parameters, nor a return value).                     *
 ***************************************************************************
 */

/*
 * (c) Copyright 1992, 1993, 1994 Hewlett-Packard Co.
 */
/*
 * @(#)HP DCE/3000
 * @(#)Module: manager.c
 */

#include         <stdlib.h>                 /* Standard POSIX defines */
#include         <stdio.h>                  /* Standard IO library */
#include         "common.h"                 /* Common defs for this app */
#include         "sleeper.h"                /* Output from sleeper.idl */

/*
 * This particular manager function simply sleeps for the number of seconds
 * specified by its argument.  Since the .idl file speficies use of explicit
 * binding, the manager must take a binding handle as its first argument.
 *
 * Note: the code in this manager function must be (and is) reentrant since it
 * may be running simultaneously in multiple server threads.
 */

void remote_sleep
    (
     /* [in] */   handle_t       h,        /* Use explicit binding */
     /* [in] */   ndr_long_int   time      /* Seconds to sleep */
    )
{
    PRINT_FUNC(PRINT_HANDLE, "Enter remote_sleep(%d) manager\n", time);
    /*
     * This is a mind-numbingly simple manager ...
     */

    (void) sleep (time);
    PRINT_FUNC(PRINT_HANDLE, "Return from remote_sleep(%d) manager\n", time);
    return;
}


/*                             server.c
 ***************************************************************************
 * This is the server program for the basic sleeper sample application. It *
 * will register the interface named "sleeper" with the local RPC runtime  *
 * and with the endpoint mapper daemon (rpcd) on the local host.  It then  *
 * listens for incoming requests and serves each request in a separate     *
 * thread.  The manager function (see manager.c) is invoked to serve the   *
 * requests after the inbound arguments are unmarshalled.                  *
 ***************************************************************************
 */

/*
 * (c) Copyright 1992, 1993, 1994 Hewlett-Packard Co.
 */
/*
 * @(#)HP DCE/3000
 * @(#)Module: server.c
 */

#include          <pthread.h>                /* POSIX threads facility */
#include          <stdlib.h>                 /* Standard POSIX defines */
#include          <strings.h>                /* str*() routines */
#include          <stdio.h>                  /* Standard IO library */
#include          <dce/dce_error.h>          /* DCE error facility */
#include          "common.h"                 /* Common defs for this app */
#include          "sleeper.h"                /* Output from sleeper.idl */

#ifdef   TRACING
tr_handle_t *   tr_handle = NULL;            /* Initialize for server */
#endif                                       /* TRACING */

void main(int argc, char *argv[])
{
    rpc_binding_vector_t  *bvec;             /* used to register w/runtime */
    error_status_t        st, _ignore;       /* returned by DCE calls */
    dce_error_string_t    dce_err_string;    /* text describing error code */
    ndr_char              *string_binding;   /* printable rep of binding */
    int                           i;         /* index into bvec */
#ifdef    TRACING
    /*
     * Initialize tracing.
     */
    if (tr_handle == NULL) {
        char    trace_name_buf[40];
    /*
     * Construct the tracing prefix string from the trace_name constant
     * and the current process id.  This allows multiple servers on the
     * same host to differentiate themselves from each other.
     */
        sprintf(trace_name_buf, "%s-%d", trace_name, getpid());
        tr_handle = tr_init("TR_SLEEPER",        /* environment variable name */
                             NULL,               /* selector level defaults */
                             NULL,               /* filename for output */
                             trace_name_buf);    /* prefix string in output */
        if (tr_handle == NULL) {
    /*
     * Still NULL -- unable to initialize tracing.  This may cause
     * the following tr_printmsg calls (via PRINT_FUNC) to fail.
     */
            fprintf(stderr, "Unable to initialize tracing interface!\n");
        }
    }
#endif     /* TRACING */
    /*     rpc_server_use_protseq() --
     *
     * Specify the protocol sequences that the RPC runtime should use when
     * creating endpoints.  The first parameter is a string representation
     * of a protocol sequence to use.  The second parameter is the maximum
     * number of concurrent remote procedure call requests that the server
     * will accept.  In the first version of DCE, the second parameter is
     * always replaced by a default value.  The third parameter is the DCE
     * return status.
     *
     * This server uses only the UDP/IP protocol sequence for efficiency
     * reasons: the UDP transport is more efficient for procedures that are
     * idempotent and expected to return only small amounts of data.  The
     * reason why we don't simply listen on all protocols and let the client
     * choose is because it consumes more system resources to listen on
     * multiple protocol sequences.
     */

    rpc_server_use_protseq((unsigned char *)"ip", /* prot seq to listen on */
                          rpc_c_protseq_max_calls_default,
                          &st);            /* error status for this call */
    if (st != rpc_s_ok) {
        dce_error_inq_text(st, dce_err_string, (int *)&_ignore);
        PRINT_FUNC(PRINT_HANDLE, "Cannot use protocol sequence ip: %s\n",
                 dce_err_string);
        exit(1);
    }

    /*    rpc_server_register_if() --
     *
     * Register the interface definition and manager entry point vector with
     * the RPC runtime.  The first parameter is the interface specification
     * generated by the IDL compiler; it is declared in the "sleeper.h" file
     * generated by idl.  The second parameter is the manager type UUID to
     * associate with the third parameter.  This application does not use
     * type UUIDs (an advanced feature).  The third parameter is the manager
     * entry point vector, the array of functions used as implementations
     * for incoming remote procedure calls.  A value of NULL indicates that
     * the runtime should use the default manager EPV generated by the IDL
     * compiler.  The fourth parameter is the DCE error status.
     */

    rpc_server_register_if(sleeper_v1_0_s_ifspec, /* generated interface spec */
                            NULL,                 /* No type UUIDs */
                            NULL,                 /* Use supplied epv */
                            &st);                 /* error status for this call */
    if (st != rpc_s_ok) {
        dce_error_inq_text(st, dce_err_string, (int *)&_ignore);
        PRINT_FUNC(PRINT_HANDLE,"Cannot register interface with runtime: %s\n",
                 dce_err_string);
        exit(1);
    }

    /*    rpc_server_inq_bindings() --
     *
     * Inquire from the RPC runtime about the bindings that were created in
     * the registration call above.
     *
     * The first parameter is the address of a binding vector data type.
     * Memory for a new binding vector will be allocated and returned.  The
     * application must later free this memory.  The second parameter is the
     * DCE error status.
     *
     * The binding information is required for registration with the
     * endpoint mapper below.  We print it out simply for debugging
     * purposes.
     */

    rpc_server_inq_bindings(&bvec,            /* runtime's binding vector */
                            &st);             /* error status for this call */
    if (st != rpc_s_ok) {
        dce_error_inq_text(st, dce_err_string, (int *)&_ignore);
        PRINT_FUNC(PRINT_HANDLE, "Cannot get bindings: %s\n", dce_err_string);
        exit(1);
    } else
        PRINT_FUNC(PRINT_HANDLE, "Bindings:\n");

    /*
     * Print out the bindings obtained from the RPC runtime.  This info is
     * only for debugging purposes -- it shows what protocol sequence and
     * ports have been grabbed by the runtime for this server.
     */

    for (i = 0; i < bvec->count; i++) {
          /*       rpc_binding_to_string_binding() --
           *
           * Convert a binding handle to a string binding for printing.  The
           * first parameter is a binding handle.  (In a binding vector there
           * are bvec->count binding handles).  The second parameter is a
           * pointer to a dce string data type; memory will be allocated and
           * the value returned in it.  The application must free this memory.
           * The third parameter is the DCE error status.
           */

        rpc_binding_to_string_binding(bvec->binding_h[i], /* a binding handle */
                                  &string_binding,  /* returned string form */
                                  &st);             /* error status for this call */
        if (st != rpc_s_ok) {
            dce_error_inq_text(st, dce_err_string, (int *)&_ignore);
            PRINT_FUNC(PRINT_HANDLE, "Cannot get string binding: %s\n",
                     dce_err_string);
        } else
            PRINT_FUNC(PRINT_HANDLE, "  %s\n", string_binding);

         /*
          * Free the memory allocated in rpc_binding_to_string_binding().
          */

        rpc_string_free(&string_binding, &_ignore);
    }

    /*    rpc_ep_register() --
     *
     * Register the interface with the endpoint mapper.  The first parameter
     * is the interface specification generated by the IDL compiler.  The
     * second parameter is the binding vector returned by the RPC runtime
     * describing the endpoints (IP ports) on which this server is listening
     * for RPC requests.  The third parameter is a vector of object UUIDs
     * that the server offers; this server does not implement multiple
     * objects so it specifies NULL.  The fourth parameter is an annotation
     * used for informational purposes only.  The RPC runtime does not use
     * this string to determine which server instance a client communicates
     * with, or for enumerating endpoint map elements.  The last parameter
     * is the DCE error status.
     *
     * When this call completes the bindings we established with the RPC,
     * runtime will be associated with this interface.  This allows a client
     * to look up a server by interface without specifying an endpoint
     * (port): instead, by contacting the endpoint mapper, a client is able
     * to locate servers registered using dynamic (system-chosen) endpoints.
     */

    rpc_ep_register(sleeper_v1_0_s_ifspec,  /* generated interface spec */
                    bvec,                   /* runtime's binding vector */
                    NULL,                   /* no objects supported */
                    (unsigned_char_t *) sleeper_description,
                    &st);                   /* error status for this call */
    if (st != rpc_s_ok) {
        dce_error_inq_text(st, dce_err_string, (int *)&_ignore);
        PRINT_FUNC(PRINT_HANDLE, "Cannot register with endpoint map: %s\n",
                  dce_err_string);
        exit(1);
    }

    PRINT_FUNC(PRINT_HANDLE, "Listening...\n");

    /*    rpc_server_listen() --
     *
     * Listen and handle incoming RPC requests.  This call typically does
     * not return; instead incoming RPC requests will be dispatched to the
     * manager function(s), each in its own thread.
     *
     * The first parameter is the maximum number of concurrently executing
     * remote procedure calls to allow.  The second parameter is the DCE
     * error status.
     */

    rpc_server_listen(rpc_c_listen_max_calls_default,
                         &st);                 /* error status for this call */
    if (st != rpc_s_ok) {
        dce_error_inq_text(st, dce_err_string, (int *)&_ignore);
        PRINT_FUNC(PRINT_HANDLE, "Listen returned with error: %s\n",
                   dce_err_string);
    } else
        PRINT_FUNC(PRINT_HANDLE, "Stopped listening...\n")

    /************************************************************************
     * IMPORTANT NOTE: We will probably never reach here.  If you interrupt
     * the server with an asynchronous signal, such as a ^C (or SIGINT) from
     * the keyboard or a "kill <PID>" (a SIGTERM signal), it will cause the
     * process to exit; it will not reach here.  See the lookup sample
     * application for code that is able to properly clean up after the
     * listen call.
     ***********************************************************************
     */

    PRINT_FUNC(PRINT_HANDLE, "Unregistering endpoints and interface...\n");

    /*    rpc_ep_unregister() --
     *
     * Unregister the interface and endpoints with the RPC runtime.  The
     * first parameter is the interface specification from the IDL compiler.
     * The second parameter is the binding vector registered with this
     * interface.  The third parameter is the object UUID vector (NULL since
     * this application does not support multiple objects).  The final
     * parameter is the DCE error status.
     */

    rpc_ep_unregister(sleeper_v1_0_s_ifspec,      /* IDL-generated ifspec */
                      bvec,                         /* this server's bindings */
                      NULL,                         /* no object UUIDs supported */
                      &_ignore);                    /* ignore any errors */

    /*    rpc_binding_vector_free() --
     * Free a binding vector that is no longer needed.  Since it was
     * allocated by the runtime, the application should remember to free it.
     * The first parameter is the binding vector to free; the second
     * parameter is the DCE error status, which is ignored.
     */

    rpc_binding_vector_free(&bvec, &_ignore);

    /*    rpc_server_unregister_if() --
     *
     * Unregister this server from the RPC runtime.  This is unnecessary
     * since this process is about to exit, but is here to demonstrate good
     * programming style.  The first parameter is the interface
     * specification; the second is the manager type UUID (which is NULL
     * since this application does not support multiple types).  The last
     * parameter is the DCE error status, which is ignored.
     */

    rpc_server_unregister_if(sleeper_v1_0_s_ifspec,      /* IDL-generated ifspec */
                             NULL,                       /* No object UUID */
                             &_ignore);                  /* ignore any errors */
    exit(0);
}


/*                             common.h
 ***************************************************************************
 * This file contains definitions common between the client and server.    *
 ***************************************************************************
 * (c) Copyright 1992, 1993, 1994 Hewlett-Packard Co.
 */
/*
 * @(#)HP DCE/3000 1.5
 * @(#)Module: common.h
 */
/*
 * This string will be registered with the RPC runtime as an annotation
 * describing the endpoint entry.
 */

# define    sleeper_description       "sleeper"
#ifdef     TRACING
/*
 * If you want to use the building blocks tracing facility then define
 * the TRACING flag in your compile (put -DTRACING in the Makefile).  For
 * this to compile and link, you will need the building blocks library
 * installed on your system.
 */

#include      <dce/trace_log.h>             /* Building blocks tracing */

extern   tr_handle_t *      tr_handle;        /* used by client, server */

/*
 * These print functions use the trace/log facility instead of stdio.
 * All print statements in this file use these macros so it's easy to
 * replace use of stdio with the trace/log facility. The NULL after
 * tr_handle signifies the use of the 500 byte, default buffer for
 * trace output.
 */

# define     RINT_FUNC     tr_printmsg
# define     PRINT_HANDLE  tr_handle, NULL

/*
 * The trace_name string is registered with the trace/log facility as the
 * name of this application.  It will appear in any tracing output.
 */

# define     trace_name    sleeper_description
#else   /* TRACING */
/*
 * These print functions use stdio instead of the trace/log facility.  They
 * turn off the tracing macros by replacing them with standard IO routines.
 */
# define     PRINT_FUNC        fprintf
# define     PRINT_HANDLE      stdout
#endif    /* TRACING */


/*                               sleeper.idl
 ****************************************************************************
 * This .idl file declares an interface with a set of remotely-callable      *
 * procedures.  This file is compiled by the idl compiler into a C interface *
 * declaration (.h) and a C client stub (_cstub.c) and server stub           *
 * (_sstub.c) that interface with the RPC runtime.  You must write a manager *
 * for this procedure (see manager.c) and the client and server main()       *
 * functions (see client.c and server.c).
 ***************************************************************************
 */

/*
 * (c) Copyright 1992, 1993, 1994 Hewlett-Packard Co.
 */
/*
 * @(#)HP DCE/3000
 * @(#)Module: sleeper.idl
 */

/*
 * This definition declares the interface for this application and
 * associates it with a globally (universally) unique identifier, or UUID.
 * The RPC runtime uses the UUID to identify this interface.  If you
 * leverage this code, BE SURE TO CHANGE THE UUID!  Do this by running the
 * program "uuidgen" and putting the uuidgen output in place of the one
 * supplied.  Failure to do this may cause bizarre results.
 */

[uuid(D0FCDD70-7DCB-11CB-BDDD-08000920E4CC),      /* NOTE: CHANGE THIS!!! */
 version(1.0)]

interface sleeper
{
    void remote_sleep
        (
         [in] handle_t      h,          /* Use explicit binding */
         [in] long        time        /* Seconds to sleep */
        );
}




DCE-IDL Compiler for RPC 1.2.1


Compiling Multithreaded Application