[comp.lang.c++] C++ classes for Sun RPC

jeffm@noisette.colorado.edu (Jeff McWhirter) (06/20/91)

We have a need for  an   O-O  rpc mechanism in C++ for
Sun RPC.  It would be nice to support blocking and non-blocking calls,
xdr routines for passing data, ...?
It  would also be nice if something like this has been done so we won't
have to do it.
Has anyone heard  of or built  such a mechanism???


Thanks
Jeff McWhirter

warsaw@nlm.nih.gov (Barry A. Warsaw) (06/21/91)

>>>>> "Jeff" == Jeff McWhirter <jeffm@noisette.colorado.edu> writes:

	Jeff> We have a need for an O-O rpc mechanism in C++ for Sun RPC.
	Jeff> It would be nice to support blocking and non-blocking calls,
	Jeff> xdr routines for passing data, ...?  It would also be nice
	Jeff> if something like this has been done so we won't have to do
	Jeff> it.  Has anyone heard of or built such a mechanism???

Following my siggy is a shar file containing 3 classes which implement
Sun's RPC/XDR.  It is currently limited to TCP (someday I'll grab the
TLI-RPC stuff) and doesn't do non-blocking, but I have built a real
thing on top of it and it does work well.  Also included are server
and client example programs.  Note that these classes use a string and
error class which I'm not including -- you are probably using your own
anyway, and if not, they're easy to write/fake/replace.  Oh yeah,
there are some bugs/typo's in the RPC header files that come with
SunC++ 2.0.  We haven't gotten 2.1 yet, but I understand that some of
the bugs were fixed, others remain (especially check the prototype for
xdr_u_char()).

Disclaimers on all the code, blah, blah, blah. This stuff has only
been compiled/tested on a Sun SS1+ running SunOS 4.1.1, compiled with
SunC++ 2.0.

Enjoy,
-Barry

P.S. Someone should really start up an archive/FAQ for this newsgroup.

NAME:  Barry A. Warsaw         INET: warsaw@nlm.nih.gov
TELE:  (301) 496-1936          UUCP: uunet!nlm.nih.gov!warsaw

-------------------- cut here --------------------
#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  client.hh server.hh xdr.hh client_example.cc server.cc
#   server_example.cc xdr.cc
# Wrapped by warsaw@warsaw on Thu Jun 20 17:05:14 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'client.hh' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'client.hh'\"
else
echo shar: Extracting \"'client.hh'\" \(3462 characters\)
sed "s/^X//" >'client.hh' <<'END_OF_FILE'
X// $Id: client.hh,v 3.0 91/06/19 15:21:15 warsaw Exp $
X//
X// $Log:	client.hh,v $
X// Revision 3.0  91/06/19  15:21:15  warsaw
X// Bumping to Revision 3.0 
X// 
X// Revision 2.0  91/06/19  15:20:23  warsaw
X// Bumping to Revision 2.0 
X// 
X// Revision 1.8  91/05/23  10:20:53  warsaw
X// added default timeout to ctor
X// 
X// Revision 1.7  91/05/22  16:44:21  warsaw
X// protocol is always going to be tcp
X// also, changed the class name from RPC_clnt to client
X// 
X// Revision 1.6  91/05/21  17:28:51  warsaw
X// fixed some more typos
X// 
X// Revision 1.5  91/05/21  17:27:48  warsaw
X// fixed some syntax typos
X// 
X// Revision 1.4  91/05/20  15:02:57  warsaw
X// check for sizeof_outdata > 0
X// 
X// Revision 1.3  91/05/20  12:46:51  warsaw
X// minor #endif fix
X// 
X// Revision 1.2  91/05/20  12:41:34  warsaw
X// simplified and moved inline
X// 
X// Revision 1.1  91/05/16  17:16:11  warsaw
X// Initial revision
X// 
X
X// This class represents an RPC communications client. It handles
X// opening the connection with the server.  It also handles remote
X// execution by the caller of RPC remote procedures, along with
X// argument, return values and xdr conversion routines. by default tcp
X// protocol is used.
X
X
X#ifndef client_hh
X#define client_hh
X
X#include <libc.h>							 // for bzero()
X#include <stdio.h>							 // rpc.h needs this
X#include <rpc/rpc.h>						 // for rpc code
X#include "string.hh"
X#include "error.hh"
X
X
Xstatic timeval _DEFAULT_CLIENT_TIMEOUT = { 25, 0 };
X
X
X// ======================================================================
X// class definition
X
Xclass client
X{
Xpublic:
X	inline client( string server,
X				   u_long prognum, u_long versnum,
X				   timeval timeout = _DEFAULT_CLIENT_TIMEOUT );
X	inline ~client( void );
X
X		// execute remote procedure on server, returns zero on
X		// success, non-zero on failure.
X	inline int rexec( u_long procedure,
X					  xdrproc_t in_converter, char*& in,
X					  xdrproc_t out_converter, char*& out,
X					  int sizeof_outdata );
X
X		// get and set the timeout value
X	inline void timeout( timeval& new_timeout );
X	inline timeval timeout( void );
X
Xprivate:
X	CLIENT* cptr;							 // RPC client struct
X	u_long pnum;
X	u_long vnum;
X	timeval to_val;
X};
X
X
X// ======================================================================
X// ctor and dtor
X
Xinline client::client( string server,
X					   u_long prognum, u_long versnum,
X					   timeval timeout )
X	: cptr( 0 ),
X	  pnum( prognum ),
X	  vnum( versnum ),
X	  to_val( timeout )
X{
X	error err( "client(RPC)" );
X
X	cptr = clnt_create( server, prognum, versnum, "tcp" );
X	if( cptr == NULL ) {
X		clnt_pcreateerror( server );
X		err.fatal( "connection not established.\n" );
X	}
X};
X
X
Xinline client::~client( void )
X{
X	clnt_destroy( cptr );
X};
X
X
X// ======================================================================
X// remote execution
X
Xinline int client::rexec( u_long procedure,
X						  xdrproc_t in_converter, char*& in,
X						  xdrproc_t out_converter, char*& out,
X						  int sizeof_outdata )
X{
X	error err( "client::rexec()" );
X
X	if( out && sizeof_outdata > 0 )
X		bzero( out, sizeof_outdata );
X
X	return( clnt_call( cptr, procedure,
X					   in_converter, in,
X					   out_converter, out,
X					   to_val ) != RPC_SUCCESS );
X};
X
X
X// ======================================================================
X// get/set the timeout
X
Xinline void client::timeout( timeval& new_timeout )
X{
X	to_val = new_timeout;
X};
X
X
Xinline timeval client::timeout( void )
X{
X	return( to_val );
X};
X
X
X#endif client_hh
END_OF_FILE
if test 3462 -ne `wc -c <'client.hh'`; then
    echo shar: \"'client.hh'\" unpacked with wrong size!
