Remote Procedure Call (RPC) is a paradigm of communication between a client and a server. Interactions consist of an alternating sequence of brief requests by the client, and brief replies by the server. Each request consists of an Operation and a set of Parameters . A reply consists of a Return Code and a set of result parameters. Site and communication failures are synchronously detected.
RPC2 consists of two relatively independent components: a
Unix-based run time library written in C, and a stub generator
RP2Gen. The run-time system is self contained and usable in the
absence of RP2Gen. The RPC2 interface, which describes the
characteristics of the procedures provided by the server to the
clients is defined in a file with extension
.rpc2
or
.rpc
. RP2Gen takes a description of the interface and
automatically produces the client and server stubs, and the code
for marshalling.
The RPC2 package is used in conjunction with the Lightweight Process (LWP) Package. The client and server code together with the respective stubs and the RPC2 and LWP packages are all linked together to generate the client and server object binaries. Although RPC2 and LWP are written in C, they have been successfully used by programs used in C++.
The concepts of ``server and ``client are quite general. A server may be the client of one or more other servers. A server may have one more Subsystems associated with it, each subsystem corresponding to a set of functionally-related procedure calls. Binding by clients is done to a host-portal-subsystem triple. There is no a priori restriction (other than resource limitations) on the number of clients a server may have, or on the number of servers a client may be connected to. Host, portal, and subsystem specifications are discriminated union types, to allow a multiplicity of representations. Thus hosts may be specified either by name or by Internet address.
Clients and servers are each assumed to be Unix processes using the LWP package. RPC2 will not work independently of the LWP package. The LWP package makes it possible for a single Unix process to contain multiple threads of control (LWPs). An RPC call is synchronous with respect to an individual LWP, but it does not block the encapsulating Unix process. Although LWPs are non-preemptive, RPC2 internally yields control when an LWP is blocked awaiting a request or reply. Thus more than one LWP can be concurrently making RPC requests. There is no a priori binding of RPC connections to LWPs, or LWPs to subsystems within a client or server. Thus RPC connections, LWPs and subsystems are completely orthogonal concepts.
Since LWPs are non-preemptive, a long-running computation by an LWP will prevent the RPC2 code from getting a chance to run. This will typically manifest itself as a timeout by the client on the RPC in progress. To avoid this, the long running computation should periodically invoke @IOMGR(Poll) followed by @LWP(DispatchProcess).
For similar reasons, Unix system calls such as
read
(2)
,
sleep (3)
, and
select (2)
that may block for a long time should be avoided. Instead, use
IOMGR_Select.
RPC connections may be associated with Side-Effects to allow application-specific network optimizations to be performed. An example is the use of a specialized protocol for bulk transfer of large files. Detailed information pertinent to each type of side effect is specified in a Side Effect Descriptor .
Side effects are explicitly initiated by the server using @RPC2(InitSideEffect) and occur asynchronously. Synchronization occurs on an @RPC2(CheckSideEffect) call by the server. Each client-server connection deals with one kind of side effect; however different connections may use different kinds of side effects.
Adding support for a new type of side effect is analogous to adding a new device driver in Unix. To allow this extensibility, the RPC code has hooks at various points where side-effect routines will be called. Global tables contain pointers to these side effect routines. The basic RPC code itself knows nothing about these side-effect routines.
A Security Level is associated with each connection between a client and a server. Currently four levels of security are supported:
Authenticated in this context means that the client and server start out as mutually suspicious parties and exchange credentials during the establishment of a connection. A secret encryption key, known a priori only to the server and client is used in authentication handshakes. The secret key itself is never sent over the network. The handshake is a variant of the well-known Needham and Schroeder protocol@cite[Needham78].
Secure means that transmitted data is immune to eavesdropping and undetected corruption. This is achieved by encryption, using a session key generated during connection establishment. No attempt is made, however, to guard against traffic analysis attacks.
The RPC package makes no assumptions about the format of client identities or about the mapping between clients, servers and shared secret keys. A server-supplied procedure is invoked during the authentication sequence to validate client identities and obtain keys. The paper on authentication in the Andrew file system@cite[Satyanarayanan89a] gives a detailed example of how this functionality may be used.
In this section we walk the reader through examples of increasing complexity, thereby demonstrating the use of RPC2. There are two examples: a relatively simple one using a single LWP and exporting a single subsystem, followed by a more complex one using multiple LWPs and subsystems. There is a Makefile for both these examples in Section < @@ref > MAKEFILEXXX. Neither example uses side effects. An example using side effects can be found in Chapter XXX .
This is a very simple example that has only one subsystem telling the client the time of the day in seconds and micro seconds on the server machine.
The interface is defined in the
rtime.rpc
file.
This file is used as input to RP2Gen to generate the client and
server stubs
rtime.client.c
and
rtime_server.c.sgml
The first step at both the client and the server is initialization of the LWP and the RPC2 package. This is done by calling the @LWP(Init) and @RPC2(Init) calls respectively. It is necessary that the LWP package be initialized before the RPC2 package.
The client, after initialization, binds to the server by calling RPC2_NewBinding. The parameters to this call include the identity of the server, the subsytem etc. A connection id is returned to the client. The client is now ready to make RPC calls on this connection.
The server indicates its willingness to accept calls for the
rtime
subsytem by calling RPC2_Export. A filter for
the
rtime
subsytem is specified and used in awaiting
an RPC request by calling @RPC2(GetRequest). On receiving a
request, the server decodes and executes it by calling
rtime_ExecuteRequest.
rtime_ExecuteRequest,
rpcwhich is actually defined in
the server stub file calls the server code for
GetRTime
routine and sends the response to the client.
The server then goes back to waiting for more requests.
Subsystem "rtime";
#define RTIMESUBSYSID 100
#define RTIMEPORTAL 4000
GetRTime(OUT RPC2_Integer time_sec, OUT RPC2_Integer time_usec) ;
#include < stdio.h > #include < sys/types.h > #include < netinet/in.h > #include < assert.h > #include "lwp.h" #include "rpc2.h" #include "rtime.h" main(argc, argv) int argc; char *argv[]; { RPC2_Handle cid; RPC2_Integer tv_sec, tv_usec; int rc ; char msg[200]; if (argc != 2) error_report("usage: rtime < machine name > "); Init_RPC(); cid = connect_to_machine(argv[1]); /* client makes a remote procedure call to get the time on the server machine */ rc = GetRTime(cid, & tv_sec, & tv_usec) ; if (rc != RPC2_SUCCESS) { sprintf(msg, "%s\nGet remote time on machine %s failed", RPC2_ErrorMsg(rc),argv[1]); error_report(msg); } else { printf("The remote time on machine %s is\n", argv[1]); printf("tv_sec = %d and tv_usec = %d\n", tv_sec, tv_usec); }; rc = RPC2_Unbind(cid) ; if (rc != RPC2_SUCCESS) error_report("%s\nCant' close the connection!", RPC2_ErrorMsg(rc)); } ; error_report(message) char *message; { fprintf(stderr, message); fprintf(stderr, "\n"); exit(1); } Init_RPC() { PROCESS mylpid; int rc ; if (LWP_Init(LWP_VERSION, LWP_NORMAL_PRIORITY, & mylpid) != LWP_SUCCESS) error_report("Can't Initialize LWP"); /* Initialize LWP package */ rc = RPC2_Init(RPC2_VERSION, NULL, NULL, 0, -1, NULL) ; if (rc != RPC2_SUCCESS) error_report("%s\nCan't Initialize RPC2", RPC2_ErrorMsg(rc)); /* Initialize RPC2 package */ } /* This routine tries to establish a connection to the server running on machine machine_name */ RPC2_Handle connect_to_machine(machine_name) char *machine_name; { RPC2_HostIdent hid; RPC2_PortalIdent pid; RPC2_SubsysIdent sid; RPC2_Handle cid; char msg[100]; int rc; RPC2_BindParms bp; hid.Tag = RPC2_HOSTBYNAME; if (strlen(machine_name) > = 64) { sprintf(msg, "Machine name %s too long!", machine_name); error_report(msg); } ; strcpy(hid.Value.Name, machine_name); pid.Tag = RPC2_PORTALBYINETNUMBER; pid.Value.InetPortNumber = htons(RTIMEPORTAL); sid.Tag = RPC2_SUBSYSBYID; sid.Value.SubsysId = RTIMESUBSYSID; bp.SecurityLevel = RPC2_OPENKIMONO; bp.EncryptionType = NULL; bp.SideEffectType = NULL; bp.ClientIdent = NULL; bp.SharedSecret = NULL; rc = RPC2_NewBinding( & hid, & pid, & sid, & bp, & Gcid); if (rc != RPC2_SUCCESS) { sprintf(msg, "%s\nCan't connect to machine %s", RPC2_ErrorMsg(rc), machine_name); error_report(msg); }; return cid; } ;
#include < stdio.h > #include < sys/types.h > #include < netinet/in.h > #include < assert.h > #include < sys/time.h > #include "lwp.h" #include "rpc2.h" #include "rtime.h" main() { RPC2_Handle cid; RPC2_RequestFilter reqfilter; RPC2_PacketBuffer *reqbuffer; int rc; Init_RPC(); reqfilter.FromWhom = ANY; reqfilter.OldOrNew = OLDORNEW; reqfilter.ConnOrSubsys.SubsysId = RTIMESUBSYSID; /* loop forever, wait for the client to call for service */ for ( ; ; ) { rc = RPC2_GetRequest( & reqfilter, & cid, & reqbuffer, NULL, NULL, NULL) ; if (rc != RPC2_SUCCESS) fprintf(stderr, RPC2_ErrorMsg(rc)); rc = rtime_ExecuteRequest(cid, reqbuffer); if (rc != RPC2_SUCCESS) fprintf(stderr, RPC2_ErrorMsg(rc)); }; }; error_report(message) char *message; { fprintf(stderr, message); fprintf(stderr, "\n"); exit(1); } Init_RPC() { PROCESS mylpid; RPC2_PortalIdent pid, *pids; RPC2_SubsysIdent sid; int rc; char msg[100]; /* Initialize LWP package */ if (LWP_Init(LWP_VERSION, LWP_NORMAL_PRIORITY, & mylpid) != LWP_SUCCESS) error_report("Can't Initialize LWP") ; /* Initialize RPC2 package */ pids = & pid; pid.Tag = RPC2_PORTALBYINETNUMBER; pid.Value.InetPortNumber = htons(RTIMEPORTAL); rc = RPC2_Init(RPC2_VERSION, NULL, & pids, 1, -1, NULL); if (rc != RPC2_SUCCESS) { sprintf(msg, "%s\nCan't Initialize RPC2", RPC2_ErrorMsg(rc)); error_report(msg); }; sid.Tag = RPC2_SUBSYSBYID; sid.Value.SubsysId = RTIMESUBSYSID; rc = RPC2_Export( & sid) != RPC2_SUCCESS ; if (rc != RPC2_SUCCESS) { sprintf(msg, "%s\nCan't export the rtime subsystem"); error_report(msg); }; } long GetRTime(_cid, tv_sec, tv_usec) RPC2_Handle _cid; int *tv_sec; int *tv_usec; { struct timeval tp; struct timezone tzp; gettimeofday( & tp, & tzp); *tv_sec = tp.tv_sec; *tv_usec = tp.tv_usec; return RPC2_SUCCESS; }
In this example there are two subsystems: an authentication subsystem and a computation subsystem. Each subsystem services a different set of calls.
In this example, two LWP processes are created at the server, one for each subsytem to be serviced. But this is not essential. An alternative would have been for a single LWP to service calls for both subsystems or for a collection of LWPs to service calls for any subsystems.
/* RPC interface specification for a trivial authentication subsystem. This is only an example: all it does is name to id and id to name conversions. */ Server Prefix "S"; Subsystem "auth"; /* Internet port number; note that this is really not part of a specific subsystem, but is part of a server; we should really have a separate ex.h file with this constant. I am being lazy here */ #define AUTHPORTAL 5000 #define AUTHSUBSYSID 100 /* The subsysid for auth subsystem */ /* Return codes from auth server */ #define AUTHSUCCESS 0 #define AUTHFAILED 1 typedef RPC2_Byte PathName[1024]; typedef RPC2_Struct { RPC2_Integer GroupId; PathName HomeDir; } AuthInfo; AuthNewConn (IN RPC2_Integer seType, IN RPC2_Integer secLevel, IN RPC2_Integer encType, IN RPC2_CountedBS cIdent) NEW_CONNECTION; AuthUserId (IN RPC2_String Username, OUT RPC2_Integer UserId); /* Returns AUTHSUCCESS or AUTHFAILED */ AuthUserName (IN RPC2_Integer UserId, IN OUT RPC2_BoundedBS Username); /* Returns AUTHSUCCESS or AUTHFAILED */ AuthUserInfo (IN RPC2_Integer UserId, OUT AuthInfo UInfo); /* Returns AUTHSUCCESS or AUTHFAILED */ AuthQuit();
/* RPC interface specification for a trivial computational subsystem. Finds squares and cubes of given numbers. */ Server Prefix "S"; Subsystem "comp"; #define COMPSUBSYSID 200 /* The subsysid for comp subsystem */ #define COMPSUCCESS 1 #define COMPFAILED 2 CompNewConn (IN RPC2_Integer seType, IN RPC2_Integer secLevel, IN RPC2_Integer encType, IN RPC2_CountedBS cIdent) NEW_CONNECTION; CompSquare (IN RPC2_Integer X); /* returns square of x */ CompCube (IN RPC2_Integer X); /* returns cube of x */ CompAge(); /* returns the age of this connection in seconds */ CompQuit();
/* exclient.c -- Trivial client to demonstrate basic RPC2 functionality */ #include < stdio.h > #include < strings.h > #include < sys/time.h > #include < sys/types.h > #include < netinet/in.h > #include < assert.h > #include < pwd.h > #include < lwp.h > #include < rpc2.h > #include "auth.h" #include "comp.h" main() { int a; char buf[100]; printf("Debug Level? (0) "); gets(buf); RPC2_DebugLevel = atoi(buf); InitRPC(); while (1) { printf("Action? (1 = New Conn, 2 = Auth Request, 3 = Comp Request) "); gets(buf); a = atoi(buf); switch(a) { case 1: NewConn(); continue; case 2: Auth(); continue; case 3: Comp(); continue; default: continue; } } } NewConn() { char hname[100], buf[100]; int newcid, rc; RPC2_HostIdent hident; RPC2_PortalIdent pident; RPC2_SubsysIdent sident; printf("Remote host name? "); gets(hident.Value.Name); hident.Tag = RPC2_HOSTBYNAME; printf("Subsystem? (Auth = %d, Comp = %d) ", AUTHSUBSYSID, COMPSUBSYSID); gets(buf); sident.Value.SubsysId = atoi(buf); sident.Tag = RPC2_SUBSYSBYID; pident.Tag = RPC2_PORTALBYINETNUMBER; pident.Value.InetPortNumber = htons(AUTHPORTAL); /* same as COMPPORTAL */ rc = RPC2_Bind(RPC2_OPENKIMONO, NULL, & hident, & pident, & sident, NULL, NULL, NULL, & newcid); if (rc == RPC2_SUCCESS) printf("Binding succeeded, this connection id is %d\n", newcid); else printf("Binding failed: %s\n", RPC2_ErrorMsg(rc)); } Auth() { RPC2_Handle cid; int op, rc, uid; char name[100], buf[100]; AuthInfo ainfo; RPC2_BoundedBS bbs; printf("Connection id? "); gets(buf); cid = atoi(buf); printf("Operation? (1 = Id, 2 = Name, 3 = Info, 4 = Quit) "); gets(buf); op = atoi(buf); switch(op) { case 1: printf("Name? "); gets(name); rc = AuthUserId(cid, name, & uid); if (rc == AUTHSUCCESS) printf("Id = %d\n", uid); else if (rc == AUTHFAILED) printf("Bogus user name\n"); else printf("Call failed -- > %s\n", RPC2_ErrorMsg(rc)); break; case 2: printf("Id? "); gets(buf); uid = atoi(buf); bbs.MaxSeqLen = sizeof(name); bbs.SeqLen = 0; bbs.SeqBody = (RPC2_ByteSeq) name; rc = AuthUserName(cid, uid, & bbs); if (rc == AUTHSUCCESS) printf("Name = %s\n", bbs.SeqBody); else if (rc == AUTHFAILED) printf("Bogus user id\n"); else printf("Call failed -- > %s\n", RPC2_ErrorMsg(rc)); break; case 3: printf("Id? "); gets(buf); uid = atoi(buf); rc = AuthUserInfo(cid, uid, & ainfo); if (rc == AUTHSUCCESS) printf("Group = %d Home = %s\n", ainfo.GroupId, ainfo.HomeDir); else if (rc == AUTHFAILED) printf("Bogus user id\n"); else printf("Call failed -- > %s\n", RPC2_ErrorMsg(rc)); break; case 4: rc = AuthQuit(cid); if (rc != AUTHSUCCESS) printf("Call failed -- > %s\n", RPC2_ErrorMsg(rc)); RPC2_Unbind(cid); break; } } Comp() { RPC2_Handle cid; int op, rc, x; char buf[100]; printf("Connection id? "); gets(buf); cid = atoi(buf); printf("Operation? (1 = Square, 2 = Cube, 3 = Age, 4 = Quit) "); gets(buf); op = atoi(buf); switch(op) { case 1: printf("x? "); gets(buf); x = atoi(buf); rc = CompSquare(cid, x); if (rc > 0) printf("x**2 = %d\n", rc); else printf("Call failed -- > %s\n", RPC2_ErrorMsg(rc)); break; case 2: printf("x? "); gets(buf); x = atoi(buf); rc = CompCube(cid, x); if (rc > 0) printf("x**3 = %d\n", rc); else printf("Call failed -- > %s\n", RPC2_ErrorMsg(rc)); break; case 3: rc = CompAge(cid); if (rc > 0) printf("Age of connection = %d seconds\n", rc); else printf("Call failed -- > %s\n", RPC2_ErrorMsg(rc)); break; case 4: rc = CompQuit(cid); if (rc < 0) printf("Call failed -- > %s\n", RPC2_ErrorMsg(rc)); RPC2_Unbind(cid); break; } } /* RPC Initialization and Error handling */ InitRPC() { int mylpid = -1; assert(LWP_Init(LWP_VERSION, LWP_NORMAL_PRIORITY, & mylpid) == LWP_SUCCESS); assert (RPC2_Init(RPC2_VERSION, 0, NULL, 1, -1, NULL) == RPC2_SUCCESS); } iopen(){}
#include < stdio.h > #include < strings.h > #include < sys/signal.h > #include < sys/time.h > #include < sys/types.h > #include < netinet/in.h > #include < assert.h > #include < pwd.h > #include < lwp.h > #include < rpc2.h > #include "auth.h" #include "comp.h" /* This data structure provides per-connection info. It is created on every new connection and ceases to exist after AuthQuit(). */ struct UserInfo { int Creation; /* Time at which this connection was created */ /* other fields would go here */ }; int NewCLWP(), AuthLWP(), CompLWP(); /* bodies of LWPs */ void DebugOn(), DebugOff(); /* signal handlers */ main() { int mypid; signal(SIGEMT, DebugOn); signal(SIGIOT, DebugOff); InitRPC(); LWP_CreateProcess(AuthLWP, 4096, LWP_NORMAL_PRIORITY, "AuthLWP", NULL, & mypid); LWP_CreateProcess(CompLWP, 4096, LWP_NORMAL_PRIORITY, "CompLWP", NULL, & mypid); LWP_WaitProcess(main); /* sleep here forever; no one will ever wake me up */ } AuthLWP(p) char *p; /* single parameter passed to LWP_CreateProcess() */ { RPC2_RequestFilter reqfilter; RPC2_PacketBuffer *reqbuffer; RPC2_Handle cid; int rc; char *pp; /* Set filter to accept auth requests on new or existing connections */ reqfilter.FromWhom = ONESUBSYS; reqfilter.OldOrNew = OLDORNEW; reqfilter.ConnOrSubsys.SubsysId = AUTHSUBSYSID; while(1) { cid = 0; rc = RPC2_GetRequest( & reqfilter, & cid, & reqbuffer, NULL, NULL, NULL, NULL); if (rc < RPC2_WLIMIT) HandleRPCError(rc, cid); rc = auth_ExecuteRequest(cid, reqbuffer); if (rc < RPC2_WLIMIT) HandleRPCError(rc, cid); pp = NULL; if (RPC2_GetPrivatePointer(cid, & pp) != RPC2_SUCCESS || pp == NULL) RPC2_Unbind(cid); /* This was almost certainly an AuthQuit() call */ } } CompLWP(p) char *p; /* single parameter passed to LWP_CreateProcess() */ { RPC2_RequestFilter reqfilter; RPC2_PacketBuffer *reqbuffer; RPC2_Handle cid; int rc; char *pp; /* Set filter to accept comp requests on new or existing connections */ reqfilter.FromWhom = ONESUBSYS; reqfilter.OldOrNew = OLDORNEW; reqfilter.ConnOrSubsys.SubsysId = COMPSUBSYSID; while(1) { cid = 0; rc = RPC2_GetRequest( & reqfilter, & cid, & reqbuffer, NULL, NULL, NULL, NULL); if (rc < RPC2_WLIMIT) HandleRPCError(rc, cid); rc = comp_ExecuteRequest(cid, reqbuffer); if (rc < RPC2_WLIMIT) HandleRPCError(rc, cid); pp = NULL; if (RPC2_GetPrivatePointer(cid, & pp) != RPC2_SUCCESS || pp == NULL) RPC2_Unbind(cid); /* This was almost certainly an CompQuit() call */ } } /* Bodies of Auth RPC routines */ S_AuthNewConn(cid, seType, secLevel, encType, cIdent) RPC2_Handle cid; RPC2_Integer seType, secLevel, encType; RPC2_CountedBS *cIdent; { struct UserInfo *p; p = (struct UserInfo *) malloc(sizeof(struct UserInfo)); RPC2_SetPrivatePointer(cid, p); p- > Creation = time(0); } S_AuthQuit(cid) /* Get rid of user state; note that we do not do RPC2_Unbind() here, because this request itself has to complete. The invoking server LWP therefore checks to see if this connection can be unbound. */ { struct UserInfo *p; RPC2_GetPrivatePointer(cid, & p); assert(p != NULL); /* we have a bug then */ free(p); RPC2_SetPrivatePointer(cid, NULL); return(AUTHSUCCESS); } S_AuthUserId(cid, userName, userId) char *userName; int *userId; { struct passwd *pw; if ((pw = getpwnam(userName)) == NULL) return(AUTHFAILED); *userId = pw- > pw_uid; return(AUTHSUCCESS); } S_AuthUserName(cid, userId, userName) int userId; RPC2_BoundedBS *userName; { struct passwd *pw; if ((pw = getpwuid(userId)) == NULL) return(AUTHFAILED); strcpy(userName- > SeqBody, pw- > pw_name); /* we hope the buffer is big enough */ userName- > SeqLen = 1 + strlen(pw- > pw_name); return(AUTHSUCCESS); } S_AuthUserInfo(cid, userId, uInfo) int userId; AuthInfo *uInfo; { struct passwd *pw; if ((pw = getpwuid(userId)) == NULL) return(AUTHFAILED); uInfo- > GroupId = pw- > pw_gid; strcpy(uInfo- > HomeDir, pw- > pw_dir); return(AUTHSUCCESS); } /* Bodies of Comp RPC routines */ S_CompNewConn(cid, seType, secLevel, encType, cIdent) RPC2_Handle cid; RPC2_Integer seType, secLevel, encType; RPC2_CountedBS *cIdent; { struct UserInfo *p; p = (struct UserInfo *) malloc(sizeof(struct UserInfo)); RPC2_SetPrivatePointer(cid, p); p- > Creation = time(0); } S_CompQuit(cid) /* Get rid of user state; note that we do not do RPC2_Unbind() here, because this request itself has to complete. The invoking server LWP therefore checks to see if this connection can be unbound. */ { struct UserInfo *p; RPC2_GetPrivatePointer(cid, & p); assert(p != NULL); /* we have a bug then */ free(p); RPC2_SetPrivatePointer(cid, NULL); return(0); } S_CompSquare(cid, x) int x; { return(x*x); } S_CompCube(cid, x) RPC2_Handle cid; int x; { return(x*x*x); } S_CompAge(cid, x) RPC2_Handle cid; int x; { struct UserInfo *p; assert(RPC2_GetPrivatePointer(cid, & p) == RPC2_SUCCESS); return(time(0) - p- > Creation); } /* iopen() is a system call created at the ITC; put a dummy here for other sites */ iopen(){} /* RPC Initialization and Error handling */ InitRPC() { int mylpid = -1; RPC2_PortalIdent portalid, *portallist[1]; RPC2_SubsysIdent subsysid; long rc; assert(LWP_Init(LWP_VERSION, LWP_NORMAL_PRIORITY, & mylpid) == LWP_SUCCESS); portalid.Tag = RPC2_PORTALBYINETNUMBER; portalid.Value.InetPortNumber = htons(AUTHPORTAL); portallist[0] = & portalid; rc = RPC2_Init(RPC2_VERSION, 0, portallist, 1, -1, NULL); if (rc != RPC2_SUCCESS) { fprintf(stderr, "RPC2_Init: failed with %ld\n", rc); exit(1); } subsysid.Tag = RPC2_SUBSYSBYID; subsysid.Value.SubsysId = AUTHSUBSYSID; assert(RPC2_Export( & subsysid) == RPC2_SUCCESS); subsysid.Value.SubsysId = COMPSUBSYSID; assert(RPC2_Export( & subsysid) == RPC2_SUCCESS); } HandleRPCError(rCode, connId) int rCode; RPC2_Handle connId; { fprintf(stderr, "exserver: %s\n", RPC2_ErrorMsg(rCode)); if (rCode < RPC2_FLIMIT & & connId != 0) RPC2_Unbind(connId); } void DebugOn() { RPC2_DebugLevel = 100; } void DebugOff() { RPC2_DebugLevel = 0; }
HFILES = $(INCLDIR)/rpc2.h $(INCLDIR)/se.h $(INCLDIR)/lwp.h LIBS = -lrpc2 -lse -loldlwp examples: authcomp_clnt authcomp_srv rtime_clnt rtime_srv rcat_clnt rcat_srv\ multi_rtime_clnt multi_rcat_clnt OCLIENT1 = rtime_clnt.o rtime.client.o OSERVER1 = rtime_srv.o rtime.server.o OCLIENT2 = rcat_clnt.o rcat.client.o OSERVER2 = rcat_srv.o rcat.server.o OCLIENT3 = authcomp_clnt.o auth.client.o comp.client.o OSERVER3 = authcomp_srv.o auth.server.o comp.server.o OCLIENT4 = multi_rtime_clnt.o rtime.client.o OCLIENT5 = multi_rcat_clnt.o rcat.client.o rtime_clnt: $(OCLIENT1) $(CC) $(CFLAGS) $(OCLIENT1) $(LIBS) -o rtime_clnt rtime_srv: $(OSERVER1) $(CC) $(CFLAGS) $(OSERVER1) $(LIBS) -o rtime_srv rcat_clnt: $(OCLIENT2) $(CC) $(CFLAGS) $(OCLIENT2) $(LIBS) -o rcat_clnt rcat_srv: $(OSERVER2) $(CC) $(CFLAGS) $(OSERVER2) $(LIBS) -o rcat_srv authcomp_clnt: $(OCLIENT3) $(CC) $(CFLAGS) $(OCLIENT3) $(LIBS) -o authcomp_clnt authcomp_srv: $(OSERVER3) $(CC) $(CFLAGS) $(OSERVER3) $(LIBS) -o authcomp_srv multi_rtime_clnt: $(OCLIENT4) $(CC) $(CFLAGS) $(OCLIENT4) $(LIBS) -o multi_rtime_clnt multi_rcat_clnt: $(OCLIENT5) $(CC) $(CFLAGS) $(OCLIENT5) $(LIBS) -o multi_rcat_clnt $(OCLIENT1) $(OSERVER1): rtime.h $(HFILES) $(OCLIENT2) $(OSERVER2): rcat.h $(HFILES) $(OCLIENT3) $(OSERVER3): auth.h comp.h $(HFILES) auth.h auth.server.c auth.client.c: auth.rpc $(RP2GEN) $(RP2GEN) auth.rpc comp.h comp.server.c comp.client.c: comp.rpc $(RP2GEN) $(RP2GEN) comp.rpc rtime.h rtime.server.c rtime.client.c: rtime.rpc $(RP2GEN) $(RP2GEN) rtime.rpc rcat.h rcat.server.c rcat.client.c: rcat.rpc $(RP2GEN) $(RP2GEN) rcat.rpc
In chapter 2, "The RP2Gen Stub Generator," we will see how the stub generator can create such files.