Section 4.1: Introduction
- This chapter documents three packages defined directly in support of the Rx facility.
- rx queue: Doubly-linked queue package.
- rx clock: Clock package, using the 4.3BSD interval timer.
- rx event: Future events package.
- References to constants, structures, and functions defined by these support packages will appear in the following API chapter.
Section 4.2: The rx queue Package
- This package provides a doubly-linked queue structure, along with a full suite of related operations. The main concern behind the coding of this facility was efficiency. All functions are implemented as macros, and it is suggested that only simple expressions be used for all parameters.
- The rx queue facility is defined by the rx queue.h include file. Some macros visible in this file are intended for rx queue internal use only. An understanding of these "hidden" macros is important, so they will also be described by this document.
Section 4.2.1: struct queue
- The queue structure provides the linkage information required to maintain a queue of objects. The queue structure is prepended to any user-defined data type which is to be organized in this fashion.
fields
- struct queue *prev - Pointer to the previous queue header.
- struct queue *next - Pointer to the next queue header.
- Note that a null Rx queue consists of a single struct queue object whose next and previous pointers refer to itself.
Section 4.2.2: Internal Operations
- This section describes the internal operations defined for Rx queues. They will be referenced by the external operations documented in Section 4.2.3.
Section 4.2.2.1: Q(): Coerce type to a queue
element
- #define _Q(x) ((struct queue *)(x))
- This operation coerces the user structure named by x to a queue element. Any user structure using the rx queue package must have a struct queue as its first field.
Section 4.2.2.2: QA(): Add a queue element
before/after another element
- #define _QA(q,i,a,b) (((i->a=q->a)->b=i)->b=q, q->a=i)
- This operation adds the queue element referenced by i either before or after a queue element represented by q. If the (a, b) argument pair corresponds to an element's (next, prev) fields, the new element at i will be linked after q. If the (a, b) argument pair corresponds to an element's (prev, next) fields, the new element at i will be linked before q.
QR(): Remove a queue element
- #define _QR(i) ((_Q(i)->prev->next=_Q(i)->next)->prev=_Q(i)->prev)
- This operation removes the queue element referenced by i from its queue. The prev and next fields within queue element i itself is not updated to reflect the fact that it is no longer part of the queue.
QS(): Splice two queues together
- #define _QS(q1,q2,a,b) if (queue_IsEmpty(q2)); else ((((q2->a->b=q1)->a->b=q2->b)->a=q1->a, q1->a=q2->a), queue_Init(q2))
- This operation takes the queues identified by q1 and q2 and splices them together into a single queue. The order in which the two queues are appended is determined by the a and b arguments. If the (a, b) argument pair corresponds to q1's (next, prev) fields, then q2 is appended to q1. If the (a, b) argument pair corresponds to q1's (prev, next) fields, then q is prepended to q2.
- This internal QS() routine uses two exported queue operations, namely queue Init() and queue IsEmpty(), defined in Sections 4.2.3.1 and 4.2.3.16 respectively below.
Section 4.2.3: External Operations
Section 4.2.3.1: queue Init(): Initialize a
queue header
- #define queue_Init(q) (_Q(q))->prev = (_Q(q))->next = (_Q(q))
- The queue header referred to by the q argument is initialized so that it describes a null (empty) queue. A queue head is simply a queue element.
Section 4.2.3.2: queue Prepend(): Put element
at the head of a queue
- #define queue_Prepend(q,i) _QA(_Q(q),_Q(i),next,prev)
- Place queue element i at the head of the queue denoted by q. The new queue element, i, should not currently be on any queue.
Section 4.2.3.3: queue Append(): Put an
element a the tail of a queue
- #define queue_Append(q,i) _QA(_Q(q),_Q(i),prev,next)
- Place queue element i at the tail of the queue denoted by q. The new queue element, i, should not currently be on any queue.
Section 4.2.3.4: queue InsertBefore(): Insert a
queue element before another element
- #define queue_InsertBefore(i1,i2) _QA(_Q(i1),_Q(i2),prev,next)
- Insert queue element i2 before element i1 in i1's queue. The new queue element, i2, should not currently be on any queue.
Section 4.2.3.5: queue InsertAfter(): Insert
a queue element after another element
- #define queue_InsertAfter(i1,i2) _QA(_Q(i1),_Q(i2),next,prev)
- Insert queue element i2 after element i1 in i1's queue. The new queue element, i2, should not currently be on any queue.
Section: 4.2.3.6: queue SplicePrepend():
Splice one queue before another
- #define queue_SplicePrepend(q1,q2) _QS(_Q(q1),_Q(q2),next,prev)
- Splice the members of the queue located at q2 to the beginning of the queue located at q1, reinitializing queue q2.
Section 4.2.3.7: queue SpliceAppend(): Splice
one queue after another
- #define queue_SpliceAppend(q1,q2) _QS(_Q(q1),_Q(q2),prev,next)
- Splice the members of the queue located at q2 to the end of the queue located at q1, reinitializing queue q2. Note that the implementation of queue SpliceAppend() is identical to that of queue SplicePrepend() except for the order of the next and prev arguments to the internal queue splicer, QS().
Section 4.2.3.8: queue Replace(): Replace the
contents of a queue with that of another
- #define queue_Replace(q1,q2) (*_Q(q1) = *_Q(q2),
_Q(q1)->next->prev = _Q(q1)->prev->next = _Q(q1),
queue_Init(q2))
- Replace the contents of the queue located at q1 with the contents of the queue located at q2. The prev and next fields from q2 are copied into the queue object referenced by q1, and the appropriate element pointers are reassigned. After the replacement has occurred, the queue header at q2 is reinitialized.
Section 4.2.3.9: queue Remove(): Remove an
element from its queue
- #define queue_Remove(i) (_QR(i), _Q(i)->next = 0)
- This function removes the queue element located at i from its queue. The next field for the removed entry is zeroed. Note that multiple removals of the same queue item are not supported.
Section 4.2.3.10: queue MoveAppend(): Move
an element from its queue to the end of another queue
- #define queue_MoveAppend(q,i) (_QR(i), queue_Append(q,i))
- This macro removes the queue element located at i from its current queue. Once removed, the element at i is appended to the end of the queue located at q.
Section 4.2.3.11: queue MovePrepend(): Move
an element from its queue to the head of another queue
- #define queue_MovePrepend(q,i) (_QR(i), queue_Prepend(q,i))
- This macro removes the queue element located at i from its current queue. Once removed, the element at i is inserted at the head fo the queue located at q.
Section 4.2.3.12: queue first(): Return the
first element of a queue, coerced to a particular type
- #define queue_first(q,s) ((struct s *)_Q(q)->next)
- Return a pointer to the first element of the queue located at q. The returned pointer value is coerced to conform to the given s structure. Note that a properly coerced pointer to the queue head is returned if q is empty.
Section 4.2.3.13: queue Last(): Return the
last element of a queue, coerced to a particular type
- #define queue_Last(q,s) ((struct s *)_Q(q)->prev)
- Return a pointer to the last element of the queue located at q. The returned pointer value is coerced to conform to the given s structure. Note that a properly coerced pointer to the queue head is returned if q is empty.
Section 4.2.3.14: queue Next(): Return the
next element of a queue, coerced to a particular type
- #define queue_Next(i,s) ((struct s *)_Q(i)->next)
- Return a pointer to the queue element occuring after the element located at i. The returned pointer value is coerced to conform to the given s structure. Note that a properly coerced pointer to the queue head is returned if item i is the last in its queue.
Section 4.2.3.15: queue Prev(): Return the
next element of a queue, coerced to a particular type
- #define queue_Prev(i,s) ((struct s *)_Q(i)->prev)
- Return a pointer to the queue element occuring before the element located at i. The returned pointer value is coerced to conform to the given s structure. Note that a properly coerced pointer to the queue head is returned if item i is the first in its queue.
Section 4.2.3.16: queue IsEmpty(): Is the
given queue empty?
- #define queue_IsEmpty(q) (_Q(q)->next == _Q(q))
- Return a non-zero value if the queue located at q does not have any elements in it. In this case, the queue consists solely of the queue header at q whose next and prev fields reference itself.
Section 4.2.3.17: queue IsNotEmpty(): Is the
given queue not empty?
- #define queue_IsNotEmpty(q) (_Q(q)->next != _Q(q))
- Return a non-zero value if the queue located at q has at least one element in it other than the queue header itself.
Section 4.2.3.18: queue IsOnQueue(): Is an
element currently queued?
- #define queue_IsOnQueue(i) (_Q(i)->next != 0)
- This macro returns a non-zero value if the queue item located at i is currently a member of a queue. This is determined by examining its next field. If it is non-null, the element is considered to be queued. Note that any element operated on by queue Remove() (Section 4.2.3.9) will have had its next field zeroed. Hence, it would cause a non-zero return from this call.
Section 4.2.3.19: queue Isfirst(): Is an
element the first on a queue?
- #define queue_Isfirst(q,i) (_Q(q)->first == _Q(i))
- This macro returns a non-zero value if the queue item located at i is the first element in the queue denoted by q.
Section 4.2.3.20: queue IsLast(): Is an
element the last on a queue?
- #define queue_IsLast(q,i) (_Q(q)->prev == _Q(i))
- This macro returns a non-zero value if the queue item located at i is the last element in the queue denoted by q.
Section 4.2.3.21: queue IsEnd(): Is an
element the end of a queue?
- #define queue_IsEnd(q,i) (_Q(q) == _Q(i))
- This macro returns a non-zero value if the queue item located at i is the end of the queue located at q. Basically, it determines whether a queue element in question is also the queue header structure itself, and thus does not represent an actual queue element. This function is useful for terminating an iterative sweep through a queue, identifying when the search has wrapped to the queue header.
Section 4.2.3.22: queue Scan(): for loop
test for scanning a queue in a forward direction
- #define queue_Scan(q, qe, next, s)
(qe) = queue_first(q, s), next = queue_Next(qe, s);
!queue_IsEnd(q, qe);
(qe) = (next), next = queue_Next(qe, s)
- This macro may be used as the body of a for loop test intended to scan through each element in the queue located at q. The qe argument is used as the for loop variable. The next argument is used to store the next value for qe in the upcoming loop iteration. The s argument provides the name of the structure to which each queue element is to be coerced. Thus, the values provided for the qe and next arguments must be of type (struct s *).
- An example of how queue Scan() may be used appears in the code fragment below. It declares a structure named mystruct, which is suitable for queueing. This queueable structure is composed of the queue pointers themselves followed by an integer value. The actual queue header is kept in demoQueue, and the currItemP and nextItemP variables are used to step through the demoQueue. The queue Scan() macro is used in the for loop to generate references in currItemP to each queue element in turn for each iteration. The loop is used to increment every queued structure's myval field by one.
struct mystruct {
struct queue q;
int myval;
};
struct queue demoQueue;
struct mystruct *currItemP, *nextItemP;
...
for (queue_Scan(&demoQueue, currItemP, nextItemP, mystruct)) {
currItemP->myval++;
}
- Note that extra initializers can be added before the body of the queue Scan() invocation above, and extra expressions can be added afterwards.
Section 4.2.3.23: queue ScanBackwards(): for
loop test for scanning a queue in a reverse direction
- #define queue_ScanBackwards(q, qe, prev, s)
(qe) = queue_Last(q, s), prev = queue_Prev(qe, s);
!queue_IsEnd(q, qe);
(qe) = prev, prev = queue_Prev(qe, s)
- This macro is identical to the queue Scan() macro described above in Section 4.2.3.22 except for the fact that the given queue is scanned backwards, starting at the last item in the queue.
Section 4.3: The rx clock Package
- This package maintains a clock which is independent of the time of day. It uses the unix 4.3BSD interval timer (e.g., getitimer(), setitimer()) in TIMER REAL mode. Its definition and interface may be found in the rx clock.h include file.
Section 4.3.1: struct clock
- This structure is used to represent a clock value as understood by this package. It consists of two fields, storing the number of seconds and microseconds that have elapsed since the associated clock Init() routine has been called.
- fields
long sec -Seconds since call to clock Init().
long usec -Microseconds since call to clock Init().
Section 4.3.12: clock nUpdates
- The integer-valued clock nUpdates is a variable exported by the rx clock facility. It records the number of times the clock value is actually updated. It is bumped each time the clock UpdateTime() routine is called, as described in Section 4.3.3.2.
Section 4.3.3: Operations
Section 4.3.3.1: clock Init(): Initialize the
clock package
- This routine uses the unix setitimer() call to initialize the unix interval timer. If the setitimer() call fails, an error message will appear on stderr, and an exit(1) will be executed.
Section 4.3.3.2: clock UpdateTime(): Compute
the current time
- The clock UpdateTime() function calls the unix getitimer() routine in order to update the current time. The exported clock nUpdates variable is incremented each time the clock UpdateTime() routine is called.
Section 4.3.3.3: clock GetTime(): Return the
current clock time
- This macro updates the current time if necessary, and returns the current time into the cv argument, which is declared to be of type (struct clock *). 4.3.3.4 clock Sec(): Get the current clock time, truncated to seconds This macro returns the long value of the sec field of the current time. The recorded time is updated if necessary before the above value is returned.
Section 4.3.3.5: clock ElapsedTime(): Measure
milliseconds between two given clock values
- This macro returns the elapsed time in milliseconds between the two clock structure pointers provided as arguments, cv1 and cv2.
Section 4.3.3.6: clock Advance(): Advance the
recorded clock time by a specified clock value
- This macro takes a single (struct clock *) pointer argument, cv, and adds this clock value to the internal clock value maintined by the package.
Section 4.3.3.7: clock Gt(): Is a clock value
greater than another?
- This macro takes two parameters of type (struct clock *), a and b. It returns a nonzero value if the a parameter points to a clock value which is later than the one pointed to by b.
Section 4.3.3.8: clock Ge(): Is a clock value
greater than or equal to another?
- This macro takes two parameters of type (struct clock *), a and b. It returns a nonzero value if the a parameter points to a clock value which is greater than or equal to the one pointed to by b.
Section 4.3.3.9: clock Gt(): Are two clock
values equal?
- This macro takes two parameters of type (struct clock *), a and b. It returns a non-zero value if the clock values pointed to by a and b are equal.
value less than or equal to another?
- This macro takes two parameters of type (struct clock *), a and b. It returns a nonzero value if the a parameter points to a clock value which is less than or equal to the one pointed to by b.
Section 4.3.3.11: clock Lt(): Is a clock
value less than another?
- This macro takes two parameters of type (struct clock *), a and b. It returns a nonzero value if the a parameter points to a clock value which is less than the one pointed to by b.
Section 4.3.3.12: clock IsZero(): Is a clock
value zero?
- This macro takes a single parameter of type (struct clock *), c. It returns a non-zero value if the c parameter points to a clock value which is equal to zero.
Section 4.3.3.13: clock Zero(): Set a clock
value to zero
- This macro takes a single parameter of type (struct clock *), c. It sets the given clock value to zero.
Section 4.3.3.14: clock Add(): Add two clock
values together
- This macro takes two parameters of type (struct clock *), c1 and c2. It adds the value of the time in c2 to c1. Both clock values must be positive.
Section 4.3.3.15: clock Sub(): Subtract two
clock values
- This macro takes two parameters of type (struct clock *), c1 and c2. It subtracts the value of the time in c2 from c1. The time pointed to by c2 should be less than the time pointed to by c1.
Section 4.3.3.16: clock Float(): Convert a
clock time into floating point
- This macro takes a single parameter of type (struct clock *), c. It expresses the given clock value as a floating point number.
Section 4.4: The rx event Package
- This package maintains an event facility. An event is defined to be something that happens at or after a specified clock time, unless cancelled prematurely. The clock times used are those provided by the rx clock facility described in Section 4.3 above. A user routine associated with an event is called with the appropriate arguments when that event occurs. There are some restrictions on user routines associated with such events. first, this user-supplied routine should not cause process preemption. Also, the event passed to the user routine is still resident on the event queue at the time of invocation. The user must not remove this event explicitly (via an event Cancel(), see below). Rather, the user routine may remove or schedule any other event at this time.
- The events recorded by this package are kept queued in order of expiration time, so that the first entry in the queue corresponds to the event which is the first to expire. This interface is defined by the rx event.h include file.
Section 4.4.1: struct rxevent
- This structure defines the format of an Rx event record.
- fields
struct queue junk -The queue to which this event belongs.
struct clock eventTime -The clock time recording when this event comes due.
int (*func)() -The user-supplied function to call upon expiration.
char *arg -The first argument to the (*func)() function above.
char *arg1 -The second argument to the (*func)() function above.
Section 4.4.2: Operations
- This section covers the interface routines provided for the Rx event package.
Section 4.4.2.1: rxevent Init(): Initialize
the event package
- The rxevent Init() routine takes two arguments. The first, nEvents, is an integer-valued parameter which specifies the number of event structures to allocate at one time. This specifies the appropriate granularity of memory allocation by the event package. The second parameter, scheduler, is a pointer to an integer-valued function. This function is to be called when an event is posted (added to the set of events managed by the package) that is scheduled to expire before any other existing event.
- This routine sets up future event allocation block sizes, initializes the queues used to manage active and free event structures, and recalls that an initialization has occurred. Thus, this function may be safely called multiple times.
Section 4.4.2.2: rxevent Post(): Schedule an
event
- This function constructs a new event based on the information included in its parameters and then schedules it. The rxevent Post() routine takes four parameters. The first is named when, and is of type (struct clock *). It specifies the clock time at which the event is to occur. The second parameter is named func and is a pointer to the integer-valued function to associate with the event that will be created. When the event comes due, this function will be executed by the event package. The next two arguments to rxevent Post() are named arg and arg1, and are both of type (char *). They serve as the two arguments thath will be supplied to the func routine when the event comes due.
- If the given event is set to take place before any other event currently posted, the scheduler routine established when the rxevent Init() routine was called will be executed. This gives the application a chance to react to this new event in a reasonable way. One might expect that this scheduler routine will alter sleep times used by the application to make sure that it executes in time to handle the new event.
Section 4.4.2.3: rxevent Cancel 1(): Cancel
an event (internal use)
- This routine removes an event from the set managed by this package. It takes a single parameter named ev of type (struct rxevent *). The ev argument identifies the pending event to be cancelled.
- The rxevent Cancel 1() routine should never be called directly. Rather, it should be accessed through the rxevent Cancel() macro, described in Section 4.4.2.4 below.
Section 4.4.2.4: rxevent Cancel(): Cancel an
event (external use)
- This macro is the proper way to call the rxevent Cancel 1() routine described in Section 4.4.2.3 above. Like rxevent Cancel 1(), it takes a single argument. This event ptr argument is of type (struct rxevent *), and identi::es the pending event to be cancelled. This macro #rst checks to see if event ptr is null. If not, it calls rxevent Cancel 1() to perform the real work. The event ptr argument is zeroed after the cancellation operation completes.
Section 4.4.2.4: rxevent RaiseEvents():
Initialize the event package
- This function processes all events that have expired relative to the current clock time maintained by the event package. Each qualifying event is removed from the queue in order, and its user-supplied routine (func()) is executed with the associated arguments.
- The rxevent RaiseEvents() routine takes a single output parameter named next, defined to be of type (struct clock *). Upon completion of rxevent RaiseEvents(), the relative time to the next event due to expire is placed in next. This knowledge may be used to calculate the amount of sleep time before more event processing is needed. If there is no recorded event which is still pending at this point, rxevent RaiseEvents() returns a zeroed clock value into next.
Section 4.4.2.6: rxevent TimeToNextEvent():
Get amount of time until the next event expires
- This function returns the time between the current clock value as maintained by the event package and the the next event's expiration time. This information is placed in the single output argument,interval, defined to be of type (struct clock *). The rxevent TimeToNextEvent() function returns integer-valued quantities. If there are no scheduled events, a zero is returned. If there are one or more scheduled events, a 1 is returned. If zero is returned, the interval argument is not updated.
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
- [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.