fi
# end of 'client.hh'
fi
if test -f 'server.hh' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'server.hh'\"
else
echo shar: Extracting \"'server.hh'\" \(4285 characters\)
sed "s/^X//" >'server.hh' <<'END_OF_FILE'
X// $Id: server.hh,v 3.0 91/06/19 15:21:10 warsaw Exp $
X//
X// $Log:	server.hh,v $
X// Revision 3.0  91/06/19  15:21:10  warsaw
X// Bumping to Revision 3.0 
X// 
X// Revision 2.0  91/06/19  15:20:20  warsaw
X// Bumping to Revision 2.0 
X// 
X// Revision 1.2  91/05/23  11:26:51  warsaw
X// had to check callback mechanism, no real changes
X// 
X// Revision 1.1  91/05/23  09:52:26  warsaw
X// Initial revision
X// 
X
X
X// this class is the server analog for the client class. it implements
X// the dispatching of RPC commands. it handles registration of the
X// program with the portmapper for automatic port number assignments.
X// it also handles registration of callback functions as calls to
X// this class' (or derived class) member functions.
X
X
X#ifndef server_hh
X#define server_hh
X
X#include <stdio.h>							 // rpc.h needs this
X#include <rpc/rpc.h>						 // for RPC stuff
X#include "error.hh"
X
X
X// ======================================================================
X// this typedef defines the form of a callback function. the callback
X// function must be a member of server class or a member of a class
X// derived from server. `in' points to an pre-allocated area of data
X// which will contain the arguments from the remote caller.  `out'
X// points to pre-allocated data in which the callback function will
X// place its results.  the callback function must know how to cast in
X// and out appropriately.  also, the callback function SHOULD NOT
X// de-allocate the memory pointed to by in and out.
X
Xclass server;
Xtypedef void (server::*rpc_handler)( char*& in, char*& out );
X
X
X// ======================================================================
X// class definition
X
Xclass server
X{
Xpublic:
X		// it is an error to instantiate more than 1 instance of the
X		// server class. this is because RPC can only handle a single
X		// callback on the socket. Note that there is no destructor
X		// for this class since (due to the nature of the run method),
X		// it will never be destroyed until the entire process dies.
X	server( u_long prognum, u_long versnum );
X
X		// register an RPC procedure with the appropriate callback
X		// function which can handle this procedure call.
X	void regproc( u_long procedure, rpc_handler callback,
X				  xdrproc_t in_converter,  int indata_size,
X				  xdrproc_t out_converter, int outdata_size );
X
X		// once all callbacks are registered, the application can call
X		// the run method below to start up the RPC server. it is an
X		// error for run() to ever return so if a callback is used to
X		// terminate the server, it should call exit()
X	inline void run( void );
X
X		// returns non-zero if the application was started up by the
X		// inet daemon
X	inline int inetd_p( void );
X
Xprotected:
X		// this function actually does the dispatching to the member
X		// function that can handle the remote procedure call. it is
X		// not directly called by the RPC level code.
X	virtual void dispatch( svc_req* request, SVCXPRT* transp );
X
Xprivate:
X		// this stuff is used to interface to the RPC callback
X		// mechanisms. it simply finds the instance object and calls
X		// the dispatch() method.
X	static server* instance;
X	static inline void rpc_callback( svc_req* request, SVCXPRT* transp );
X
X	SVCXPRT* transp;						 // RPC server handle
X	int StartedByINETD_p;					 // started by inetd?
X
X		// callback process table entries
X	struct ProcTableEntry {
X		u_long procnum;
X		xdrproc_t inproc;
X		xdrproc_t outproc;
X		int insz;
X		int outsz;
X		rpc_handler callback;
X		ProcTableEntry* next;
X	} *head;
X};
X
X
X// ======================================================================
X// inline methods
X
Xinline void server::run( void )
X{
X	error err( "server::run()" );
X	svc_run();
X	err.fatal( "this should never return!" );
X};
X
X
Xinline int server::inetd_p( void )
X{
X	return( StartedByINETD_p );
X};
X
X
X// ======================================================================
X// this is the routine which is registered with RPC
X
Xvoid server::rpc_callback( svc_req* request, SVCXPRT* transp )
X{
X		// this is necessary since this function will not be called
X		// with a valid `this' pointer.  it will be called by
X		// svc_run() so we need to get the object on which to dispatch
X		// member callback functions.
X	server* whoami = server::instance;
X	whoami->dispatch( request, transp );
X};
X
X
X#endif server_hh
END_OF_FILE
if test 4285 -ne `wc -c <'server.hh'`; then
    echo shar: \"'server.hh'\" unpacked with wrong size!
