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 */
);
}