Section 6.1: Introduction
- This chapter provides a sample program showing the use of Rx. Specifically, the rxdemo application, with all its support files, is documented and examined. The goal is to provide the reader with a fully-developed and operational program illustrating the use of both regular Rx remote procedure calls and streamed RPCs. The full text of the rxdemo application is reproduced in the sections below, along with additional commentary.
- Readers wishing to directly experiment with this example Rx application are encouraged to examine the on-line version of rxdemo. Since it is a program of general interest, it has been installed in the usr/contrib tree in the grand.central.org cell. This area contains user-contributed software for the entire AFS community. At the top of this tree is the /afs/grand.central.org/darpa/usr/contrib directory. Both the server-side and client-side rxdemo binaries (rxdemo server and rxdemo client, respectively) may be found in the bin subdirectory. The actual sources reside in the .site/grand.central.org/rxdemo/src subdirectory.
- The rxdemo code is composed of two classes of files, namely those written by a human programmer and those generated from the human-written code by the Rxgen tool. Included in the first group of files are:
- rxdemo.xg This is the RPC interface definition file, providing high-level definitions of the supported calls.
- rxdemo client.c: This is the rxdemo client program, calling upon the associated server to perform operations defined by rxdemo.xg.
- rxdemo server.c: This is the rxdemo server program, implementing the operations promised in rxdemo.xg.
- Makefile: This is the file that directs the compilation and installation of the rxdemo code.
- The class of automatically-generated files includes the following items:
- rxdemo.h: This header file contains the set of constant definitions present in rxdemo.xg, along with information on the RPC opcodes defined for this Rx service.
- rxdemo.cs.c: This client-side stub file performs all the marshalling and unmarshalling of the arguments for the RPC routines defined in rxdemo.xg.
- rxdemo.ss.c: This stub file similarly defines all the marshalling and unmarshalling of arguments for the server side of the RPCs, invokes the routines defined within rxdemo server.c to implement the calls, and also provides the dispatcher function.
- rxdemo.xdr.c: This module defines the routines required to convert complex user-defined data structures appearing as arguments to the Rx RPC calls exported by rxdemo.xg into network byte order, so that correct communication is guaranteed between clients and server with different memory organizations.
- The chapter concludes with a section containing sample output from running the rxdemo server and client programs.
Section 6.2: Human-Generated files
- The rxdemo application is based on the four human-authored files described in this section. They provide the basis for the construction of the full set of modules needed to implement the specified Rx service.
Section 6.2.1: Interface file: rxdemo.xg
- This file serves as the RPC interface definition file for this application. It defines various constants, including the Rx service port to use and the index of the null security object (no encryption is used by rxdemo). It defines the RXDEMO MAX and RXDEMO MIN constants, which will be used by the server as the upper and lower bounds on the number of Rx listener threads to run. It also defines the set of error codes exported by this facility. finally, it provides the RPC function declarations, namely Add() and Getfile(). Note that when building the actual function definitions, Rxgen will prepend the value of the package line in this file, namely "RXDEMO ", to the function declarations. Thus, the generated functions become RXDEMO Add() and RXDEMO Getfile(), respectively. Note the use of the split keyword in the RXDEMO Getfile() declaration, which specifies that this is a streamed call, and actually generates two client-side stub routines (see Section 6.3.1).
package RXDEMO_
%#include <rx/rx.h>
%#include <rx/rx_null.h>
%#define RXDEMO_SERVER_PORT 8000
%#define RXDEMO_SERVICE_PORT 0
%#define RXDEMO_SERVICE_ID 4
%#define RXDEMO_NULL_SECOBJ_IDX 0
%#define RXDEMO_MAX 3
%#define RXDEMO_MIN 2
%#define RXDEMO_NULL 0
%#define RXDEMO_NAME_MAX_CHARS 64
%#define RXDEMO_BUFF_BYTES 512
%#define RXDEMO_CODE_SUCCESS 0
%#define RXDEMO_CODE_CANT_OPEN 1
%#define RXDEMO_CODE_CANT_STAT 2
%#define RXDEMO_CODE_CANT_READ 3
%#define RXDEMO_CODE_WRITE_ERROR 4
Add(IN int a, int b, OUT int *result) = 1;
Getfile(IN string a_nameToRead<RXDEMO_NAME_MAX_CHARS>, OUT int *a_result)
split = 2;
Section 6.2.2: Client Program: rxdemo client.c
- The rxdemo client program, rxdemo client, calls upon the associated server to perform operations defined by rxdemo.xg. After its header, it defines a private GetIPAddress() utility routine, which given a character string host name will return its IP address.
#include <sys/types.h>
#include <netdb.h>
#include <stdio.h>
#include "rxdemo.h"
static char pn[] = "rxdemo";
static u_long GetIpAddress(a_hostName) char *a_hostName;
{
static char rn[] = "GetIPAddress";
struct hostent *hostEntP;
u_long hostIPAddr;
hostEntP = gethostbyname(a_hostName);
if (hostEntP == (struct hostent *)0) {
printf("[%s:%s] Host '%s' not found\n",
pn, rn, a_hostName);
exit(1);
}
if (hostEntP->h_length != sizeof(u_long)) {
printf("[%s:%s] Wrong host address length (%d bytes instead of
%d)",
pn, rn, hostEntP->h_length, sizeof(u_long));
exit(1);
}
bcopy(hostEntP->h_addr, (char *)&hostIPAddr, sizeof(hostIPAddr));
return(hostIPAddr);
}
- The main program section of the client code, after handling its command line arguments, starts off by initializing the Rx facility.
main(argc, argv)
int argc;
char **argv;
{
struct rx_connection *rxConnP;
struct rx_call *rxCallP;
u_long hostIPAddr;
int demoUDPPort;
struct rx_securityClass *nullSecObjP;
int operand1, operand2;
int code;
char fileName[64];
long fileDataBytes;
char buff[RXDEMO_BUFF_BYTES+1];
int currBytesToRead;
int maxBytesToRead;
int bytesReallyRead;
int getResults;
printf("\n%s: Example Rx client process\n\n", pn);
if ((argc < 2) || (argc > 3)) {
printf("Usage: rxdemo <HostName> [PortToUse]");
exit(1);
}
hostIPAddr = GetIpAddress(argv[1]);
if (argc > 2)
demoUDPPort = atoi(argv[2]);
else
demoUDPPort = RXDEMO_SERVER_PORT;
code = rx_Init(htons(demoUDPPort));
if (code) {
printf("** Error calling rx_Init(); code is %d\n", code);
exit(1);
}
nullSecObjP = rxnull_NewClientSecurityObject();
if (nullSecObjP == (struct rx_securityClass *)0) {
printf("%s: Can't create a null client-side security
object!\n", pn);
exit(1);
}
printf("Connecting to Rx server on '%s', IP address 0x%x, UDP port
%d\n", argv[1], hostIPAddr, demoUDPPort);
rxConnP = rx_NewConnection(hostIPAddr, RXDEMO_SERVER_PORT,
RXDEMO_SERVICE_ID, nullSecObjP, RXDEMO_NULL_SECOBJ_IDX);
if (rxConnP == (struct rx_connection *)0) {
printf("rxdemo: Can't create connection to server!\n");
exit(1);
} else
printf(" ---> Connected.\n");
- The rx Init() invocation initializes the Rx library and defines the desired service UDP port (in network byte order). The rxnull NewClientSecurityObject() call creates a client-side Rx security object that does not perform any authentication on Rx calls. Once a client authentication object is in hand, the program calls rx NewConnection(), specifying the host, UDP port, Rx service ID, and security information needed to establish contact with the rxdemo server entity that will be providing the service.
- With the Rx connection in place, the program may perform RPCs. The first one to be invoked is RXDEMO Add():
operand1 = 1;
operand2 = 2;
printf("Asking server to add %d and %d: ", operand1, operand2);
code = RXDEMO_Add(rxConnP, operand1, operand2, &sum);
if (code) {
printf(" // ** Error in the RXDEMO_Add RPC: code is %d\n", code);
exit(1);
}
printf("Reported sum is %d\n", sum);
- The first argument to RXDEMO Add() is a pointer to the Rx connection established above. The client-side body of the RXDEMO Add() function was generated from the rxdemo.xg interface file, and resides in the rxdemo.cs.c file (see Section 6.3.1). It gives the appearance of being a normal C procedure call.
- The second RPC invocation involves the more complex, streamed RXDEMO Getfile() function. More of the internal Rx workings are exposed in this type of call. The first additional detail to consider is that we must manually create a new Rx call on the connection.
printf("Name of file to read from server: ");
scanf("%s", fileName);
maxBytesToRead = RXDEMO_BUFF_BYTES;
printf("Setting up an Rx call for RXDEMO_Getfile...");
rxCallP = rx_NewCall(rxConnP);
if (rxCallP == (struct rx_call *)0) {
printf("** Can't create call\n");
exit(1);
}
printf("done\n");
- Once the Rx call structure has been created, we may begin executing the call itself. Having been declared to be split in the interface file, Rxgen creates two function bodies for rxdemo Getfile() and places them in rxdemo.cs.c. The first, StartRXDEMO Getfile(), is responsible for marshalling the outgoing arguments and issuing the RPC. The second, EndRXDEMO Getfile(), takes care of unmarshalling the non-streamed OUT function parameters. The following code fragment illustrates how the RPC is started, using the StartRXDEMO Getfile() routine to pass the call parameters to the server.
code = StartRXDEMO_Getfile(rxCallP, fileName);
if (code) {
printf("** Error calling StartRXDEMO_Getfile(); code is %d\n",
code);
exit(1);
}
- Once the call parameters have been shipped, the server will commence delivering the "stream" data bytes back to the client on the given Rx call structure. The first longword to come back on the stream specifies the number of bytes to follow.
- Begin reading the data being shipped from the server in response to * our setup call. The first longword coming back on the Rx call is the number of bytes to follow. It appears in network byte order, so we have to fix it up before referring to it.
bytesReallyRead = rx_Read(rxCallP, &fileDataBytes, sizeof(long));
if (bytesReallyRead != sizeof(long)) {
printf("** Only %d bytes read for file length; should have been %d\n",
bytesReallyRead, sizeof(long));
exit(1);
}
fileDataBytes = ntohl(fileDataBytes);
- Once the client knows how many bytes will be sent, it runs a loop in which it reads a buffer at a time from the Rx call stream, using rx Read() to accomplish this. In this application, all that is done with each newly-acquired buffer of information is printing it out.
printf("[file contents (%d bytes) fetched over the Rx call appear
below]\n\n", fileDataBytes);
while (fileDataBytes > 0)
{
currBytesToRead = (fileDataBytes > maxBytesToRead ? maxBytesToRead :
fileDataBytes);
bytesReallyRead = rx_Read(rxCallP, buff, currBytesToRead);
if (bytesReallyRead != currBytesToRead)
{
printf("\nExpecting %d bytes on this read, got %d instead\n",
currBytesToRead, bytesReallyRead);
exit(1);
}
buff[currBytesToRead] = 0;
printf("%s", buff);
fileDataBytes -= currBytesToRead;
}
- After this loop terminates, the Rx stream has been drained of all data. The Rx call is concluded by invoking the second of the two automatically-generated functions, EndRXDEMO Getfile(), which retrieves the call's OUT parameter from the server.
printf("\n\n[End of file data]\n");
code = EndRXDEMO_Getfile(rxCallP, &getResults);
if (code)
{
printf("** Error getting file transfer results; code is %d\n",
code);
exit(1);
}
- With both normal and streamed Rx calls accomplished, the client demo code concludes by terminating the Rx call it set up earlier. With that done, the client exits.
code = rx_EndCall(rxCallP, code);
if (code)
printf("Error in calling rx_EndCall(); code is %d\n", code);
printf("\n\nrxdemo complete.\n");
Server Program: rxdemo server.c
- The rxdemo server program, rxdemo server, implements the operations promised in the rxdemo.xg interface file.
- After the initial header, the external function RXDEMO ExecuteRequest() is declared. The RXDEMO ExecuteRequest() function is generated automatically by rxgen from the interface file and deposited in rxdemo.ss.c. The main program listed below will associate this RXDEMO ExecuteRequest() routine with the Rx service to be instantiated.
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <netdb.h>
#include <stdio.h>
#include "rxdemo.h"
#define N_SECURITY_OBJECTS 1
extern RXDEMO_ExecuteRequest();
- After choosing either the default or user-specified UDP port on which the Rx service will be established, rx Init() is called to set up the library.
main(argc, argv)
int argc;
char **argv;
{
static char pn[] = "rxdemo_server";
struct rx_securityClass
(securityObjects[1]);
struct rx_service *rxServiceP;
struct rx_call *rxCallP;
int demoUDPPort;
int fd;
int code;
printf("\n%s: Example Rx server process\n\n", pn);
if (argc >2) {
printf("Usage: rxdemo [PortToUse]");
exit(1);
}
if (argc > 1)
demoUDPPort = atoi(argv[1]);
else
demoUDPPort = RXDEMO_SERVER_PORT;
printf("Listening on UDP port %d\n", demoUDPPort);
code = rx_Init(demoUDPPort);
if (code) {
printf("** Error calling rx_Init(); code is %d\n", code);
exit(1);
}
- A security object specific to the server side of an Rx conversation is created in the next code fragment. As with the client side of the code, a "null" server security object, namely one that does not perform any authentication at all, is constructed with the rxnull NewServerSecurityObject() function.
securityObjects[RXDEMO_NULL_SECOBJ_IDX] =
rxnull_NewServerSecurityObject();
if (securityObjects[RXDEMO_NULL_SECOBJ_IDX] == (struct rx_securityClass
*) 0) {
printf("** Can't create server-side security object\n");
exit(1);
}
- The rxdemo server program is now in a position to create the desired Rx service, primed to recognize exactly those interface calls defined in rxdemo.xg. This is accomplished by calling the rx NewService() library routine, passing it the security object created above and the generated Rx dispatcher routine.
rxServiceP = rx_NewService( 0,
RXDEMO_SERVICE_ID,
"rxdemo",
securityObjects,
1,
RXDEMO_ExecuteRequest
);
if (rxServiceP == (struct rx_service *) 0) {
printf("** Can't create Rx service\n");
exit(1);
}
- The final step in this main routine is to activate servicing of calls to the exported Rx interface. Specifically, the proper number of threads are created to handle incoming interface calls. Since we are passing a non-zero argument to the rx StartServer() call, the main program will itself begin executing the server thread loop, never returning from the rx StartServer() call. The print statement afterwards should never be executed, and its presence represents some level of paranoia, useful for debugging malfunctioning thread packages.
rx_StartServer(1);
printf("** rx_StartServer() returned!!\n"); exit(1);
}
- Following the main procedure are the functions called by the automatically-generated routines in the rxdemo.ss.c module to implement the specific routines defined in the Rx interface.
- The first to be defined is the RXDEMO Add() function. The arguments for this routine are exactly as they appear in the interface definition, with the exception of the very first. The a rxCallP parameter is a pointer to the Rx structure describing the call on which this function was activated. All user-supplied routines implementing an interface function are required to have a pointer to this structure as their first parameter. Other than printing out the fact that it has been called and which operands it received, all that RXDEMO Add() does is compute the sum and place it in the output parameter.
- Since RXDEMO Add() is a non-streamed function, with all data travelling through the set of parameters, this is all that needs to be done. To mark a successful completion, RXDEMO Add() returns zero, which is passed all the way through to the RPC's client.
int RXDEMO_Add(a_rxCallP, a_operand1, a_operand2, a_resultP)
struct rx_call *a_rxCallP;
int a_operand1, a_operand2;
int *a_resultP;
{
printf("\t[Handling call to RXDEMO_Add(%d, %d)]\n",
a_operand1, a_operand2);
*a_resultP = a_operand1 + a_operand2;
return(0);
}
- The next and final interface routine defined in this file is RXDEMO Getfile(). Declared as a split function in the interface file, RXDEMO Getfile() is an example of a streamed Rx call. As with RXDEMO Add(), the initial parameter is required to be a pointer to the Rx call structure with which this routine is associated, Similarly, the other parameters appear exactly as in the interface definition, and are handled identically.
- The difference between RXDEMO Add() and RXDEMO Getfile() is in the use of the rx Write() library routine by RXDEMO Getfile() to feed the desired file's data directly into the Rx call stream. This is an example of the use of the a rxCallP argument, providing all the information necessary to support the rx Write() activity.
- The RXDEMO Getfile() function begins by printing out the fact that it's been called and the name of the requested file. It will then attempt to open the requested file and stat it to determine its size.
int RXDEMO_Getfile(a_rxCallP, a_nameToRead, a_resultP)
struct rx_call *a_rxCallP;
char *a_nameToRead;
int *a_resultP;
{
struct stat fileStat;
long fileBytes;
long nbofileBytes;
int code;
int bytesReallyWritten;
int bytesToSend;
int maxBytesToSend;
int bytesRead;
char buff[RXDEMO_BUFF_BYTES+1];
int fd;
maxBytesToSend = RXDEMO_BUFF_BYTES;
printf("\t[Handling call to RXDEMO_Getfile(%s)]\n", a_nameToRead);
fd = open(a_nameToRead, O_RDONLY, 0444);
if (fd <0) {
printf("\t\t[**Can't open file '%s']\n", a_nameToRead);
*a_resultP = RXDEMO_CODE_CANT_OPEN;
return(1);
} else
printf("\t\t[file opened]\n");
code = fstat(fd, &fileStat);
if (code) {
a_resultP = RXDEMO_CODE_CANT_STAT;
printf("\t\t[file closed]\n");
close(fd);
return(1);
}
fileBytes = fileStat.st_size;
printf("\t\t[file has %d bytes]\n", fileBytes);
- Only standard unix operations have been used so far. Now that the file is open, we must first feed the size of the file, in bytes, to the Rx call stream. With this information, the client code can then determine how many bytes will follow on the stream. As with all data that flows through an Rx stream, the longword containing the file size, in bytes, must be converted to network byte order before being sent. This insures that the recipient may properly interpret the streamed information, regardless of its memory architecture.
nbofileBytes = htonl(fileBytes);
bytesReallyWritten = rx_Write(a_rxCallP, &nbofileBytes, sizeof(long));
if (bytesReallyWritten != sizeof(long)) {
printf("** %d bytes written instead of %d for file length\n",
bytesReallyWritten, sizeof(long));
*a_resultP = RXDEMO_CODE_WRITE_ERROR;
printf("\t\t[file closed]\n");
close(fd);
return(1);
}
- Once the number of file bytes has been placed in the stream, the RXDEMO Getfile() routine runs a loop, reading a buffer's worth of the file and then inserting that buffer of file data into the Rx stream at each iteration. This loop executes until all of the file's bytes have been shipped. Notice there is no special end-of-file character or marker inserted into the stream.
- The body of the loop checks for both unix read() and rx Write errors. If there is a problem reading from the unix file into the transfer buffer, it is reflected back to the client by setting the error return parameter appropriately. Specifically, an individual unix read() operation could fail to return the desired number of bytes. Problems with rx Write() are handled similarly. All errors discovered in the loop result in the file being closed, and RXDEMO Getfile() exiting with a non-zero return value.
while (fileBytes > 0) {
bytesToSend = (fileBytes > maxBytesToSend ?
maxBytesToSend : fileBytes);
bytesRead = read(fd, buff, bytesToSend);
if (bytesRead != bytesToSend) {
printf("Read %d instead of %d bytes from the file\n",
bytesRead, bytesToSend);
*a_resultP = RXDEMO_CODE_WRITE_ERROR;
printf("\t\t[file closed]\n");
close(fd);
return(1);
}
bytesReallyWritten = rx_Write(a_rxCallP, buff, bytesToSend);
if (bytesReallyWritten != bytesToSend) {
printf("%d file bytes written instead of %d\n",
bytesReallyWritten, bytesToSend);
*a_resultP = RXDEMO_CODE_WRITE_ERROR;
printf("\t\t[file closed]\n");
close(fd);
return(1);
}
fileBytes -= bytesToSend;
}
- Once all of the file's bytes have been shipped to the remote client, all that remains to be done is to close the file and return successfully.
*a_resultP = RXDEMO_CODE_SUCCESS;
printf("\t\t[file closed]\n");
close(fd);
return(0);
}
Section 6.2.4: Makefile
- This file directs the compilation and installation of the rxdemo code. It specifies the locations of libraries, include files, sources, and such tools as Rxgen and install, which strips symbol tables from executables and places them in their target directories. This Makefile demostrates cross-cell software development, with the rxdemo sources residing in the grand.central.org cell and the AFS include files and libraries accessed from their locations in the transarc.com cell.
- In order to produce and install the rxdemo server and rxdemo client binaries, the system target should be specified on the command line when invoking make:
- A note of caution is in order concerning generation of the rxdemo binaries. While tools exist that deposit the results of all compilations to other (architecture-specific) directories, and thus facilitate multiple simultaneous builds across a variety of machine architectures (e.g., Transarc's washtool), the assumption is made here that compilations will take place directly in the directory containing all the rxdemo sources. Thus, a user will have to execute a make clean command to remove all machine-specific object, library, and executable files before compiling for a different architecture. Note, though, that the binaries are installed into a directory specifically reserved for the current machine type. Specifically, the final pathname component of the ${PROJ DIR}bin installation target is really a symbolic link to ${PROJ DIR}.bin/.
- Two libraries are needed to support the rxdemo code. The first is obvious, namely the Rx librx.a library. The second is the lightweight thread package library, liblwp.a, which implements all the threading operations that must be performed. The include files are taken from the unix /usr/include directory, along with various AFS-specific directories. Note that for portability reasons, this Makefile only contains fully-qualified AFS pathnames and "standard" unix pathnames (such as /usr/include).
SHELL = /bin/sh
TOOL_CELL = grand.central.org
AFS_INCLIB_CELL = transarc.com
USR_CONTRIB = /afs/${TOOL_CELL}/darpa/usr/contrib/
PROJ_DIR = ${USR_CONTRIB}.site/grand.central.org/rxdemo/
AFS_INCLIB_DIR = /afs/${AFS_INCLIB_CELL}/afs/dest/
RXGEN = ${AFS_INCLIB_DIR}bin/rxgen
INSTALL = ${AFS_INCLIB_DIR}bin/install
LIBS = ${AFS_INCLIB_DIR}lib/librx.a \ ${AFS_INCLIB_DIR}lib/liblwp.a
CFLAGS = -g \
-I. \
-I${AFS_INCLIB_DIR}include \
-I${AFS_INCLIB_DIR}include/afs \
-I${AFS_INCLIB_DIR} \
-I/usr/include
system: install
install: all
${INSTALL} rxdemo_client
${PROJ_DIR}bin
${INSTALL} rxdemo_server
${PROJ_DIR}bin
all: rxdemo_client rxdemo_server
rxdemo_client: rxdemo_client.o ${LIBS} rxdemo.cs.o ${CC} ${CFLAGS}
-o rxdemo_client rxdemo_client.o rxdemo.cs.o ${LIBS}
rxdemo_server: rxdemo_server.o rxdemo.ss.o ${LIBS} ${CC} ${CFLAGS}
-o rxdemo_server rxdemo_server.o rxdemo.ss.o ${LIBS}
rxdemo_client.o: rxdemo.h
rxdemo_server.o: rxdemo.h
rxdemo.cs.c rxdemo.ss.c rxdemo.er.c rxdemo.h: rxdemo.xg rxgen rxdemo.xg
clean: rm -f *.o rxdemo.cs.c rxdemo.ss.c rxdemo.xdr.c rxdemo.h \
rxdemo_client rxdemo_server core
Section 6.3: Computer-Generated files
- The four human-generated files described above provide all the information necessary to construct the full set of modules to support the rxdemo example application. This section describes those routines that are generated from the base set by Rxgen, filling out the code required to implement an Rx service.
Client-Side Routines: rxdemo.cs.c
- The rxdemo client.c program, described in Section 6.2.2, calls the client-side stub routines contained in this module in order to make rxdemo RPCs. Basically, these client-side stubs are responsible for creating new Rx calls on the given connection parameter and then marshalling and unmarshalling the rest of the interface call parameters. The IN and INOUT arguments, namely those that are to be delivered to the server-side code implementing the call, must be packaged in network byte order and shipped along the given Rx call. The return parameters, namely those objects declared as INOUT and OUT, must be fetched from the server side of the associated Rx call, put back in host byte order, and inserted into the appropriate parameter variables.
- The first part of rxdemo.cs.c echoes the definitions appearing in the rxdemo.xg interface file, and also #includes another Rxgen-generated file, rxdemo.h.
#include "rxdemo.h"
#define RXDEMO_CODE_WRITE_ERROR 4
#include <rx/rx.h>
#include <rx/rx_null.h>
#define RXDEMO_SERVER_PORT 8000
#define RXDEMO_SERVICE_PORT 0
#define RXDEMO_SERVICE_ID 4
#define RXDEMO_NULL_SECOBJ_IDX 0
#define RXDEMO_MAX 3
#define RXDEMO_MIN 2
#define RXDEMO_NULL 0
#define RXDEMO_NAME_MAX_CHARS 64
#define RXDEMO_BUFF_BYTES 512
#define RXDEMO_CODE_SUCCESS 0
#define RXDEMO_CODE_CANT_OPEN 1
#define RXDEMO_CODE_CANT_STAT 2
#define RXDEMO_CODE_CANT_READ 3
#define RXDEMO_CODE_WRITE_ERROR 4
- The next code fragment defines the client-side stub for the RXDEMO Add() routine, called by the rxdemo client program to execute the associated RPC.
int RXDEMO_Add(z_conn, a, b, result) register struct rx_connection *z_conn;
int a, b;
int * result;
{
struct rx_call *z_call = rx_NewCall(z_conn);
static int z_op = 1;
int z_result;
XDR z_xdrs;
xdrrx_create(&z_xdrs, z_call, XDR_ENCODE);
if ((!xdr_int(&z_xdrs, &z_op))
|| (!xdr_int(&z_xdrs, &a))
|| (!xdr_int(&z_xdrs, &b))) {
z_result = RXGEN_CC_MARSHAL;
goto fail;
}
z_xdrs.x_op = XDR_DECODE;
if ((!xdr_int(&z_xdrs, result))) {
z_result = RXGEN_CC_UNMARSHAL;
goto fail;
}
z_result = RXGEN_SUCCESS;
fail: return rx_EndCall(z_call, z_result);
}
- The very first operation performed by RXDEMO Add() occurs in the local variable declarations, where z call is set to point to the structure describing a newly-created Rx call on the given connection. An XDR structure, z xdrs, is then created for the given Rx call with xdrrx create(). This XDR object is used to deliver the proper arguments, in network byte order, to the matching server stub code. Three calls to xdr int() follow, which insert the appropriate Rx opcode and the two operands into the Rx call. With the IN arguments thus transmitted, RXDEMO Add() prepares to pull the value of the single OUT parameter. The z xdrs XDR structure, originally set to XDR ENCODE objects, is now reset to XDR DECODE to convert further items received into host byte order. Once the return parameter promised by the function is retrieved, RXDEMO Add() returns successfully.
- Should any failure occur in passing the parameters to and from the server side of the call, the branch to fail will invoke Rx EndCall(), which advises the server that the call has come to a premature end (see Section 5.6.6 for full details on rx EndCall() and the meaning of its return value).
- The next client-side stub appearing in this generated file handles the delivery of the IN parameters for StartRXDEMO Getfile(). It operates identically as the RXDEMO Add() stub routine in this respect, except that it does not attempt to retrieve the OUT parameter. Since this is a streamed call, the number of bytes that will be placed on the Rx stream cannot be determined at compile time, and must be handled explicitly by rxdemo client.c.
int StartRXDEMO_Getfile(z_call, a_nameToRead)
register struct rx_call *z_call;
char * a_nameToRead;
{
static int z_op = 2;
int z_result;
XDR z_xdrs;
xdrrx_create(&z_xdrs, z_call, XDR_ENCODE);
if ((!xdr_int(&z_xdrs, &z_op)) || (!xdr_string(&z_xdrs, &a_nameToRead,
RXDEMO_NAME_MAX_CHARS))) {
z_result = RXGEN_CC_MARSHAL;
goto fail;
}
z_result = RXGEN_SUCCESS;
fail: return z_result;
}
- The final stub routine appearing in this generated file, EndRXDEMO Getfile(), handles the case where rxdemo client.c has already successfully recovered the unbounded streamed data appearing on the call, and then simply has to fetch the OUT parameter. This routine behaves identially to the latter portion of RXDEMO Getfile().
int EndRXDEMO_Getfile(z_call, a_result)
register struct rx_call *z_call;
int * a_result;
{
int z_result;
XDR z_xdrs;
xdrrx_create(&z_xdrs, z_call, XDR_DECODE);
if ((!xdr_int(&z_xdrs, a_result))) {
z_result = RXGEN_CC_UNMARSHAL;
goto fail;
}
z_result = RXGEN_SUCCESS; fail:
return z_result;
}
Server-Side Routines: rxdemo.ss.c
- This generated file provides the core components required to implement the server side of the rxdemo RPC service. Included in this file is the generated dispatcher routine, RXDEMO ExecuteRequest(), which the rx NewService() invocation in rxdemo server.c uses to construct the body of each listener thread's loop. Also included are the server-side stubs to handle marshalling and unmarshalling of parameters for each defined RPC call (i.e., RXDEMO Add() and RXDEMO Getfile()). These stubs are called by RXDEMO ExecuteRequest(). The routine to be called by RXDEMO ExecuteRequest() depends on the opcode received, which appears as the very first longword in the call data.
- As usual, the first fragment is copyright information followed by the body of the definitions from the interface file.
#include "rxdemo.h"
#include <rx/rx.h>
#include <rx/rx_null.h>
#define RXDEMO_SERVER_PORT 8000
#define RXDEMO_SERVICE_PORT 0
#define RXDEMO_SERVICE_ID 4
#define RXDEMO_NULL_SECOBJ_IDX 0
#define RXDEMO_MAX 3
#define RXDEMO_MIN 2
#define RXDEMO_NULL 0
#define RXDEMO_NAME_MAX_CHARS 64
#define RXDEMO_BUFF_BYTES 512
#define RXDEMO_CODE_SUCCESS 0
#define RXDEMO_CODE_CANT_OPEN 1
#define RXDEMO_CODE_CANT_STAT 2
#define RXDEMO_CODE_CANT_READ 3
#define RXDEMO_CODE_WRITE_ERROR 4
- After this preamble, the first server-side stub appears. This RXDEMO Add() routine is basically the inverse of the RXDEMO Add() client-side stub defined in rxdemo.cs.c. Its job is to unmarshall the IN parameters for the call, invoke the "true" server-side RXDEMO Add() routine (defined in rxdemo server.c), and then package and ship the OUT parameter. Being so similar to the client-side RXDEMO Add(), no further discussion is offered here.
long _RXDEMO_Add(z_call, z_xdrs)
struct rx_call *z_call;
XDR *z_xdrs;
{
long z_result;
int a, b;
int result;
if ((!xdr_int(z_xdrs, &a)) || (!xdr_int(z_xdrs, &b)))
{
z_result = RXGEN_SS_UNMARSHAL;
goto fail;
}
z_result = RXDEMO_Add(z_call, a, b, &result);
z_xdrs->x_op = XDR_ENCODE;
if ((!xdr_int(z_xdrs, &result)))
z_result = RXGEN_SS_MARSHAL;
fail: return z_result;
}
- The second server-side stub, RXDEMO Getfile(), appears next. It operates identically to RXDEMO Add(), first unmarshalling the IN arguments, then invoking the routine that actually performs the server-side work for the call, then finishing up by returning the OUT parameters.
long _RXDEMO_Getfile(z_call, z_xdrs)
struct rx_call *z_call;
XDR *z_xdrs;
{
long z_result;
char * a_nameToRead=(char *)0;
int a_result;
if ((!xdr_string(z_xdrs, &a_nameToRead, RXDEMO_NAME_MAX_CHARS))) {
z_result = RXGEN_SS_UNMARSHAL;
goto fail;
}
z_result = RXDEMO_Getfile(z_call, a_nameToRead, &a_result);
z_xdrs->x_op = XDR_ENCODE;
if ((!xdr_int(z_xdrs, &a_result)))
z_result = RXGEN_SS_MARSHAL;
fail: z_xdrs->x_op = XDR_FREE;
if (!xdr_string(z_xdrs, &a_nameToRead, RXDEMO_NAME_MAX_CHARS))
goto fail1;
return z_result;
fail1: return RXGEN_SS_XDRFREE;
}
- The next portion of the automatically generated server-side module sets up the dispatcher routine for incoming Rx calls. The above stub routines are placed into an array in opcode order.
long _RXDEMO_Add();
long _RXDEMO_Getfile();
static long (*StubProcsArray0[])() = {_RXDEMO_Add, _RXDEMO_Getfile};
- The dispatcher routine itself, RXDEMO ExecuteRequest, appears next. This is the function provided to the rx NewService() call in rxdemo server.c, and it is used as the body of each listener thread's service loop. When activated, it decodes the first longword in the given Rx call, which contains the opcode. It then dispatches the call based on this opcode, invoking the appropriate server-side stub as organized in the StubProcsArray.
RXDEMO_ExecuteRequest(z_call)
register struct rx_call *z_call;
{
int op;
XDR z_xdrs;
long z_result;
xdrrx_create(&z_xdrs, z_call, XDR_DECODE);
if (!xdr_int(&z_xdrs, &op))
z_result = RXGEN_DECODE;
else if (op < RXDEMO_LOWEST_OPCODE || op > RXDEMO_HIGHEST_OPCODE)
z_result = RXGEN_OPCODE;
else
z_result = (*StubProcsArray0[op -RXDEMO_LOWEST_OPCODE])(z_call,
&z_xdrs);
return z_result;
}
External Data Rep file: rxdemo.xdr.c
- This file is created to provide the special routines needed to map any user-defined structures appearing as Rx arguments into and out of network byte order. Again, all on-thewire data appears in network byte order, insuring proper communication between servers and clients with different memory organizations.
- Since the rxdemo example application does not define any special structures to pass as arguments in its calls, this generated file contains only the set of definitions appearing in the interface file. In general, though, should the user define a struct xyz and use it as a parameter to an RPC function, this file would contain a routine named xdr xyz(), which converted the structure field-by-field to and from network byte order.
#include "rxdemo.h"
#include <rx/rx.h>
#include <rx/rx_null.h>
#define RXDEMO_SERVER_PORT 8000
#define RXDEMO_SERVICE_PORT 0
#define RXDEMO_SERVICE_ID 4
#define RXDEMO_NULL_SECOBJ_IDX 0
#define RXDEMO_MAX 3
#define RXDEMO_MIN 2
#define RXDEMO_NULL 0
#define RXDEMO_NAME_MAX_CHARS 64
#define RXDEMO_BUFF_BYTES 512
#define RXDEMO_CODE_SUCCESS 0
#define RXDEMO_CODE_CANT_OPEN 1
#define RXDEMO_CODE_CANT_STAT 2
#define RXDEMO_CODE_CANT_READ 3
#define RXDEMO_CODE_WRITE_ERROR 4
Section 6.4: Sample Output
- This section contains the output generated by running the example rxdemo server and rxdemo client programs described above. The server end was run on a machine named Apollo, and the client program was run on a machine named Bigtime.
- The server program on Apollo was started as follows:
- apollo: rxdemo_server
- rxdemo_server: Example Rx server process
- Listening on UDP port 8000
- At this point, rxdemo server has initialized its Rx module and started up its listener LWPs, which are sleeping on the arrival of an RPC from any rxdemo client.
- The client portion was then started on Bigtime:
bigtime: rxdemo_client apollo
rxdemo: Example Rx client process
Connecting to Rx server on 'apollo', IP address 0x1acf37c0, UDP port 8000
---> Connected. Asking server to add 1 and 2: Reported sum is 3
- The command line instructs rxdemo client to connect to the rxdemo server on host apollo and to use the standard port defined for this service. It reports on the successful Rx connection establishment, and immediately executes an rxdemo Add(1, 2) RPC. It reports that the sum was successfully received. When the RPC request arrived at the server and was dispatched by the rxdemo server code, it printed out the following line:
[Handling call to RXDEMO_Add(1, 2)]
- Next, rxdemo client prompts for the name of the file to read from the rxdemo server. It is told to fetch the Makefile for the Rx demo directory. The server is executing in the same directory in which it was compiled, so an absolute name for the Makefile is not required. The client echoes the following:
Name of file to read from server: Makefile Setting up an Rx call for RXDEMO_Getfile...done
- As with the rxdemo Add() call, rxdemo server receives this RPC, and prints out the following information:
- [Handling call to RXDEMO_Getfile(Makefile)]
- [file opened]
- [file has 2450 bytes]
- [file closed]
- It successfully opens the named file, and reports on its size in bytes. The rxdemo server program then executes the streamed portion of the rxdemo Getfile call, and when complete, indicates that the file has been closed. Meanwhile, rxdemo client prints out the reported size of the file, follows it with the file's contents, then advises that the test run has completed:
[file contents (2450 bytes) fetched over the Rx call appear below]
SHELL = /bin/sh
TOOL_CELL = grand.central.org
AFS_INCLIB_CELL = transarc.com
USR_CONTRIB = /afs/${TOOL_CELL}/darpa/usr/contrib/
PROJ_DIR = ${USR_CONTRIB}.site/grand.central.org/rxdemo/
AFS_INCLIB_DIR = /afs/${AFS_INCLIB_CELL}/afs/dest/
RXGEN = ${AFS_INCLIB_DIR}bin/rxgen
INSTALL = ${AFS_INCLIB_DIR}bin/install
LIBS = ${AFS_INCLIB_DIR}lib/librx.a \ ${AFS_INCLIB_DIR}lib/liblwp.a
CFLAGS = -g \
-I. \
-I${AFS_INCLIB_DIR}include \
-I${AFS_INCLIB_DIR}include/afs \
-I${AFS_INCLIB_DIR} \
-I/usr/include
system: install
install: all
${INSTALL} rxdemo_client ${PROJ_DIR}bin
${INSTALL} rxdemo_server ${PROJ_DIR}bin
all: rxdemo_client rxdemo_server
rxdemo_client: rxdemo_client.o ${LIBS} rxdemo.cs.o ${CC} ${CFLAGS}
-o rxdemo_client rxdemo_client.o rxdemo.cs.o ${LIBS}
rxdemo_server: rxdemo_server.o rxdemo.ss.o ${LIBS} ${CC} ${CFLAGS}
-o rxdemo_server rxdemo_server.o rxdemo.ss.o ${LIBS}
rxdemo_client.o: rxdemo.h
rxdemo_server.o: rxdemo.h
rxdemo.cs.c rxdemo.ss.c rxdemo.er.c rxdemo.h: rxdemo.xg rxgen rxdemo.xg
clean: rm -f *.o rxdemo.cs.c rxdemo.ss.c rxdemo.xdr.c rxdemo.h \
rxdemo_client rxdemo_server core
[End of file data]
rxdemo complete.
- The rxdemo server program continues to run after handling these calls, offering its services to any other callers. It can be killed by sending it an interrupt signal using Control-C (or whatever mapping has been set up for the shell's interrupt character).
Bibliography
- [1] Transarc Corporation. AFS 3.0 System Administrator's Guide, F-30-0-D102, Pittsburgh, PA, April 1990.
- [2] S.P. Miller, B.C. Neuman, J.I. Schiller, J.H. Saltzer. Kerberos Authentication and Authorization System, Project Athena Technical Plan, Section E.2.1, M.I.T., December 1987.
- [3] Bill Bryant. Designing an Authentication System: a Dialogue in Four Scenes, Project Athena internal document, M.I.T, draft of 8 February 1988.
- [4] S. R. Kleinman. Vnodes: An Architecture for Multiple file System Types in Sun UNIX, Conference Proceedings, 1986 Summer Usenix Technical Conference, pp. 238-247, El Toro, CA, 1986.