fi
# end of 'server.hh'
fi
if test -f 'xdr.hh' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'xdr.hh'\"
else
echo shar: Extracting \"'xdr.hh'\" \(1784 characters\)
sed "s/^X//" >'xdr.hh' <<'END_OF_FILE'
X// $Id: xdr.hh,v 3.0 91/06/19 15:21:14 warsaw Exp $
X//
X// $Log:	xdr.hh,v $
X// Revision 3.0  91/06/19  15:21:14  warsaw
X// Bumping to Revision 3.0 
X// 
X// Revision 2.0  91/06/19  15:20:23  warsaw
X// Bumping to Revision 2.0 
X// 
X// Revision 1.3  91/06/10  13:55:36  warsaw
X// added Xchar and Xuchar
X// 
X// Revision 1.2  91/05/28  10:05:02  warsaw
X// fixed static initialization of static variables
X// we no longer use a static class object as this wasn't working for some
X// (unknown) reason.
X// 
X// Revision 1.1  91/05/24  15:57:13  warsaw
X// Initial revision
X// 
X
X
X// this class contains routines to serialize and deserialize XDR data
X// streams. it is most useful in conjunction with the RPC server and
X// client classes.
X
X
X#ifndef xdr_hh
X#define xdr_hh
X
X#include <stdio.h>							 // for rpc.h
X#include <rpc/rpc.h>						 // for xdr stuff
X#include <string.hh>						 // for string class
X
X
X// ======================================================================
X// structures that are useful
X
Xstruct xdrOpaque {
X	u_int length;							 // length of opaque data
X	char* handle;							 // handle
X};
X
X
X
X// ======================================================================
X// class definition
X
Xclass xdr
X{
Xpublic:
X	static xdrproc_t Xchar;
X	static xdrproc_t Xshort;
X	static xdrproc_t Xint;
X	static xdrproc_t Xlong;
X	static xdrproc_t Xuchar;
X	static xdrproc_t Xushort;
X	static xdrproc_t Xuint;
X	static xdrproc_t Xulong;
X	static xdrproc_t Xfloat;
X	static xdrproc_t Xdouble;
X
X	static xdrproc_t Xenum_t;
X	static xdrproc_t Xbool_t;
X	static xdrproc_t Xvoid;
X
X	static xdrproc_t Xwrapstring;
X	static xdrproc_t Xstringclass;
X	static xdrproc_t Xopaque;
X
Xprotected:
X	static bool_t xdr_stringclass( XDR* xdrs, string* obj );
X	static bool_t xdr_Opaque( XDR* xdrs, xdrOpaque* obj );
X};
X
X#endif xdr_hh
END_OF_FILE
if test 1784 -ne `wc -c <'xdr.hh'`; then
    echo shar: \"'xdr.hh'\" unpacked with wrong size!
