D E V E L O P M E N T R E C I P E S
t face=arial size=-1 color=black>
D E V E L O P M E N T R E C I P E S
119
6
D E V E L O P M E N T R E C I P E S
Assuming that you are familiar with the Reactor environment and
the Modula-3 language, this chapter describes a number of recipes
for building simple realistic systems applications.
C H A P T E R O R G A N I Z A T I O N
A handful of complete programs illustrate the use of advanced
facilities in Reactor. You can find the sources for programs in this
chapter in the
Examples
section of your Reactor environment.
Using a simple automated bank teller scenario,
Robust Distributed
Applications: Network Objects
on page 120 illustrates how to build
distributed applications with Network Objects.
Client/Server Computing: Safe TCP/IP Interfaces
on page 128 describes
the safe, multi-platform, and multi-threaded TCP/IP interfaces. A
Finger client and an a simple HTTP server are described.
Taking Persistent Snapshots of Objects: Pickles
on page 134 demon-
strates how to transcribe objects onto an I/O stream.
Quick Comparison of Large Data: Fingerprints
on page 138 outlines
how to take fingerprints of large data structures, and use the finger-
prints to compare the data structures efficiently.
Portable Operating System Interfaces
on page 140 illustrates the use of
portable interfaces for operating system services, such as: file sys-
tems access, process management, thread creation, and environ-
ment variables. A complete and portable command-shell program
is used as a demonstration.
Dynamic Web Applications: the Web Server Toolkit
on page 152 out-
lines a simple contact database program based on the web toolkit.
Interacting with C Programs
on page 155 shows how to have your
code call C programs or be called by C programs. Examples illus-
trate the integration of C source code and libraries into Reactor.
Read this chapter
if you know Reac-
tor well, and would
like to use Reactor
for system develop-
ment.
120
CHAPTER
6
: DEVELOPMENT RECIPES
6.1
R O B U S T D I S T R I B U T E D A P P L I C A T I O N S : N E T W O R K
O B J E C T S
Network Objects allows an object to be handed to another process
in such a way that the process receiving the object can operate on it
as if it were local. The holder of a remote object can freely invoke
operations on that object just as if it had created that object locally.
Further, it can pass the object to other processes. Thus, the Network
Objects system allows the development of not just simple client/
server applications, but more general multi-tiered distributed
applications.
When a program calls another through Network objects, we refer
to the caller as
the client
, and the callee as
the server
. In the context of
network objects, the names client and server signify roles in a par-
ticular interactiona server may in fact be a client of another
server.
The contract between the client and the server is defined by a
com-
mon
interface
.
Here we describe a simple automated bank teller program as an
example by outlining each component: the interface, the client and
the server.
6.1.1
The Common Interface
The
Bank
interface defines the common contract between client and
server in our example.
NetObj
is the primary interface for building network object applica-
tions.
NetObj.Error
and
Thread.Alerted
may be raised by network object
operations. A
Bank.T
is a network object which supports the opera-
tion
findAccount
, which returns a
Bank.Account
object. Type
Bank.Account
supports operations
deposit
,
withdraw
and
get_balance
.
Network object operations can raise user-defined exceptions such
as
BadAmount
, and
InsufficientFunds
.
INTERFACE Bank;
IMPORT NetObj;
FROM NetObj IMPORT Error;
FROM Thread IMPORT Alerted;
TYPE
SECTION
6.1
: ROBUST DISTRIBUTED APPLICATIONS: NETWORK OBJECTS
121
T = NetObj.T OBJECT METHODS
ndAccount (acct: AcctNum): Account RAISES {Alerted, Error};
END;
TYPE
Account = NetObj.T OBJECT METHODS
deposit (amount: REAL) RAISES {BadAmount, Alerted, Error};
withdraw (amount: REAL)
RAISES {BadAmount, InsufcientFunds, Alerted, Error};
get_balance (): REAL RAISES {Alerted, Error};
END;
TYPE
AcctNum = [1..100];
EXCEPTION
BadAmount;
InsufcientFunds;
END Bank.
A simple makefile instructs Reactor that
Bank.T
and
Bank.Account
are
network objects. Reactor will generate the required stubs automati-
cally as part of this library, so a client or a server in this scenario
may use
netobj-interface
.
Import netobj to bring in the network object libraries
import("netobj")
For each network object type
I.T
you must call
netobj(I,T)
interface("Bank")
netobj("Bank", "T")
netobj("Bank", "Account")
library("netobj-interface")
122
CHAPTER
6
: DEVELOPMENT RECIPES
6.1.2
A Network Object Server
NetObjServer
is a sample implementation of a network object server
that exports an implementation of the
Bank
interface.
MODULE NetObjServer EXPORTS Main;
IMPORT Bank, NetObj, Thread;
IMPORT IO, Fmt;
BankImpl
defines a full representation for the
Bank.T
network object.
TYPE
BankImpl = Bank.T OBJECT
accounts : ARRAY Bank.AcctNum OF Account;
OVERRIDES
ndAccount := FindAccount;
END;
Find an account in the table of accounts:
PROCEDURE FindAccount
(self: BankImpl; acct: Bank.AcctNum): Bank.Account =
BEGIN
RETURN self.accounts[acct];
END FindAccount;
For
Bank.Account
network objects,
Bank.Account
uses a
MUTEX
to syn-
chronize access to its balance. It also implements the operations
deposit
,
withdraw
, and
get_balance
.
TYPE
Account = Bank.Account OBJECT
lock : MUTEX;
balance : REAL := 0.0;
OVERRIDES
deposit := Deposit;
withdraw := Withdraw; (* not included *)
get_balance := Balance; (* not included *)
END;
Deposit the money, making sure to serialize access with others try-
SECTION
6.1
: ROBUST DISTRIBUTED APPLICATIONS: NETWORK OBJECTS
123
ing to operate on this account.
PROCEDURE Deposit (self: Account; amount: REAL)
RAISES {Bank.BadAmount} =
BEGIN
IF amount < 0.0 THEN RAISE Bank.BadAmount; END;
LOCK self.lock DO
self.balance := self.balance + amount;
END;
END Deposit;
Withdraw the money, making sure to serialize access with others
trying to operate on this account.
PROCEDURE Withdraw (self: Account; amount: REAL)
RAISES {Bank.BadAmount, Bank.InsufcientFunds} =
BEGIN
IF amount < 0.0 THEN RAISE Bank.BadAmount; END;
LOCK self.lock DO
IF self.balance < amount THEN
RAISE Bank.InsufcientFunds
END;
self.balance := self.balance - amount;
END;
END Withdraw;
Get the balance, making sure to serialize access with others trying
to operate on this account.
PROCEDURE Balance (self: Account): REAL =
BEGIN
LOCK self.lock DO
RETURN self.balance;
END;
END Balance;
Create a new bank by instantiating all the account objects.
PROCEDURE NewBank () : BankImpl =
VAR b := NEW (BankImpl);
BEGIN
FOR i := FIRST (b.accounts) TO LAST (b.accounts) DO
b.accounts[i] := NEW (Account, lock := NEW (MUTEX));
END;
124
CHAPTER
6
: DEVELOPMENT RECIPES
RETURN b;
END NewBank;
Print a summary of all the active accounts, i.e., ones that have a pos-
itive balance.
PROCEDURE PrintSummary() =
BEGIN
IO.Put (BankName & ": active account information\n");
FOR i := FIRST(bank.accounts) TO LAST(bank.accounts) DO
IF bank.accounts[i].balance > 0.0 THEN
IO.Put (Fmt.Int(i) & ".......$" &
Fmt.Real(bank.accounts[i].balance) & "\n");
END;
END;
END PrintSummary;
Finally, the servers global variables and main body. The main body
prints the summaries for accounts every 60 seconds. Since the net-
work objects runtime forks and manages threads to handle incom-
ing calls, the server can simply loop, printing its summary.
CONST
BankName = "LastNationalBank";
VAR
bank := NewBank();
BEGIN
IO.Put ("Starting bank server.\n");
TRY
(* Export the bank object under "LastNationalBank". *)
NetObj.Export (BankName, bank);
IO.Put ("Bank server was exported as " & BankName & "\n");
LOOP
Thread.Pause (60.0D0);
PrintSummary();
END;
EXCEPT (* If there is a problem, print an error and exit. *)
| NetObj.Error =>
IO.Put ("A network object failure occured.\n");
| Thread.Alerted => IO.Put ("Thread was alerted.\n");
END;
END NetObjServer.
SECTION
6.1
: ROBUST DISTRIBUTED APPLICATIONS: NETWORK OBJ