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