fi
# end of 'xdr.hh'
fi
if test -f 'client_example.cc' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'client_example.cc'\"
else
echo shar: Extracting \"'client_example.cc'\" \(1318 characters\)
sed "s/^X//" >'client_example.cc' <<'END_OF_FILE'
X#include <sys/param.h>
X#include <sysent.h>
X#include <stream.h>
X#include <client.hh>
X#include <string.hh>
X#include <error.hh>
X#include <xdr.hh>
X
Xvoid usage( string progname )
X{
X	cerr << "Usage: " << progname << " -h <host>" << endl;
X	exit( 1 );
X};
X
X
Xint main( int argc, char** argv )
X{
X	error err( "client_example" );
X	string host( null );
X
X	for( int argci=1; argci<argc; argci++ ) {
X		string argstr( argv[argci] );
X		if( argstr == "-h" ) {
X			if( argci == argc-1 ) usage( argv[0] );
X			else host = argv[++argci];
X		} else usage( argv[0] );
X	}
X	if( !host ) {
X		char hostname[MAXHOSTNAMELEN];
X		if( gethostname( hostname, MAXHOSTNAMELEN )) {
X			perror( "client_example" );
X			err.fatal( "system error" );
X		}
X		host = hostname;
X	}
X
X	client clnt( host, 1, 1 );
X
X	int answer;
X	u_char value;
X	for( ;; ) {
X		cout << "you can: [1]next, [2]exit ===>" << flush;
X		cin >> answer;
X		cout << endl;
X		switch( answer ) {
X		case 1:
X			if( clnt.rexec( 1,
X						    xdr::Xvoid, 0,
X						    xdr::Xuchar, (char*)&value,
X						    sizeof(u_char) ))
X				err.fatal( "connection error" );
X
X			cout << "client::main:" << value << endl;
X			break;
X		case 2:
X			char* msg = strdup( "client says, \"die\"" );
X			(void)clnt.rexec( 2,
X							  xdr::Xwrapstring, (char*)&msg,
X							  xdr::Xvoid, 0,
X							  1 );
X			exit( 0 );
X			break;
X		}
X	}
X};
END_OF_FILE
if test 1318 -ne `wc -c <'client_example.cc'`; then
    echo shar: \"'client_example.cc'\" unpacked with wrong size!
fi
# end of 'client_example.cc'
fi
if test -f 'server.cc' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'server.cc'\"
else
echo shar: Extracting \"'server.cc'\" \(5561 characters\)
sed "s/^X//" >'server.cc' <<'END_OF_FILE'
X// $Id: server.cc,v 3.0 91/06/19 15:21:06 warsaw Exp $
X//
X// $Log:	server.cc,v $
X// Revision 3.0  91/06/19  15:21:06  warsaw
X// Bumping to Revision 3.0 
X// 
X// Revision 2.0  91/06/19  15:20:14  warsaw
X// Bumping to Revision 2.0 
X// 
X// Revision 1.3  91/06/18  13:03:16  warsaw
X// need to #include libc.h since error.hh no longer includes it
X// 
X// Revision 1.2  91/05/23  11:26:07  warsaw
X// in svc_register, I was registering the wrong callback function
X// (server::dispatch) which was crashing process. MUST register 
X// server::rpc_callback instead (and this MUST be a static member function)
X// 
X// Revision 1.1  91/05/23  09:52:34  warsaw
X// Initial revision
X// 
X
Xstatic const char
Xrcsid[]="$Id: server.cc,v 3.0 91/06/19 15:21:06 warsaw Exp $";
X
X#include "server.hh"
X#include <strstream.h>						 // for ostrstream class
X#include <rpc/pmap_clnt.h>					 // for pmap_unset()
X#include <sys/types.h>						 // sys/sockets.h needs this
X#include <sys/socket.h>						 // for socket stuff
X#include <libc.h>							 // for bzero
X
X
X// ======================================================================
X// ctor and dtor
X
Xserver::server( u_long prognum, u_long versnum )
X	: transp( 0 ),
X	  StartedByINETD_p( 0 ),
X	  head( 0 )
X{
X	error err( "rpc server:" );
X	if( instance )
X		err.fatal( "already instantiated." );
X
X	instance = this;
X
X		// check to see if inetd started up the server process. if so,
X		// it would pass the socket on file descriptor 0.
X	sockaddr_in saddr;
X	int sz = sizeof( saddr );				 // for getsockname()
X	int sock=0;								 // socket number
X	int proto=IPPROTO_TCP;					 // protocol (always TPC)
X	int fdtype=0;							 // file descriptor type
X	
X	if( !getsockname( sock, (sockaddr*)&saddr, &sz )) {
X
X			// we must have been started up by inetd
X		if( saddr.sin_family != AF_INET )
X			err.fatal( "not internet address family" );
X
X			// get socket type
X		sz = sizeof( int );
X		if( getsockopt( sock, SOL_SOCKET, SO_TYPE, (char*)&fdtype, &sz )) {
X			perror( "rpc server: " );
X			err.fatal( "couldn't get socket type." );
X		}
X		StartedByINETD_p = 1;
X
X	} else {
X			// we must have been started up manually, so we close any
X			// previous portmap registrations that may be hanging
X			// around (probably due to the untimely death of this
X			// server process).
X		sock = RPC_ANYSOCK;
X		(void) pmap_unset( prognum, versnum );
X	}
X
X		// currently, this stuff is hard-coded to work only with tcp
X		// transport protocol. I suppose we could eventually use udp
X		// also, but for now we need the extra reliance and message
X		// lengths > 8k bytes.
X	if( !fdtype || fdtype == SOCK_STREAM ) {
X		if( (transp=svctcp_create( sock, 0, 0 )) == NULL )
X			err.fatal( "cannot create tcp service." );
X
X		if( !svc_register( transp, prognum, versnum,
X						   (void (*)(...))server::rpc_callback,
X						   proto )) {
X			ostrstream oss;
X			oss << "unable to register program " << prognum
X				<< ", version " << versnum << char(0);
X			err.fatal( oss.str() );
X		}
X	} else
X		err.fatal( "could not create a transport handle." );
X};
X
X
X// ======================================================================
X// register a callback procedure
X
Xvoid server::regproc( u_long procedure, rpc_handler callback,
X					  xdrproc_t in_converter, int indata_size,
X					  xdrproc_t out_converter, int outdata_size )
X{
X	error err( "server::regproc()" );
X
X		// first see if the procedure number has already been
X		// registered. if so, just change it proctable entries
X	for( ProcTableEntry* q=head; q && q->procnum != procedure; q=q->next );
X	if( q ) {
X		q->inproc = in_converter;
X		q->outproc = out_converter;
X		q->insz = indata_size;
X		q->outsz = outdata_size;
X		q->callback = callback;
X		return;
X	}
X
X	ProcTableEntry* p = new ProcTableEntry;
X	err.memory( p );
X
X	p->procnum = procedure;
X	p->inproc = in_converter;
X	p->outproc = out_converter;
X	p->insz = indata_size;
X	p->outsz = outdata_size;
X	p->callback = callback;
X	p->next = 0;
X
X		// is this the first proc entry in the table?
X	if( !head ) {
X		head = p;
X		return;
X	}
X
X		// add this procedure to the end of the list
X	for( q=head; q->next; q=q->next );
X	q->next = p;
X};
X
X
X// ======================================================================
X// dispatch to a callback
X
Xvoid server::dispatch( svc_req* request, SVCXPRT* transp )
X{
X	error err( "server::dispatch()" );
X	const u_long rexec = request->rq_proc;
X
X		// for NULLPROC, just send ack
X	if( rexec == NULLPROC ) {
X		(void) svc_sendreply( transp, (xdrproc_t)xdr_void, (char*)NULL );
X		return;
X	}
X
X	if( !head )
X		err.fatal( "no RPC server procedures registered." );
X
X		// find the proc table entry for the requested procedure
X	for( ProcTableEntry* q=head; q && q->procnum != rexec; q=q->next );
X	if( !q ) {
X		ostrstream oss;
X		oss << "remote procedure " << rexec
X			<< " is not registered with server dispatcher." << char(0);
X		svcerr_noproc( transp );
X		err.nonfatal( oss.str() );
X		return;
X	}
X
X	char* in = new char [q->insz];
X	err.memory( in );
X	bzero( in, q->insz );
X
X	char* out = new char [q->outsz];
X	err.memory( out );
X	bzero( out, q->outsz );
X
X		// get arguments
X	if( !svc_getargs( transp, q->inproc, in )) {
X		svcerr_decode( transp );
X		delete in;
X		delete out;
X		err.nonfatal( "couldn't get arguments." );
X		return;
X	}
X
X		// dispatch to callback function
X	(this->*(q->callback))( in, out );
X
X		// send reply to client and free XDR data
X	if( !svc_sendreply( transp, q->outproc, out )) {
X		svcerr_systemerr( transp );
X		err.nonfatal( "couldn't send reply." );
X	}
X	if( !svc_freeargs( transp, q->inproc, in ))
X		err.fatal( "unable to free XDR arguments." );
X
X	delete in;
X	delete out;
X};
END_OF_FILE
if test 5561 -ne `wc -c <'server.cc'`; then
    echo shar: \"'server.cc'\" unpacked with wrong size!
fi
# end of 'server.cc'
fi
if test -f 'server_example.cc' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'server_example.cc'\"
else
echo shar: Extracting \"'server_example.cc'\" \(807 characters\)
sed "s/^X//" >'server_example.cc' <<'END_OF_FILE'
X#include <stream.h>
X#include <server.hh>
X#include <xdr.hh>
X
X
Xclass svc : public server
X{
Xpublic:
X	svc( int pnum, int vnum ) : server( pnum, vnum ) {};
X
X	void incr( char*&, char*& out );
X	void die( char*&, char*& );
X};
X
X
Xvoid svc::incr( char*&, char*& out )
X{
X	static u_char i;
X
X	u_char* rtn = (u_char*)out;
X	*rtn = ++i;
X	cout << "svc::incr: " << *rtn << endl;
X};
X
X
Xvoid svc::die( char*& in, char*& )
X{
X	char** arg = (char**)in;
X	cout << "server exiting with client message: " << *arg << endl;
X	exit( 0 );
X};
X
X
Xint main( int, char** )
X{
X	svc svcobj( 1, 1 );
X
X	svcobj.regproc( 1, (rpc_handler)&svc::incr,
X				    xdr::Xvoid, 1,
X				    xdr::Xuchar, sizeof(u_char) );
X
X	svcobj.regproc( 2, (rpc_handler)&svc::die,
X				    xdr::Xwrapstring, sizeof(char*),
X				    xdr::Xvoid, 1 );
X
X	svcobj.run();
X	exit( 1 );
X};
END_OF_FILE
if test 807 -ne `wc -c <'server_example.cc'`; then
    echo shar: \"'server_example.cc'\" unpacked with wrong size!
fi
# end of 'server_example.cc'
fi
if test -f 'xdr.cc' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'xdr.cc'\"
else
echo shar: Extracting \"'xdr.cc'\" \(4042 characters\)
sed "s/^X//" >'xdr.cc' <<'END_OF_FILE'
X// $Id: xdr.cc,v 3.0 91/06/19 15:21:09 warsaw Exp $
X//
X// $Log:	xdr.cc,v $
X// Revision 3.0  91/06/19  15:21:09  warsaw
X// Bumping to Revision 3.0 
X// 
X// Revision 2.0  91/06/19  15:20:19  warsaw
X// Bumping to Revision 2.0 
X// 
X// Revision 1.7  91/06/10  13:55:20  warsaw
X// added Xchar and Xuchar
X// 
X// Revision 1.6  91/06/05  17:09:33  warsaw
X// optimized xdr_stringclass when string object is null.
X// 
X// Revision 1.5  91/06/05  16:47:12  warsaw
X// fixed xdr_stringclass to correctly handle "null" string
X// objects.  cannot just pass a (char*)0 to xdr_string so we first send a
X// u_char indicating nullness of following string.
X// 
X// Revision 1.4  91/06/04  12:09:10  warsaw
X// fixed xdr_stringclass so that it correctly handles non-null
X// terminated strings by first (de)serializing the length, then the array
X// of bytes. still have to handle (EN/DE)CODE and FREE separately.
X// 
X// Revision 1.3  91/05/28  10:06:51  warsaw
X// fixed static initialization of static variables
X// 
X// Revision 1.2  91/05/24  16:26:56  warsaw
X// forgot to set initP after initial check
X// 
X// Revision 1.1  91/05/24  15:57:18  warsaw
X// Initial revision
X// 
X
Xstatic const char
Xrcsid[] = "$Id: xdr.cc,v 3.0 91/06/19 15:21:09 warsaw Exp $";
X
X#include "xdr.hh"
X#include <rpc/xdr.h>
X
X
X// ======================================================================
X// static variable initializations
X
Xxdrproc_t xdr::Xchar   = (xdrproc_t) xdr_char;
Xxdrproc_t xdr::Xshort  = (xdrproc_t) xdr_short;
Xxdrproc_t xdr::Xint    = (xdrproc_t) xdr_int;
Xxdrproc_t xdr::Xlong   = (xdrproc_t) xdr_long;
Xxdrproc_t xdr::Xuchar  = (xdrproc_t) xdr_u_char;
Xxdrproc_t xdr::Xushort = (xdrproc_t) xdr_u_short;
Xxdrproc_t xdr::Xuint   = (xdrproc_t) xdr_u_int;
Xxdrproc_t xdr::Xulong  = (xdrproc_t) xdr_u_long;
Xxdrproc_t xdr::Xfloat  = (xdrproc_t) xdr_float;
Xxdrproc_t xdr::Xdouble = (xdrproc_t) xdr_double;
X
Xxdrproc_t xdr::Xenum_t = (xdrproc_t) xdr_enum;
Xxdrproc_t xdr::Xbool_t = (xdrproc_t) xdr_bool;
Xxdrproc_t xdr::Xvoid   = (xdrproc_t) xdr_void;
X
Xxdrproc_t xdr::Xwrapstring  = (xdrproc_t) xdr_wrapstring;
Xxdrproc_t xdr::Xstringclass = (xdrproc_t) xdr::xdr_stringclass;
Xxdrproc_t xdr::Xopaque      = (xdrproc_t) xdr::xdr_Opaque;
X
X
X// ======================================================================
X// non standard XDR functions
X
Xbool_t xdr::xdr_stringclass( XDR* xdrs, string* obj )
X{
X		// we need these because we must pass address to the
X		// underlying xdr routines. we cannot directly access the data
X		// members of class string and we cannot get an address of a
X		// non-lvalue (which member functions return).
X	static char* data;
X	static u_int size;
X
X		// this u_char is a null indicator. if data==(char*)0 or
X		// size==0, then obj is the "null" string and we must do some
X		// special case serializing/deserializing
X	static u_char null_p;
X
X	switch( xdrs->x_op ) {
X	case XDR_ENCODE:
X		null_p = (obj->null_p()) ? 1 : 0;
X
X		if( !xdr_u_char( xdrs, &null_p ))
X			return( FALSE );
X
X			// if the object is null, just send the null indicator
X		if( null_p ) break;
X
X		data = (char*)(*obj);
X		size = (u_int)obj->size();
X
X		if( !xdr_u_int( xdrs, &size ))
X			return( FALSE );
X
X		if( !xdr_string( xdrs, &data, size ))
X			return( FALSE );
X
X		break;
X	case XDR_DECODE:
X		data = 0;
X
X		if( !xdr_u_char( xdrs, &null_p ))
X			return( FALSE );
X
X			// if the null indicator is non-zero, then nothing else is
X			// being sent.
X		if( null_p ) {
X			(*obj) = null;
X			break;
X		}
X
X		if( !xdr_u_int( xdrs, &size ))
X			return( FALSE );
X
X		if( !xdr_string( xdrs, &data, size ))
X			return( FALSE );
X
X		(*obj) = string( data, size );
X		break;
X	case XDR_FREE:
X		if( !xdr_u_char( xdrs, &null_p ))
X			return( FALSE );
X
X		if( null_p ) break;
X
X		if( !xdr_u_int( xdrs, &size ))
X			return( FALSE );
X
X		if( !xdr_string( xdrs, &data, size ))
X			return( FALSE );
X
X		break;
X	}
X	return( TRUE );
X};
X
X
Xbool_t xdr::xdr_Opaque( XDR* xdrs, xdrOpaque* obj )
X{
X	if( !xdr_u_int( xdrs, &obj->length ))
X		return( FALSE );
X	if( !xdr_opaque( xdrs, (char*)&obj->handle, obj->length ))
X		return( FALSE );
X
X	return( TRUE );
X};		
END_OF_FILE
if test 4042 -ne `wc -c <'xdr.cc'`; then
    echo shar: \"'xdr.cc'\" unpacked with wrong size!
fi
# end of 'xdr.cc'
fi
echo shar: End of shell archive.
exit 0

furr@mdavcr.UUCP (Steven Furr) (06/22/91)

In article <1991Jun19.192025.14683@colorado.edu>, jeffm@noisette.colorado.edu (Jeff McWhirter) writes:
> We have a need for  an   O-O  rpc mechanism in C++ for
> Sun RPC.  It would be nice to support blocking and non-blocking calls,
> xdr routines for passing data, ...?
> It  would also be nice if something like this has been done so we won't
> have to do it.
> Has anyone heard  of or built  such a mechanism???
> 

I saw another that was just posted, but I thought I might describe my own anyway
in case there is interest.  Sorry I can't send out code without approval from
higher-ups.  If you anyone is interested, I will seek approval.

The logical data model for the library is this:

       Sender                            Receiver
	 |                                  |
	 |                                  |
	 -                                  -
	 |                                  |
      Protocol                           Protocol
       Sender                            Receiver
					    ^
					    |
					    v
					 Registry
					    ^
					    |
					    v
					    v
			Protocol <---->> Protocol <<----> Dispatcher
			                 Service
					    ^
					    |
					    v
			Packet   <---->    PAD

The sender and receiver are the classes that encapsulate the transport
mechanism, in this case just TCP.  The ProtocolSender and ProtocolReceiver are
designed to implement a particular protocol.  They can use a different transport
by changing which sender or receiver class they inherit from.

The ProtocolReceiver looks up requests that arrive in the registry to find what
PAD and dispatcher to use.  The PAD is where the XDR routines are kept.
New dispatcher classes are derived to implement each type of service.  The 
dispatcher class provides processor() and post_processor() methods which are
called before and after a reply is sent, respectively.  Messages can be round-
trip or asynchronous; this detail is handled by ProtocolSender and
ProtocolReceiver.

Like everybody else using RPC, I had to tweak the header files. I also made
a minor extension to RPC allowing client data on the svc routines so I could
look up the ProtocolReceiver efficiently.

Contact me for further information.


--
Steve Furr ( furr@mda.ca )

Lesser artists borrow,
great artists steal.

	- blatantly stolen from
	  Igor Stravinsky

djones@megatest.UUCP (Dave Jones) (06/25/91)

A few years ago, I did built a remote object facility using RPC. It's
written in C.

I didn't use much of the RPC layer per se, but because of a pre-existing
application, I did use the underlying XDR.

My system, which is still in use, centers around a daemon which performs
name-server kinds of services.  Server programs in the domain must register
with this daemon to advertize either that they contain static
object, or that they can construct a new object of a given class.
This rendesvous also creates a map from class-names and member-function
names to numbers, which are used in the actual transactions between client and
server. Client programs request "handles" on objects, which are thereafter
used when invoking member-functions in the server. The handle is an
"opaque" pointer to a data-structure describing the network connection,
the member-function info, et cetera. If the request is for a new object,
the daemon automatically invokes a constructor registered by
the server, which returns info to identify the new object subsequently.
After the initial connection, the daemon does not take part in the further
transactions. Library code on the client side does a name-to-number
translation which users do not need to know about. It is really very easy
to use.

Now the bad news.

Because of another existing application, I had to hack the Sun RPC library
so that calls could be made which did not block awaiting an acknowlegement.
Or at least I thought I had to. It would have been better perhaps to have
used an "almost always ready" daemon to queue up the rpc calls to this one
application, but at the time we needed all the speed we could get, so I
let the TCP layer handle the queuing.

I've run into trouble now because yet another application uses the
standard Sun release of RPC independantly from my hacked version. (It
should not, because the standard server makes resources available on
a machine-by-machine basis. There is no other concept of a process-domain,
which makes it impossible to run two of these multi-process programs on
the same machine. But that's another story.) The problem I am facing right now
is that Sun has made an incompatible change to the semantics of XDR as
released for the SPARC, removing the documented variable "svc_fds". I think
the mc68 version that we have still uses the variable. I haven't yet decided
what I'm going to do about that.

If I had it to do again, I would not use RPC or XDR. Neither protocol
brings enough to the party to justify its existence. Of course I would
also use C++ rather than C. When I did this before, C++ was not available.
Rather than using ascii names, I probably would come up with some kind
of automated "stub-procedure" building program, to build member-functions
that look to the user as though they are the regular kind. Their "this"
pointer would be the handle which I described.

jhc@ivan.uucp (James H. Coombs) (06/25/91)

In article <1991Jun19.192025.14683@colorado.edu> jeffm@noisette.colorado.edu (Jeff McWhirter) writes:
>We have a need for  an   O-O  rpc mechanism in C++ for
>Sun RPC.  It would be nice to support blocking and non-blocking calls,
>xdr routines for passing data, ...?
>It  would also be nice if something like this has been done so we won't
>have to do it.
>Has anyone heard  of or built  such a mechanism???

I have developed an RPC++ on top of Sun's RPC.  RPC++ consists of two
major components:

	1. A C++ class definition parser generates a full set of XDR
	routines.  This set includes both the pointer and reference xdr
	routines.  It supports multiple inheritance.  A keyword can be
	applied to class fields that should not be transported.  Etc.

	2. I revised rpcgen to generate C++ classes and method
	implementations.  To implement a server, the developer uses the
	standard RPC program syntax, compiles the RPC program with the
	revised rpcgen, and subclasses the generated server/client.
	The C++ classes then have such methods as ClntInit/SvcInit and
	ClntTerm/SvcTerm.  Given a method such as RegisterQuery, the
	client simply invokes the method RegisterQuery with the C++
	Query object; the server overrides the base class and
	implements any required functionality.

Other features:

	1. RPC++ supports both blocking and asynchronous I/O.  There is
	a separate library to implement async. I/O for Motif
	applications.

	2. Built-in error handling.  I also developed an error handling
	package.  All RPC++ methods return a subclass of an error
	object, which contains a code and a message.  The RPC++
	framework automatically conveys error information from the
	server back to the client and loads the data into the client's
	error handler.  The application writer need only check the code
	and invoke the display routine.

	3. Flexible program number assignment.  The application writer
	specifies the name of an environment variable that can be set
	to override the default program number.

	4. Easy definition of callbacks.  The RPC++ library contains
	methods for acquiring transient numbers.  To register a
	callback, the user need only use a manifest constant in place
	of the program number and then initialize the program.

This is a quick overview of RPC++.  It has been very successful here.
I worked at the socket level for three years, and I would not hesitate
to take the time to re-implement RPC++ before working at the socket
level again.


Availability:

This is a research institute.  We do not create products.  We also
cannot give things away.  We have licensed software in the past and
have discussed licensing RPC++ to a couple of companies.  In any
licensing agreement, we would want to recover our costs for licensing
and distribution as well as some of the development costs.  We also
have research partnership plans for those who want to fund our research
in return for access to the ideas, etc.  I do not handle the business
end, so I do not know how much a reasonable agreement would cost.  I am
also in the middle of writing a grant proposal, so I am reluctant to
encourage people to send me mail for more information.  But, if there
is a reasonable chance that your organization can make a fair licensing
or funding agreement, I would be happy to bring the institute's
director into the discussion.

Cheers!  --Jim