Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With...

94
1 Distributed Objects Axel-Tobias Schreiner Department of Mathematics and Computer Science University of Osnabrück, Germany This volume contains copies of the overhead slides used in class. This information is available online as part of the World Wide Web; it contains hypertext references to itself and to parts of the system documentation. The example programs are included into this text from the original sources. So that it may be viewed on other platforms, this text also exists as a PDF document. With the Acrobat Reader from Adobe the text can be printed on Windows systems. The text is not a complete transcript of the lectures. For self study one would have to consult books on client/server programming and Java, as well as parts of the system documentation; a rudimentary knowledge of Java and C is assumed. Contents 0 Introduction 1 1 Case Study 3 2 Distributed Objects 55 3 CORBA and IDL 75

Transcript of Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With...

Page 1: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

1

Distributed ObjectsAxel-Tobias Schreiner

Department of Mathematics and Computer ScienceUniversity of Osnabrück, Germany

This volume contains copies of the overhead slides used in class. This information is available online as part of the World Wide Web; it contains hypertext references to itself and to parts of the system documentation. The example programs are included into this text from the original sources.

So that it may be viewed on other platforms, this text also exists as a PDF document. With the Acrobat Reader from Adobe the text can be printed on Windows systems.

The text is not a complete transcript of the lectures. For self study one would have to consult books on client/server programming and Java, as well as parts of the system documentation; a rudimentary knowledge of Java and C is assumed.

Contents0 Introduction 11 Case Study 32 Distributed Objects 553 CORBA and IDL 75

Page 2: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

2

ReferencesThese slides are developed in the Blue Box of MacOS X using Adobe Framemaker, PhotoShop and Distiller. They are available in the World Wide Web.

Today there are lots of books on UNIX, distributed systems, and client/server programming. Some deal with the protocols, others describe how to program connection mechanisms, and others discuss how to use clients. Here are useful ones:

The following are very informative on object-oriented programming and design:

Papers with some of the programming examples were published in unix/mail and in the Blaue Blätter.

Comer 0-13-474321-0 Internetworking with TCP/IPComer/Stevens 0-13-465378-5 ... Design, Implementation, and InternalsComer/Stevens 0-13-020272-X ... Client-Server Programming and ApplicationsFlanagan 1-56592-487-8 Java in a Nutshell (3rd Edition)Flanagan et al. 1-56592-483-5 Java Enterprise in a NutshellFlanagan 1-56592-371-5 Java Examples in a NutshellFlanagan 1-56592-488-6 Java Foundation Classes in a NutshellKernighan/Ritchie 3-446-15497-3 The C Programming LanguageKernighan/Pike 3-446-14273-8 The UNIX Programming EnvironmentLea 3-8273-1243-4 Concurrent Programming in JavaStevens 0-13-949876-1 UNIX Network ProgrammingStevens 0-201-63346-9 TCP/IP Illustrated, Vol. 1: The ProtocolsTanenbaum 3-925328-079-3 Computer Networks

Budd 0-201-82419-1 An Introduction to Object-Oriented ProgrammingCooper 0-201-48539-7 Java Design PatternsGamma et al. 0-201-63361-2 Design PatternsMeyer 0-13-629155-4 Object-Oriented Software Construction

Page 3: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

3

1Case Study

ProblemObjects use messages to communicate and may freely switch the roles of client and server. In this chapter the messages will not be executed as function calls; rather, they will be transmitted across serial data streams. In the course of implementation one discovers many principles of distributed programming without even needing network programming.

The first running example is a program to determine the current time and date.

1.1 An All-In-One SolutionThe current time can be obtained from a new instance of Date:

System.out.println(new Date());

An ‘‘object oriented’’ solution could distribute the job among a TimeServer as Model and a TimeClient as View and Controller, which agree on Time as a common interface:

jdo/1/Time.java

// interface used by client, implemented by server

public interface Time { String getTime ();}

jdo/1/TimeServer.java

// server for Time interface

import java.util.Date;

public class TimeServer implements Time { public String getTime () { return new Date().toString(); }}

jdo/1/TimeClient.java

// client for Time interface

public class TimeClient { public static void main (String args []) { System.err.println(new TimeServer().getTime()); }}

The server must be created before the client can issue a query. Anyhow:

$ javac TimeClient.java; java TimeClientWed Oct 06 05:28:49 GMT 1999

Page 4: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

4

1.2 Proxies With Java MessagesProxies (stand-ins) are introduced to separate client and server without them knowing it:

Client and server continue to communicate with other objects using function calls returning results. The proxies can mutually agree on rather arbitrary mechanisms; e.g., they can employ very general messages and use java.lang.reflect at the server:

jdo/2/ServerProxy.java

// server-side proxy for server object, messages server

final class ServerProxy { Object server;

Object forward (String method, Object[] args) throws Throwable { Class[] classes = new Class[args.length]; for (int i = 0; i < args.length; ++ i) classes[i] = args[i].getClass(); if (Boolean.getBoolean("trace")) System.err.println("server"); return server.getClass().getMethod(method, classes).invoke(server, args); }}

The client has to prepare appropriate data:

jdo/2/Proxy.java

// base class for client-side proxies

public class Proxy { protected final Object forward (String method, Object[] args) throws Throwable { if (Boolean.getBoolean("trace")) System.err.println("client"); return serverProxy.forward(method, args); }

While the approach is patterned after NeXT’s Portable Distributed Objects for Objective C, in Java the client needs to see the interface that it expects from the server.

If the server is accessed using java.lang.reflect no further interface is required.

On the client side the interface is implemented with a problem-specific Proxy:

jdo/2/TimeProxy.java

// client-side proxy for Time server object, messages server-side proxy

public final class TimeProxy extends Proxy implements Time { public String getTime () { String result = null; try { result = (String)forward("getTime", new Object[0]); } catch (Throwable t) { t.printStackTrace(); System.exit(1); } return result; }

While error handling is rudimentary, at least the server remains unchanged.

client proxy server

Time

proxy

Page 5: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

5

InitializationThe TimeClient instance only needs a TimeProxy instance as it’s server:

jdo/2/TimeClient.java

// client for Time interface

public class TimeClient { public static void main (String args []) { System.err.println(new TimeProxy().getTime()); }}

A Proxy knows that it must cooperate with a ServerProxy:

jdo/2/Proxy.java

// peer proxy creation protected final ServerProxy serverProxy = new ServerProxy();}

A TimeProxy knows that there is a TimeServer behind the ServerProxy. The implementation works because the base class is initialized before any derived class:

jdo/2/TimeProxy.java

// server creation { serverProxy.server = new TimeServer(); }}

Time and TimeServer are reused unchanged.

$ CLASSPATH=.:../1 javac TimeClient.java$ CLASSPATH=.:../1 java -Dtrace=true TimeClientclientserverWed Oct 06 05:43:18 GMT 1999

-D defines the value of a property which may be retrieved, e.g., using Boolean.getBoolean(String).The trace shows that the proxies participate in the execution.

Page 6: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

6

1.3 Proxies With Java PipesAs long as Proxy and ServerProxy communicate with each other independent of server and client, the data for a method invocation can be represented as an object:

jdo/3/Message.java

// wraps method call

import java.io.Serializable;

final class Message implements Serializable { final String method; final Object[] args; Message (String method, Object args []) { this.method = method; this.args = args; }}

Serializable means that a Message instance can be written to an ObjectOutputStream and read from an ObjectInputStream.

However, args[] is also transferred — this only works if each element in turn is Serializable or Externalizable, too. It is the case for a number of standard Java classes.

Assuming that the result of a method invocation may also be transferred, Proxy can be reimplemented:

jdo/3/Proxy.java

// base class for client-side proxies

import java.io.ObjectInputStream;import java.io.ObjectOutputStream;

public class Proxy { protected final ServerProxy serverProxy = new ServerProxy(this); protected ObjectOutputStream out; protected ObjectInputStream in;

protected final Object forward (String method, Object[] args) throws Throwable { if (Boolean.getBoolean("trace")) System.err.println("request"); out.writeObject(new Message(method, args)); if (Boolean.getBoolean("trace")) System.err.println("reply"); return in.readObject(); }}

By now the method invocation can be sent to wherever one can get with Java streams.

Page 7: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

7

Server ThreadIf the proxies are connected using Java streams the server should be able to block in a read() without the client being blocked at the same time. This necessitates a separate main loop in ServerProxy:

jdo/3/ServerProxy.java

// server-side proxy for server object, messages serverimport java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.PipedInputStream;import java.io.PipedOutputStream;

final class ServerProxy extends Thread { Object server; private ObjectOutputStream out; private ObjectInputStream in; public void run () { // server loop try { for (;;) { if (Boolean.getBoolean("trace")) System.err.println("receive"); Message message = (Message)in.readObject(); out.writeObject(forward(message.method, message.args)); if (Boolean.getBoolean("trace")) System.err.println("send"); } } catch (Throwable t) { t.printStackTrace(); System.exit(1); } }

forward() remains unchanged. When the objects are constructed, one can for example connect ServerProxy and Proxy with pipelines to securely transfer the objects:

jdo/3/ServerProxy.java

// pipeline connection and start of server ServerProxy (Proxy peer) { try { PipedOutputStream request = new PipedOutputStream(),

out = new PipedOutputStream(); PipedInputStream reply = new PipedInputStream(),

in = new PipedInputStream(); in.connect(request); out.connect(reply); peer.out = new ObjectOutputStream(request); this.out = new ObjectOutputStream(out); peer.in = new ObjectInputStream(reply); this.in = new ObjectInputStream(in); } catch (IOException e) { e.printStackTrace(); System.exit(1); } setDaemon(true); start(); }

ObjectOutputStream must be constructed prior to ObjectInputStream because a header is already transferred during construction. ServerProxy is a daemon thread; therefore, the program ends once main() ends.

Page 8: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

8

ExecutionThe remaining code gets reused: Time, TimeClient, and TimeServer remain unchanged. TimeProxy is derived from Proxy but as long as the call of forward() is not modified TimeProxy need not be recompiled. It takes a fancy CLASSPATH to find the proper versions of all classes:

$ CLASSPATH=.:../1 javac ServerProxy.java$ CLASSPATH=.:../2:../1 java -Dtrace=true TimeClientreceiverequestreplyserversendreceiveWed Oct 13 13:26:52 GMT 1999

The trace demonstrates that the server is waiting for further calls.

Remote Procedure CallThe implementation has progressed far enough to send various procedure calls with arguments from a client to a server, have them executed there, and receive a result. The client uses an interface to ensure that the method call is legal.

Objects from arbitrary classes can be used as arguments and results, as long as they can be transported over an object stream. Java guarantees that such objects are represented in a platform-independent fashion.

Server and client do not share a global state which both might use or modify.

To make this implementation suitable for real applications, one would have to ensure that errors in executing the procedure on the server are bounced back to the client. Presently, they crash the server.

getMethod().invoke() is not such a good idea: In this case the signature of the method is computed at the server at runtime — this could well be a more specific signature than the interface required and it need not be found at the server.

Page 9: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

9

1.4 Distributed Objects With Java Pipes

SymmetryObjects tend to talk back; executing a message usually requires further messages which often are sent back to the sender or to argument objects. This means that the roles of client and server are frequently switched. The cycle

Client: Server: send request receive request compute send reply receive reply

must be interruptible by callbacks:

Client: Server: send request receive request compute send request receive request compute send reply receive reply compute send reply receive reply

Server and client are almost symmetrical — both contain forward() from Proxy and the loop in run() from ServerProxy in section 1.3, page 6, to send a message, receive one, and execute it.

Server and Client thus become subclasses of Proxy. Additionally a Thread is generated for the first Server so that it may reply independently.

A message must be designed so that after a Request a successful Reply with a result may be distinguished from a Throw for error messages.

Page 10: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

10

Secondary Server ObjectsA dialog starts once a client connects to a server. Subsequently, more objects get involved in the dialog which need neither be in the same environment nor have been sent as arguments.

It stands to reason that one creates as few connections as possible; but then several proxies must share a connection and a Request must include the recipient.

SomeProxy client: SomeCaller server: send server.request receive server.request forward {

AnyProxy otherClient: send otherServer.request receive otherServer.request otherServer.forward {

AnyCaller otherServer: forward ( )

} send reply receive reply

} send reply receive reply

This dialog scenario involves the black proxy pair client and server as well as the red proxy pair otherClient and otherServer. Apparently, client gets a job rather than an answer and asks otherServer locally for help.

The scenario can be extended at will; it is, however, completely nested, as long as only one thread accesses one connection.

There is an identification problem: A server proxy knows the object that it represents to the connection by it’s local address. A client proxy has to send a ‘‘name’’ such as otherServer over the connection so that the appropriate server proxy may be found.

Java does not have addresses or other unique identifiers for objects — and even addresses are only unique on their own side of a connection. Therefore, there have to be tables to manage proxy identification.

The tables start when the first client and server meet as the connection is established — perhaps the first server signs up with a fairly public registry which the first client can search.

The tables also must find an existing server proxy for a local object, so that it’s client proxy may represent the object on the other side. If a client proxy is returned, one must be able to find the corresponding server proxy and thus the actual local object, that really is returned.

Tables for first servers are very globally known, maybe even beyond a single computer and across a network. Tables for secondary servers are specific to a connection. The object streams used thus far should be paired as a Connection and improved so that the transition from server to client proxy becomes invisible.

Page 11: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

11

ImplementationThe foundation for distributed objects is best collected into a package jdo. The package contains three important classes that are visible externally: Client and Server are the base classes for interface-specific proxies; they have a common, invisible base class Proxy which must take care of identification and access to the bidirectional connection.

Connection encapsulates the end of a bidirectional connection; it contains a number of invisible classes which transparently transmit messages so that at each end they point to the appropriate local objects — proxies or real objects. Classes with a Peer interface are used for identification: their objects can be transmitted and found in tables on each side. Instances of ClientPeer identify Client objects and are used to search for Server objects, instances of ServerPeer identify Server objects and are used to search for Client objects.

Pipe, finally, is an example to demonstrate that connections can be based on Java pipes. It shows how to provide global access to the topmost, publicly known servers within a single JVM.

Altogether the system consists of very many small classes with very localized knowledge. In spite of built-in tracing it is quite confusing to follow a message through the system because of the many classes that are involved. Therefore, the subsequent description proceeds top-down in layers and describes those classes together which cooperate on the server and the client side in each layer.

Information Hiding In Javajdo is supposed to encapsulate communication as far as possible. private is used if a class hides it’s information even from subclasses. protected exports information from a package, but only to subclasses. public makes information generally accessible. Information without any of these attributes is accessible anywhere in the package but not in external subclasses; therefore, package access can be used to protect information from dubious subclasses. It should be noted, however, that one can at any time insert a class into a package, even if only the class files are available.

abstract means for a class that there cannot be instances. If a method is abstract it must be implemented in subclasses before there can be objects.

If an instance variable is marked final it must be initialized at the latest in all constructors —if there are inner classes the compiler seems to demand initialization as part of the definition. Once initialized, a final variable cannot be assigned to anymore.

A final method cannot be overwritten, a final class cannot be subclassed. Both is intended to permit the compiler to generate more efficient code.

jdo contains many comments starting with /** and preceding the things being commented on. From these comments and from the program structure itself javadoc generates a very useful documentation.

Page 12: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

12

MethodsTimeProxy, TimeCaller, jedi

At the top layer a client like TimeProxy provides the methods

jdo/4/time/TimeProxy.java

// generated by jedi 0.5 (c) 1999 Axel T. Schreinerpublic final class TimeProxy extends jdo.Client implements Time {

public java.lang.String getTime () { java.lang.String result = null; try { if (java.lang.Boolean.getBoolean("trace")) java.lang.System.err.println("forward getTime/0 ("); result = (java.lang.String)forward(0, new java.lang.Object[0]); if (java.lang.Boolean.getBoolean("trace")) java.lang.System.err.println("forward getTime )"); } catch (java.lang.RuntimeException e) { throw e; } catch (java.lang.Throwable t) { t.printStackTrace(); java.lang.System.exit(1); } return result; }

which a server like TimeCaller eventually invokes:

jdo/4/time/TimeCaller.java

// generated by jedi 0.5 (c) 1999 Axel T. Schreinerpublic final class TimeCaller extends jdo.Server {

public java.lang.Object forward (int method, java.lang.Object[] p) throws java.lang.Throwable { Time receiver = (Time)object; java.lang.Object result = null; switch (method) { case 0: result = receiver.getTime(); break; default: throw new java.lang.Error(method+": unknown method number"); } return result; }

Numbers are used to avoid glitches in using getMethod().invoke().

These classes are not part of jdo; they are interface-specific and it turns out that they can be generated from the interface with a rudimentary preprocessor. jedi loads an interface by name, analyzes it with the techniques of java.lang.reflect and generates the two proxy classes:

$ java jedi.Main -caller TimeCaller.java -proxy TimeProxy.java -trace Time

jedi could be integrated with the construction of the proxies but it is not clear whether the methods are always returned in the same order.

Page 13: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

13

Call and ResultProxy, Client, Server

Client and Server belong to jdo and are superclasses of TimeProxy and TimeCaller. They are tied to a communication line with each endpoint described by a Connection managed by the common superclass Proxy:

jdo/4/jdo/Proxy.java

// Proxypackage jdo;/** base class for proxies, holds a Connection and an Id. proxy() is used from outside the package, hence public. */public abstract class Proxy { /** access to streams and conversion tables. */ final Connection wire; /** managed by the Connection. */ final Connection.Id id; Proxy (Connection wire, Connection.Id id) { this.wire = wire; this.id = id; } public String toString () { return id+" "+super.toString(); } /** arrange for an object to be proxied on the same connection. */ public Object proxy (String interfaceName, Object object) { wire.proxy(interfaceName, object); return object; }}

Proxy also stores the Identification, page 15, which the Connection objects on both ends use to associate corresponding Client and Server instances.

A secondary server is created for a connection by registering it together with it’s interface at an arbitrary proxy, a Client or a Server. Each Proxy knows the connection and passes the registration on to Server Registration, page 18.

The method proxy() returns it’s argument so that it can easily be cascaded, e.g., as part of passing parameters.

Page 14: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

14

Client is the superclass of the interface-specific TimeProxy and has to use forward() to pass the method call along:

jdo/4/jdo/Client.java

// Clientpackage jdo;/** base class for interface-specific proxies representing servers on the client side, holds a ClientPeer. */public abstract class Client extends Proxy { /** called to implement AnyProxy.method(). @see Connection */ protected final Object forward (int method, Object[] args) throws Throwable { return wire.forward(this, method, args); }

Connection is charged with the actual transfer. The Client includes this because the Connection must be able to compute the message receiver.

Server is the superclass of the interface-specific TimeCaller which in forward() processes the method call. Server provides the reference to the actual server object:

jdo/4/jdo/Server.java

// Serverpackage jdo;/** base class for interface-specific callers representing objects on the server side, holds a ServerPeer. */public abstract class Server extends Proxy { /** represented by this. */ protected final Object object; /** must pass request on to object, called by Connection even if AnyCaller is in a different package. */ public abstract Object forward (int method, Object[] args) throws Throwable;

Page 15: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

15

IdentificationConnection.Peer, Connection.Id, Connection.ClientPeer, Connection.ServerPeer

Proxies are not Serializable because they are only supposed to exist at one end of a connection. Therefore, a Proxy always contains an identification that can be transmitted and from which the Connection at the other end can produce the corresponding object using object():

jdo/4/jdo/Connection.java

/** base for peers, i.e., symbolically transferable items. */ private static interface Peer extends Serializable { /** returns object corresponding to peer. */ Object object (Connection wire); }

An integer value is used to identify proxies. It is encapsulated in the base class Id in such a way that it can be found in hashtables independent of the actual class:

jdo/4/jdo/Connection.java

/** base class for transferable identifiers, known to Proxy. Server and Client have 'equal' peers, either can be used for searching. */ static abstract class Id implements Peer { private final int id; // local identifier /** servers. (1.1) compiler does not permit this(++serverId); */ Id () { synchronized(Id.class) { this.id = ++serverId; }} /** clients. */ Id (Id server) { this.id = server.id; } public int hashCode () { return id; } public boolean equals (Object id) { return id == this || (id instanceof Id && ((Id)id).id == this.id); } public String toString () { return ""+id; } } private static int serverId; // locally unique

For a server an Id is created with a new integer value; a client uses the value from the server. Thus, the value always leads to corresponding proxies when it is presented to appropriate tables.

The value is unique within a Connection because it is drawn from the monotone serverId.

Page 16: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

16

The ServerPeer knows the interface which it’s server implements because this information has to be transmitted across the connection:

jdo/4/jdo/Connection.java

/** knows the interfaceName needed at the client side. public needed to implement AnyProxy(). */ public static final class ServerPeer extends Id { final String interfaceName; ServerPeer (String interfaceName) { this.interfaceName = interfaceName; if (Boolean.getBoolean("trace")) System.err.println("new: "+this); } public String toString () { return "["+interfaceName+" "+super.toString()+"]"; } public Object object (Connection wire) { return wire.locate(this); } }

The Connection has to maintain tables from which locate(ServerPeer) can determine a Client. Similarly the CallerPeer produces on the server side with object() the server, e.g., TimeCaller:

jdo/4/jdo/Connection.java

/** does not know anything, dereferences to AnyCaller. */ private static class CallerPeer extends Id { CallerPeer (Id server) { super(server); if (Boolean.getBoolean("trace")) System.err.println("new: "+this); } public String toString () { return "["+super.toString()+"]"; } public Object object (Connection wire) { return wire.locate(this); } }

However, a Client contains a ClientPeer, which directly leads to the object which is represented by it’s Server:

jdo/4/jdo/Connection.java

/** does not know anything, dereferences to AnyCaller.object. */ private static final class ClientPeer extends CallerPeer { ClientPeer (Id server) { super(server); } public Object object (Connection wire) { return ((Server)super.object(wire)).object; } public String toString () { return "["+super.toString()+"]"; } }

Page 17: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

17

Client RegistrationA Client such as TimeProxy has to know it’s ServerPeer:

jdo/4/time/TimeProxy.java

// TimeProxy public TimeProxy (jdo.Connection wire, jdo.Connection.ServerPeer peer) { super(wire, peer); }}

jdo/4/jdo/Client.java

/** called to implement AnyProxy(). */ protected Client (Connection wire, Connection.ServerPeer server) { super(wire, wire.client(server)); wire.register(this); if (Boolean.getBoolean("trace")) System.err.println("new: "+this); }}

client(ServerPeer) has to create a ClientPeer and register(Client) maintains the table which maps identifications to instances of Client for the benefit of locate():

jdo/4/jdo/Connection.java

// ------------------------------------------------------------ client registry private Hashtable clients; // peer -> client

/** creates Id to insert into Client; server must be known before client. */ Id client (Id server) { return new ClientPeer(server); } synchronized void register (Client client) { check(); clients.put(client.id, client); } synchronized Client locate (ServerPeer peer) { check(); Client client = (Client)clients.get(peer); if (client != null) return client; Throwable t = null; try { client = (Client)Class.forName(peer.interfaceName + "Proxy")

.getConstructor(new Class[] { Connection.class, ServerPeer.class }) .newInstance(new Object[] { this, peer });

} catch (ClassNotFoundException e) { t = e; } catch (NoSuchMethodException e) { t = e; } catch (InvocationTargetException e) { t = e.getTargetException(); } catch (InstantiationException e) { t = e; } catch (IllegalAccessException e) { t = e; } finally { if (t != null)

throw new RuntimeException(peer.interfaceName+": "+t+": no client"); } return client; }

In the course of Transfer, page 25, locate(ServerPeer) is called to create the Client.

Page 18: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

18

Server RegistrationTo create a Server and a ServerPeer an interface has to be supplied:

jdo/4/time/TimeCaller.java

// TimeCaller public TimeCaller (jdo.Connection wire, java.lang.String interfaceName, java.lang.Object object) { super(wire, interfaceName, object); }}

jdo/4/jdo/Server.java

/** called to implement AnyCaller(). */ protected Server (Connection wire, String interfaceName, Object object) { super(wire, wire.server(interfaceName)); this.object = object; wire.register(this); if (Boolean.getBoolean("trace")) System.err.println("new: "+this+" serves "+object); }}

server(String) has to create a ServerPeer and register(Server) which maps identifications to instances of Server for the benefit of locate():

jdo/4/jdo/Connection.java

// ------------------------------------------------------------ server registry private Hashtable servers; // peer -> server private OpenHashtable serverByObject; // object -> server

/** creates Id to insert into Server; interfaceName must be known. */ Id server (String interfaceName) { return new ServerPeer(interfaceName); } synchronized void register (Server server) { check(); servers.put(server.id, server); serverByObject.put(server.object, server); } synchronized Server locate (CallerPeer peer) { check(); Server server = (Server)servers.get(peer); if (server == null) throw new Error(peer+": botched server"); // server must exist before client return server; }

synchronized Server locate (Object object) { // may return null check(); return (Server)serverByObject.get(object); }

Page 19: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

19

This time, however, there are two tables and two locate() methods: instances of Client have to be related to their Server objects and Server instances must also be located for arbitrary objects which they represent. The latter requires its own OpenHashtable which uses a Hashtable and Vector objects to find entries based on identity and not on equal(). The search is mostly linear and not cheap.

jdo/4/jdo/Connection.java

/** find/create server for object on this connection. */ public synchronized Server proxy (String interfaceName, Object object) { check(); Server server = (Server)serverByObject.get(object); if (server != null) return server;

Throwable t = null; try { server = (Server)Class.forName(interfaceName + "Caller")

.getConstructor(new Class[] { Connection.class, String.class,Object.class })

.newInstance(new Object[] { this, interfaceName, object }); } catch (ClassNotFoundException e) { t = e; } catch (NoSuchMethodException e) { t = e; } catch (InvocationTargetException e) { t = e.getTargetException(); } catch (InstantiationException e) { t = e; } catch (IllegalAccessException e) { t = e; } finally { if (t != null)

throw new RuntimeException(interfaceName+": "+t+": no server"); } return server; }

Given an instance of Connection for the first server, proxy() can hopefully be used to create the first server. It is returned so that it’s identification can be passed to the first client.

More servers can be created by sending proxy() to known proxies because they pass the message on to their Connection.

The design does not permit sending proxies over unrelated connections.

Page 20: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

20

StreamsConnection.In, Connection.Out

The tables are used to extend the Object-Streams of the Connection so that proxies are replaced en route:

jdo/4/jdo/Connection.java

// -------------------------------------------- streams for peer/proxy exchange

private Out out; // to network private In in; // from network

/** performs proxy replacement on output, needed to open a Connection. */ public final class Out extends ObjectOutputStream { /** requires permission to replace peer objects on output, writes header. */ public Out (OutputStream out) throws IOException { super(out); enableReplaceObject(true); } /** replaces proxy or proxied object by it's peer. */ protected Object replaceObject (Object object) throws IOException { if (! (object instanceof Proxy)) { Server server = locate(object);

if (server == null) return object;object = server;

} if (Boolean.getBoolean("trace")) System.err.println("replace: "+object); return ((Proxy)object).id; } }

On output, for each object but proxies there is a search for a potential Server. For the Server and for every other Proxy only the identification is output.

replaceObject() is called in an ObjectOutputStream if an object is seen for the first time. Unless a SecurityManager prohibits it, the object can be replaced at this point.

Page 21: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

21

Similarly, an ObjectInputStream allows resolveObject() to substitute for an object:

jdo/4/jdo/Connection.java

/** performs peer resolution on input, needed to open a Connection. */ public final class In extends ObjectInputStream { /** requires permission to resolve peer objects on input, blocks until header can be read. */ public In (InputStream in) throws IOException { super(in); enableResolveObject(true); } /** replaces peer by it's object. */ protected Object resolveObject (Object object) throws IOException { if (object instanceof Peer) {

object = ((Peer)object).object(Connection.this); if (Boolean.getBoolean("trace"))

System.err.println("resolve: "+object); } return object; } }

On input, only Peer show up for proxies and they implement object() to locate the actual objects.

Id and the subclasses have to be declared static in Connection because otherwise the Connection would be transmitted during serialization. In and Out are member classes of their Connection because they access the tables.

object() is sent to a Peer and includes the relevant Connection so that locate() can be used to check it’s tables.

Page 22: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

22

A Connection is bidirectional. Logically it should provide the typical connection and transfer operations:

jdo/4/jdo/Connection.java

/** connects to network; must be called to enable operation. */ public synchronized void open (In in, Out out) { if (clients != null) throw new RuntimeException("connection open"); clients = new Hashtable(); servers = new Hashtable(); serverByObject = new OpenHashtable(); this.in = in; this.out = out; }

/** shuts down, tries to close streams and permit peer recycling. */ public synchronized void close () { if (clients != null) { clients = null; servers = null; serverByObject = null; if (in != null) try { in.close(); } catch (Exception e) { } if (out != null) try { out.close(); } catch (Exception e) { } in = null; out = null; } } private void check () { if (clients == null) throw new RuntimeException("connection closed"); }

open() deposits the Object-Streams. This is not hidden in the Connection constructor so that the streams may be constructed in any order.

close() releases the streams so that the proxies notice that they cannot communicate anymore.

The proxy tables exist exactly between open() and close(). The other methods call check() to make sure that there really is a usable connection.

Page 23: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

23

MessagesConnection.Message, Connection.Request, Connection.Reply, Connection.Throw

Messages, i.e., representations for the data passed over the connection, are hidden in Connection. Different messages are distinguished by their classes. A common abstract baseclass Message manages unique tags which could be used to pair requests and replies:

jdo/4/jdo/Connection.java

/** base class for everything on a Connection. */ private static abstract class Message implements Serializable { private static int requestTag; final int tag; /** request: new tag. */ Message () { tag = ++ requestTag; } /** reply or error: request's tag. */ Message (Request request) { tag = request.tag; } }

A Request is sent to a Server for processing by it’s server object. Request contains a receiver which originates on the Client and references the Server on the other side of the connection. A Request always receives a new tag from Message because it starts a new round in the dialog.

jdo/4/jdo/Connection.java

/** sent from Client to Server; ClientPeer and ServerPeer share same id. Encapsulates peer.method(args); arguments are assumed to be Serializable or Externalizable. method and args are public for the benefit of AnyCaller. */ private static final class Request extends Message { final Object receiver; final int method; final Object[] args; Request (Id receiver, int method, Object[] args) { this.receiver = receiver; this.method = method; this.args = args; if (Boolean.getBoolean("trace")) System.err.println(this); } public String toString () { StringBuffer buf = new StringBuffer("("); buf.append(tag).append(") request: "); buf.append(receiver).append("."); buf.append(method).append("("); for (int i = 0; i < args.length; ++ i) {

buf.append(args[i]); if (i < args.length-1) buf.append(", "); } buf.append(")"); return buf.toString(); } }

Page 24: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

24

A Reply contains a result object or null and reuses the tag of the Request; this can be used to check on nesting or to process replies asynchronously.

jdo/4/jdo/Connection.java

/** sent from Server to Client. Encapsulates result of a Request, which is null for void; result is assumed to be Serializable or Externalizable. Exactly one Reply or Throw must follow each Request. */ private static final class Reply extends Message { final Object result; Reply (Request request, Object result) { super(request); this.result = result; if (Boolean.getBoolean("trace")) System.err.println(this); } public String toString () { return "("+tag+") reply: "+result; } }

In Java errors are reported by means of more or less serious Throwable objects which are sent with throw rather than return; jdo provides Throw as a subclass of Message:

jdo/4/jdo/Connection.java

/** encapsulates exception resulting from Request. Exactly one Throw or Reply must follow each Request. */ private static final class Throw extends Message { final Throwable throwable; Throw (Request request, Throwable throwable) { super(request); this.throwable = throwable; if (Boolean.getBoolean("trace")) System.err.println(this); } public String toString () { return "("+tag+") throw: "+throwable; } }

Page 25: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

25

TransferA Connection should only transport messages; therefore, the transfer operations can be defined in terms of messages:

jdo/4/jdo/Connection.java

// transfers

private synchronized Message read ()throws ClassNotFoundException, IOException {

check(); boolean inTrouble = true; try { Message result = (Message)in.readObject(); inTrouble = false; return (Message)result; } catch (EOFException e) { return null; // indicates end of file } finally { // rest remain thrown if (inTrouble) close(); } } private synchronized void write (Message message) throws IOException { check(); boolean inTrouble = true; try { out.writeObject(message); inTrouble = false; } finally { if (inTrouble) close(); } }

Both, an Exception, or the end of transmission, result in the connection being terminated with close(). The usual Object-Stream exceptions are not caught, only an EOFException is converted into a null result. Further transfer activities would definitely trigger errors in check().

finally is particularly suited to implement this; the phrase is executed in any case, regardless if a try concludes normally or by way of another Exception.

Page 26: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

26

Message is hidden outside of Connection; therefore, read() and write() are not too useful. TimeProxy and it’s superclass Client use forward() to ask Connection to transfer:

jdo/4/jdo/Connection.java

/** implements AnyProxy.method(), receives requests until reply, processes reply. @param client provides proxy's peer, leads to receiver. @param method to be called for server's object. @param args array of arguments, will be wired. @return resulting object, null for void, can be a proxy. */ synchronized Object forward (Client client, int method, Object[] args) throws Throwable { Request request = new Request(new CallerPeer(client.id), method, args); write(request);

Message message = receive(); if (message == null) throw new Error(method+": no reply"); if (message.tag != request.tag) throw new Error(message+": botched tag: "+request);

if (message instanceof Reply) return ((Reply)message).result; throw ((Throw)message).throwable; }

Another problem has to be circumvented here: Client enters itself as the receiver because this Proxy gets replaced during transfer via Out and In.

If a Client such as a TimeProxy is used as an argument for a method it must be replaced by a real object such as a TimeServer represented by the TimeProxy. However, if Client is the receiver rather than an argument, it must be replaced by the TimeCaller on the other side because only this proxy can use the data in the Request to construct the actual call to the TimeServer.

The CallerPeer addresses precisely this case: it can be constructed from a ClientPeer, it is not a Proxy and thus passes unchanged through Out, but as a Peer it is replaced at In, and it’s object() method produces the Server and not the actual object.

Peer is deliberately designed to permit other replacements between In and Out.

Page 27: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

27

The Request is received at the other end, processed, and acknowledged with Reply or Throw. The returning Message should be accepted with receive() and passed along. If, however, a callback comes across the connection, rather than a Reply or Throw, a Request is received and has to be processed and acknowledged.

Therefore, receive() handles an arbitrary Message and returns only Reply, Throw, or null:

jdo/4/jdo/Connection.java

/** loops to handle requests, implements run() for threaded server. @return matching non-Request or null at eof. */ public synchronized Message receive () { Message message; try { while ((message = read()) != null && (message instanceof Request)) {

Request request = (Request)message;Server server = (Server)request.receiver;

Message reply;try { if (java.lang.Boolean.getBoolean("trace")) java.lang.System.err.println("invoking: "+request); reply = new Reply(request, server.forward(request.method, request.args));} catch (Throwable t) { reply = new Throw(request, t);}write(reply);

} } catch (ClassNotFoundException e) { throw new Error(e+": unresolvable class in message"); } catch (IOException e) { throw new Error(e+": connection aborted"); } return message; }

It is clear that Out and In must have replaced the receiver, because it is now a Server such as a TimeCaller. The method receive() uses forward() to send the Request data to the Server and then handles the result and the acknowledgement.

This, finally, completes the walk-through.

Page 28: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

28

ConnectingPipe

The runtime system is complete. All that is missing is a first server and client together with two Connection objects that could, e.g., be constructed on top of Java pipes.

Class methods are globally known in a Java Virtual Machine. The class Pipe provides register() to record a name for a server object and an interface:

jdo/4/jdo/Pipe.java

// ------------------------------------------------------------ server registry

private final static Hashtable servers = new Hashtable();// -> sd private final static class ServerDescriptor { final Object server; final String interfaceName; ServerDescriptor (Object server, String interfaceName) { this.server = server; this.interfaceName = interfaceName; } } /** registers the top-level server object. If a previous server is known under the same name it is silently dropped. @param name under which locate() will find the server object. @param server the top-level server object. @param interfaceName the interface name supported by server;

for client-side proxy classes, <tt>Proxy</tt> is appended,for server-side proxy classes, <tt>Caller</tt> is appended.

*/ public static synchronized void register (String name, Object server, String interfaceName) { servers.put(name, new ServerDescriptor(server, interfaceName)); if (Boolean.getBoolean("trace")) System.err.println("registered: \""+name+"\" "+interfaceName+" "+server); }

locate() then finds an existing client or a registered server

jdo/4/jdo/Pipe.java

// ------------------------------------------------------------ client registry

private final static Hashtable clients = new Hashtable();// -> client /** locates a previously registered top-level server object and creates a connection joining a pair of proxies. @param name under which register() registered the server object. @return client-side proxy from a class interfaceName+Proxy. */ public static synchronized Client locate (String name) throws IOException, RuntimeException { Client c = (Client)clients.get(name); if (c == null) { ServerDescriptor sd = (ServerDescriptor)servers.get(name); if (sd == null) throw new RuntimeException(name +": server not found");

Page 29: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

29

and in this implementation constructs the connections and starts the server:

jdo/4/jdo/Pipe.java

// final int server = 0; // reads in[0] and writes out[0] final int client = 1; // reads in[1] and writes out[1] final Connection[] wire = { new Connection(), new Connection() }; { Connection.Out[] out = new Connection.Out[2]; Connection.In[] in = new Connection.In[2]; PipedOutputStream[] _out = { new PipedOutputStream(), new PipedOutputStream() };

PipedInputStream[] _in = { new PipedInputStream(), new PipedInputStream() };

for (int i = 0; i <= 1; ++ i) _in[i].connect(_out[1-i]); // cross!for (int o = 0; o <= 1; ++ o) out[o] = wire[o].new Out(_out[o]);for (int i = 0; i <= 1; ++ i) in[i] = wire[i].new In(_in[i]);for (int w = 0; w <= 1; ++ w) wire[w].open(in[w], out[w]);

}

Server s = wire[0].proxy(sd.interfaceName, sd.server); Thread thread = new Thread() {

public void run () { if (wire[0].receive() != null) throw new Error("unexpected message"); if (Boolean.getBoolean("trace")) System.err.println("thread ends");}

}; thread.setDaemon(true); thread.start(); c = wire[1].locate((Connection.ServerPeer)s.id); clients.put(name, c); } if (Boolean.getBoolean("trace")) System.err.println("located: "+name+" "+c); return c; }

A thread runs receive() at the Connection of the server. locate() is used to construct the client from the Peer of the server — one could also send the server across the connection and retrieve the client at the other end; this would avoid the explicit call to locate().

Page 30: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

30

Remote Procedure CallTime and TimeServer can be used as examples; they have not changed since section 1.1, page 3. The PipeClient employs Pipe:

jdo/4/time/PipeClient.java

// Time Pipe client

import jdo.Pipe;

public class PipeClient { public static void main (String args []) { Pipe.register("Time", new TimeServer(), "Time"); try { System.err.println(((Time)Pipe.locate("Time")).getTime()); } catch (Exception e) { e.printStackTrace(); } }}

The trace gives a good impression of the workings of the system. The server side is shown in red:

$ CLASSPATH=.:..:../../1 java -Dtrace=true PipeClient

Pipe registered: "Time" Time TimeServer@66f08

ServerPeer new: [Time 1]

Server new: [Time 1] TimeCaller@66f8f serves TimeServer@66f08

ClientPeer new: [[1]]

Client new: [[1]] TimeProxy@67039located: Time [[1]] TimeProxy@67039

TimeProxy forward getTime/0 (

CallerPeer new: [1]

Request (1) request: [1].0()

In resolve: [Time 1] TimeCaller@66f8f

receive() invoking: (1) request: [Time 1] [email protected]()(1) reply: Sun Oct 10 15:22:01 GMT 1999

TimeProxy forward getTime )Sun Oct 10 15:22:01 GMT 1999

Page 31: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

31

Distributed Objectscpu is a package with a more interesting example: A CpuServer is a simple calculator chip which retrieves Integer values and operators from a vector which it fetches from it’s argument:

jdo/4/cpu/Cpu.java

// Cpu// technology cannot handle one server supporting two interfacespackage cpu;public interface Cpu { /** retrieves Integer array from 'terms' and computes and stores a[0] a[1] a[2] a[3] a[4] ... where the a[odd] must be + - * % /. Various exceptions can happen... */ Cpu cpu (Terms terms) throws Exception; /** lets cpu deliver result of previous computation. */ Integer result (Cpu cpu);}

jdo/4/cpu/Terms.java

// Termspackage cpu;public interface Terms { /** provided by the Cpu client, see {@link Cpu#cpu(cpu.Terms) cpu()}. */ Integer[] terms ();}

jdo/4/cpu/CpuServer.java

// Cpu serverpackage cpu;

public class CpuServer implements Cpu { private int result; public Cpu cpu (Terms terms) throws Exception { Integer[] t = terms.terms(); result = t[0].intValue(); for (int n = 1; n < t.length; n += 2) switch (t[n].intValue()) { case '+': result += t[n+1].intValue(); continue; case '-': result -= t[n+1].intValue(); continue; case '*': result *= t[n+1].intValue(); continue; case '/': result /= t[n+1].intValue(); continue; case '%': result %= t[n+1].intValue(); continue; default: throw new Exception(t[n]+": unexpected"); } return this; } public Integer result (Cpu cpu) { return cpu == this ? new Integer(result) : cpu.result(cpu); }}

Page 32: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

32

A local client with several jobs could look as follows:

jdo/4/cpu/Client.java

// Cpu client// local and private Terms result in IllegalAccess problemspackage cpu;import java.io.Serializable;/** exercises {@link Cpu Cpu}. */public class Client { private static int job; private static Integer[] jobs[] = { { new Integer(2), new Integer('+'), new Integer(3) }, // 0: ok { new Integer(2), new Integer('/'), new Integer(0) }, // 1: zero div { new Integer(2), new Integer('+') }, // 2: array bounds { new Integer(2), new Integer('?') } // 3: unexpected }; /** provides a set of terms depending on global variable <tt>job</tt>. */ public static class Terms implements cpu.Terms, Serializable { public Integer[] terms () { return jobs[job]; } } /** runs each row of global array <tt>jobs</tt>. */ protected static void run (Cpu cpu, Terms terms) { for (job = 0; job < jobs.length; ++ job) try {

System.out.println(cpu.cpu(terms).result(cpu)); } catch (Exception e) { System.err.println(e); } } /** tests without distributing objects. */ public static void main (String args []) { run(new CpuServer(), new Terms()); }}

The client even commits typical errors:

$ CLASSPATH=.. java cpu.Client5java.lang.ArithmeticException: / by zerojava.lang.ArrayIndexOutOfBoundsException: 2java.lang.Exception: 63: unexpected

A distributed solution requires proxies and a PipeClient like the following:

Page 33: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

33

jdo/4/cpu/PipeClient.java

// Cpu Pipe clientpackage cpu;import jdo.Pipe;import jdo.Proxy;public class PipeClient extends Client { /** <tt>java cpu.PipeClient [-proxy]</tt> uses {@link jdo.Pipe Pipe}, optionally proxies Terms. */ public static void main (String args []) { Pipe.register("Cpu", new cpu.CpuServer(), "cpu.Cpu"); try { Cpu cpu = (Cpu)Pipe.locate("Cpu"); Terms terms = new Terms(); if (args.length > 0) ((Proxy)cpu).proxy("cpu.Terms", terms); run(cpu, terms); } catch (Exception e) { e.printStackTrace(); } }}

The trace is particularly illuminating if the Terms are distributed in the opposite direction (the server side is red, the callback blue and green):

This takes care of one call to cpu() with a callback to Terms; getting the result requires another round.

$ CLASSPATH=.. java -Dtrace=true cpu.PipeClient proxied

Pipe registered: "Cpu" cpu.Cpu cpu.CpuServer@66f3c

ServerPeer new: [cpu.Cpu 1]

Server new: [cpu.Cpu 1] cpu.CpuCaller@66fc2 serves cpu.CpuServer@66f3c

ClientPeer new: [[1]]

Client new: [[1]] cpu.CpuProxy@67015located: Cpu [[1]] cpu.CpuProxy@67015

ServerPeer new: [cpu.Terms 2]

Server new: [cpu.Terms 2] cpu.TermsCaller@670b0 serves cpu.Cli-ent$Terms@6706a

CpuProxy forward cpu/0 (

CallerPeer new: [1]

Request (1) request: [1].0(cpu.Client$Terms@6706a)

Out replace: [cpu.Terms 2] cpu.TermsCaller@670b0

ClientPeer new: [[2]]

Client new: [[2]] cpu.TermsProxy@673fd

In resolve: [[2]] cpu.TermsProxy@673fd

In resolve: [cpu.Cpu 1] cpu.CpuCaller@66fc2

receive() invoking: (1) request: [cpu.Cpu 1] [email protected]([[2]] cpu.TermsProxy@673fd)

TermsProxy forward terms/0 (

CallerPeer new: [2]

Request (2) request: [2].0()

In resolve: [cpu.Terms 2] cpu.TermsCaller@670b0

receive() invoking: (2) request: [cpu.Terms 2] [email protected]()

Reply (2) reply: [Ljava.lang.Integer;@66f0aforward terms )

Reply (1) reply: cpu.CpuServer@66f3c

Out replace: [cpu.Cpu 1] cpu.CpuCaller@66fc2

In resolve: [[1]] cpu.CpuProxy@67015forward cpu )

Page 34: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

34

SummaryGiven a bidirectional connection (Transport-Layer) consisting of two loss less byte streams, the endpoints can be encapsulated as two instances of Connection and messages can be passed across.

Messages must be described by an interface; arguments and results must be Serializable or representable by proxies. jedi.Main produces specific subclasses of Client and Server from the interface which can accept messages and pass them across the connection, or receive and process them, respectively:

Conceptually, the communication happens horizontally at each layer. Actually, one communicates on each side from top to bottom until the bottom layer really transfers information horizontally.

read/write communication requires a thread on the server side, which counters this calling hierarchy: partially from the middle (run() in the thread) down to the transfer, partially from the middle upward to the server.

The system can be extended in several places: jedi.Main should accept primitive types such as int and should perhaps consider interface hierarchies. Different transport layers should communicate between processes in one or several systems. Proxies should also be transferable across unrelated connections. If a ClassLoader is integrated one can send .class files right before they are required.

TimeClient main { getTime }

TimeServer getTime { }

TimeProxy getTime { forward }

TimeCaller forward { getTime }

Client forward { wire.forward }

Server abstract forward

Connection forward { write } write { out.writeObject }

Connection receive { read; server.forward } read { in.readObject }

Request ReplyThrow

Out writeObject { replaceObject } replaceObject { id }

In readObject { resolveObject } resolveObject { id.object }

CallerPeer object { wire.locate }ClientPeer object { super.locate.object }

ServerPeer object { wire.locate }

Page 35: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

35

1.5 Alternative Transport Layer: FifosFifos are unidirectional pipes on Unix systems which can be reached by names in the file system. Reader and writer process block until they both near-simultaneously get a connection to a Fifo, and they delay their execution depending on data supply and demand just as they would with common, unnamed pipes. Fifos can only be created on a local file system with a specific system call.

Using two Fifos the examples discussed thus far can be distributed to two different JVM processes:

$ fifos=/tmp/fifo$$$ mkdir $fifos; mkfifo $fifos/Time.request $fifos/Time.reply$ CLASSPATH=.:../../4/time:..:../../4:../../1; export CLASSPATH$ java -Dtrace=true FifoServer $fifos/Time &registered: "/tmp/fifo731/Time" Time TimeServer@80ad203$ java FifoClient $fifos/Timenew: [Time 1]new: [Time 1] TimeCaller@80ad281 serves TimeServer@80ad203replace: [Time 1] TimeCaller@80ad281resolve: [Time 1] TimeCaller@80ad281invoking: (1) request: [Time 1] [email protected]()(1) reply: Mon Oct 25 13:58:17 GMT+03:30 1999Mon Oct 25 13:58:17 GMT+03:30 1999fifo ends$ java -Dtrace=true FifoClient $fifos/Timenew: [Time 2]new: [Time 2] TimeCaller@80ad2c1 serves TimeServer@80ad203replace: [Time 2] TimeCaller@80ad2c1new: [[2]]new: [[2]] TimeProxy@80ad38dresolve: [[2]] TimeProxy@80ad38dlocated: /tmp/fifo731/Time [[2]] TimeProxy@80ad38dforward getTime/0 (new: [2](1) request: [2].0()resolve: [Time 2] TimeCaller@80ad2c1invoking: (1) request: [Time 2] [email protected]()(1) reply: Mon Oct 25 13:58:20 GMT+03:30 1999forward getTime )Mon Oct 25 13:58:20 GMT+03:30 1999$ kill $!$ rm -r $fifos

The trace shows that the server answers several queries, one after another, by opening the Fifos several times — this requires that the operating system returns end of file if the writer of a Fifo terminates. (This is not the case for MacOS X Server and MacOS X DP4.)

Page 36: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

36

FifoServer and FifoClient obtain the first proxies from a new registry:

jdo/5/time/FifoServer.java

// Time Fifo server// java [-Dtrace=true] FifoServer path/Timeimport jdo.Fifo;

public class FifoServer {

public static void main (String args []) { if (args != null && args.length > 0) Fifo.register(args[0], new TimeServer(), "Time"); }}

jdo/5/time/FifoClient.java

// Time Fifo client// java [-Dtrace=true] FifoClient path/Timeimport jdo.Fifo;

public class FifoClient {

public static void main (String args []) { if (args != null && args.length > 0) try { System.err.println(((Time)Fifo.locate(args[0])).getTime()); } catch (Exception e) { e.printStackTrace(); } }}

The client can encounter an IOException because it receives the proxy over the connection.

If a Time service employs Fifos called Time.request and Time.reply in a public folder accessible to FifoClient and FifoServer, it is trivial to implement locate():

jdo/5/jdo/Fifo.java

/** connects to top-level server object, reads server as first object from reply line. This should not be called twice for the same name. @param name designates two Fifos name.request and name.reply. @return client-side proxy from a class interfaceName+Proxy. */ public static synchronized Client locate (String name) throws IOException, ClassNotFoundException { final Connection wire = new Connection(); Connection.In in = wire.new In(new FileInputStream(name+".reply")); Connection.Out out = wire.new Out(new FileOutputStream(name+".request")); wire.open(in, out); Client client = (Client)in.readObject(); if (Boolean.getBoolean("trace")) System.err.println("located: "+name+" "+client); return client; }

Page 37: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

37

When an ObjectInputStream is constructed, a header is read which is produced by the ObjectOutputStream. If this is to work in both directions, register() must create the streams in the opposite order from locate().

jdo/5/jdo/Fifo.java

/** connects and builds thread for top-level server object, writes server as first object to reply line. This should not be called twice for the same name. @param name designates two Fifos name.request and name.reply. @param server the top-level server object. @param interfaceName the interface name supported by server;

for client-side proxy classes, <tt>Proxy</tt> is appended,for server-side proxy classes, <tt>Caller</tt> is appended.

*/ public static void register (final String nm, final Object server, final String interfaceName) { new Thread() { public void run () { final Connection wire = new Connection(); for (;;) try {

Connection.Out out = wire.new Out(new FileOutputStream(nm+".reply")); Connection.In in = wire.new In(new FileInputStream(nm+".request")); wire.open(in, out);

Server s = wire.proxy(interfaceName, server); out.writeObject(s); // client receives server's proxy first

if (wire.receive() != null) throw new Exception("unexpected message"); if (Boolean.getBoolean("trace")) System.err.println("fifo ends");

} catch (FileNotFoundException e) { System.err.println(e); break; } catch (Exception e) { System.err.println(e); } finally { wire.close(); } if (Boolean.getBoolean("trace")) System.err.println("thread ends"); } }.start(); // no daemon! if (Boolean.getBoolean("trace")) System.err.println("registered: \""+nm+"\" "+interfaceName+" "+server); }

The server starts in register() a thread which cannot be a Daemon thread.

The thread creates a new Object-Stream for each Fifo. The Fifos blocks during the open operation until the partner is available — register() itself does not block so that a server can manage several server objects.

The thread uses proxy() to create a server proxy and sends it to the client — when it arrives at the client it becomes a client proxy which is a suitable result for locate().

Afterwards the thread merely executes receive(). If receive() has nothing more to do the Fifos are closed to be reopened with a new partner later. This solution assumes that the partners arrive one after another because the Fifos are not protected against simultaneous access.

Page 38: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

38

Server ModelsA typical server keeps waiting for new calls. Once a call comes in the server establishes a connection to the client and provides service until the connection is terminated.

Different servers might accept calls and provide service on the same connection and in the same process (or thread), or they might support several clients (perhaps up to a maximum number) in parallel.

E.g., the Apache web server creates new server processes up to a configurable number which answer requests near simultaneously.

The so called Internet daemon inetd is started when a Berkeley Unix system is started. It waits for calls for certain services which are configured in /etc/inetd.conf. Depending on the entries, new processes are created which may execute sequentially or in parallel.

The Fifo server presented in this section silently assumes that only one call at a time has to be answered, i.e., calls must not overlap.

Fifos are not too useful for constructing a robust server which is to serve more than one client. If a Fifo has a reader and a writer, another process can open the Fifo and participate in transferring. Fifos preserve message blocks only for reasonably small block sizes (typically less than 8k). Larger blocks are split.

If a Fifo server is to answer several calls, it can use the following typical design: A caller writes a single line into a single, globally known Fifo. The server reads one line after another and finds out which private Fifo to use for the actual connection.

The design arranges for a sensible order of the calls, but the private connections can still be subject to interference. On some Unix and Mach systems one can transmit file descriptors and thus completely hide the actual connections.

accept calls

service

receive()

Client

Page 39: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

39

Global InformationThe CpuServer example for Distributed Objects, page 31, illustrates that thread connections such as Java pipes do not necessarily produce the same results as Fifos or other process connections.

Terms can either be transmitted with cpu():

In this case the server has it’s own instance of Terms. This instance considers job to be a global variable and uses it to select the vector to be processed. However, job does not get changed at the server.

Alternatively, the Terms instances can be sent as proxies with cpu():

This way there is only a single instance of Terms with a vector newly selected by job each time. Java pipes produce the same result in both cases:

$ CLASSPATH=.. java cpu.PipeClient5java.lang.ArithmeticException: / by zerojava.lang.ArrayIndexOutOfBoundsException: 2java.lang.Exception: 63: unexpected

Fifos produce this result only in the second case:

$ fifos=/tmp/fifo$$$ mkdir $fifos; mkfifo $fifos/Cpu.request $fifos/Cpu.reply$ CLASSPATH=..:../../4; export CLASSPATH$ java FifoServer $fifos/Cpu &$ java FifoClient $fifos/Cpu5555$ java FifoClient $fifos/Cpu proxied5java.lang.ArithmeticException: / by zerojava.lang.ArrayIndexOutOfBoundsException: 2java.lang.Exception: 63: unexpected

It is important to understand that global information is not necessarily sent together with objects.

CpuClient

Terms

CpuServercpu

Terms

CpuClient

Terms

CpuServercpu

TermsProxy

Page 40: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

40

1.6 Alternative Transport Layer: TCP SocketsTCP, the Internet transmission control protocol, provides bidirectional connections which can be used to securely transmit byte streams between different computers and processes. A host name and a port number are used as addresses. If the necessary information is passed on the command line, distributed objects can be operated over TCP.

jdo/5/jdo/Tcp.java

/** connects to top-level server object, reads server as first object from reply line. @param name "[host:]portnumber". @return client-side proxy from a class interfaceName+Proxy. */ public static synchronized Client locate (String name) throws IOException, ClassNotFoundException { int n = name.indexOf(":"); Socket socket = n > 0 && n < name.length() ? new Socket(name.substring(0,n), Integer.parseInt(name.substring(n+1))) : new Socket("localhost", Integer.parseInt(name));

if (Boolean.getBoolean("trace")) System.err.println("called: "+socket.getInetAddress().getHostName() +":"+socket.getPort()); final Connection wire = new Connection(); Connection.In in = wire.new In(socket.getInputStream()); Connection.Out out = wire.new Out(socket.getOutputStream()); wire.open(in, out); Client client = (Client)in.readObject(); if (Boolean.getBoolean("trace")) System.err.println("located: "+name+" "+client); return client; }}

locate() gets a port number, optionally preceded by a host name, and tries to construct a Socket which is connected to this address. The server must exist already.

If communication is possible, a Connection is constructed and supplied with the two streams that one can obtain from a Socket.

The first proxy is once again sent by the server.

Page 41: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

41

register() is more difficult because it must first create a ServerSocket where a thread waits for calls from sockets:

jdo/5/jdo/Tcp.java

/** connects and builds thread for top-level server object, writes server as first object to reply line. This should not be called twice for the same name. @param name designates server's TCP port, can be "0". @param server the top-level server object. @param interfaceName the interface name supported by server;

for client-side proxy classes, <tt>Proxy</tt> is appended,for server-side proxy classes, <tt>Caller</tt> is appended.

*/ public static synchronized void register (String name, final Object server, final String interfaceName) throws IOException { final ServerSocket service = new ServerSocket(Integer.parseInt(name)); new Thread() { public void run () { for (;;)

try { Socket call = service.accept(); if (Boolean.getBoolean("trace")) System.err.println("called: "+call.getInetAddress().getHostName()

+":"+call.getPort()); final Connection wire = new Connection(); Connection.Out out = wire.new Out(call.getOutputStream()); Connection.In in = wire.new In(call.getInputStream()); wire.open(in, out);

Server s = wire.proxy(interfaceName, server); out.writeObject(s); // client receives server's proxy first

Thread thread = new Thread() { public void run () {

if (wire.receive() != null) throw new Error("unexpected message");if (Boolean.getBoolean("trace")) System.err.println("thread ends");

} }; thread.setDaemon(true); thread.start(); } catch (IOException e) { System.err.println(e+": while accepting"); }

} }.start(); if (Boolean.getBoolean("trace")) System.err.println("registered: "+service.getLocalPort()

+": "+interfaceName+" "+server); }

accept() returns a socket call which is connected to the caller. This is used to fill a Connection and to send the first proxy. A new thread then worries about receive().

This solution can handle many clients in parallel because the receive() threads operate in parallel with the thread waiting for more calls.

Page 42: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

42

1.7 A ClassLoader For Client ProxiesA JVM has a built-in ClassLoader to load the system classes. If one wants to control how other classes are loaded, different ClassLoader objects can be used or even a new ClassLoader can be implemented.

A client needs to know a class file for an interface such as Time because otherwise the methods cannot be called. The client also needs to have the package jdo in order to make any connection. But if a proxy such as TimeProxy is used, the client should not need to provide it’s implementation because the proxy is supposed to be invisible. Therefore, implementations for client proxy classes should be delivered just before a client proxy is sent.

This takes very few changes in Connection. If an interface is sent with a ServerPeer in Out by replaceObject() for the first time

jdo/6/jdo/Connection.java

/** replaces proxy or proxied object by it's peer. */ protected Object replaceObject (Object object) throws IOException { if (! (object instanceof Proxy)) { Server server = locate(object);

if (server == null) return object;object = server;

} if (Boolean.getBoolean("trace")) System.err.println("replace: "+object); Id result = ((Proxy)object).id; if (result instanceof ServerPeer && classes.get(((ServerPeer)result).interfaceName) == null) {

classes.put(((ServerPeer)result).interfaceName, "");result = new FirstServerPeer((ServerPeer)result);

} return result; }

one substitutes a FirstServerPeer which transmits the contents of the relevant .class file which must be found relative to a codebase property:

jdo/6/jdo/Connection.java

/** includes client proxy's class file; used first time an interface is sent across the wire. */ private static final class FirstServerPeer extends ServerPeer { byte[] code; FirstServerPeer (ServerPeer peer) throws IOException { super(peer); String name = interfaceName.replace('.',File.separatorChar)+"Proxy.class"; File file = new File(System.getProperty("codebase", "."), name); InputStream in = new FileInputStream(file); code = new byte[(int)file.length()]; for (int n = 0, m; code.length > n; n += m) if ((m = in.read(code, n, code.length - n)) < 0)

throw new IOException(name+": unexpected eof"); in.close(); if (Boolean.getBoolean("trace")) System.err.println("new (code "+code.length+"): "+this); }

Page 43: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

43

The FirstServerPeer should not be entered into the Connection tables; therefor a special constructor is added to ServerPeer:

jdo/6/jdo/Connection.java

/** needed to make a FirstServerPeer which is not recorded. */ ServerPeer (ServerPeer peer) { super(peer); this.interfaceName = peer.interfaceName; }

If the FirstServerPeer appears in the In stream at resolveObject() it talks in object() to a JDOClassLoader, which is newly implemented in Connection. This class loader defines the class from the imported bytes

jdo/6/jdo/Connection.java

/** defines class before instantiating it. */ public Object object (Connection wire) { wire.check(); wire.classLoader.defineClass(interfaceName+"Proxy", code); return super.object(wire); } }

before object() uses locate() to create the proxy in the new class. However, locate() explicitly searches using JDOClassLoader:

jdo/6/jdo/Connection.java

// use JDOClassLoader to obtain proxy from network. client = (Client)classLoader.loadClass(peer.interfaceName + "Proxy")

.getConstructor(new Class[] { Connection.class, ServerPeer.class }) .newInstance(new Object[] { this, peer });

The JDOClassLoader must define loadClass() so that the imported bytes are used and defineClass() has to be accessible:

jdo/6/jdo/Connection.java

// JDOClassLoader for proxy classes coming in from the network. JDOClassLoader classLoader; // proxy classes loaded from network private Hashtable classes; // records proxies sent to network

// compatible to 1.1 ClassLoader final static class JDOClassLoader extends ClassLoader { protected Class loadClass (String name, boolean resolve)

throws ClassNotFoundException { Class result = findLoadedClass(name); if (result == null) result = findSystemClass(name); else if (Boolean.getBoolean("trace")) System.err.println("loaded: "+name); if (resolve) resolveClass(result); return result; } void defineClass (String name, byte[] code) { defineClass(name, code, 0, code.length); if (Boolean.getBoolean("trace")) System.err.println("defined: "+name); } }

classLoader and classes are created and deleted just like the other tables.

Page 44: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

44

1.8 Asynchronous Access: Chat Service

This graphical interface can be used to join a chat service: the TextArea displays messages from all participants and the TextField underneath is used for typing to the others.

The client promises that it can receive messages from all participants:

jdo/5/chat/Chat.java

// client publishes messages, server broadcasts messagespackage chat;

public interface Chat { void tell (String message);}

The client connects to a server and finds out where to send his contributions:

jdo/5/chat/Room.java

// client joins chat at a serverpackage chat;

public interface Room { Chat join (Chat withMe);}

One way to implement the chat service is to give each Chatter in a ChatRoom a Person as a partner. A Chatter sends a contribution to his Person which in turn passes it on to all others in the ChatRoom. Each Person is additionally responsible for sending the incoming contributions to it’s Chatter:

To minimize chances of blocking, it seems that each Person should have one thread for input and one for output.

ChatRoom

Chatter Person

run

tell

tellPerson

run

tell

Page 45: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

45

Implementing the client is quite trivial:

jdo/5/chat/Chatter.java

// Chat AWT/Tcp client// java [-Dtrace=true] chat.Chatter [host:]portpackage chat;

import java.awt.Frame;import java.awt.BorderLayout;import java.awt.TextArea;import java.awt.TextField;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.awt.event.WindowEvent;import java.awt.event.WindowAdapter;import jdo.Tcp;import jdo.Proxy;

public class Chatter extends Frame implements Chat { /** starts a GUI client. */ public static void main (String args []) { if (args != null && args.length > 0) try { new Chatter((Room)Tcp.locate(args[0])); } catch (Exception e) { e.printStackTrace(); } } protected TextArea ta = new TextArea(); protected TextField tf = new TextField(); protected Chat crowd; { add(ta, BorderLayout.CENTER); ta.setEditable(false); add(tf, BorderLayout.SOUTH); tf.addActionListener(new ActionListener() { public void actionPerformed (ActionEvent ae) { crowd.tell(tf.getText()); tf.selectAll(); } }); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); pack(); show(); } public Chatter (Room room) { ((Proxy)room).proxy("chat.Chat", this); crowd = room.join(this); } public void tell (String message) { ta.append(message+"\n"); }}

The main program looks for a server and constructs a Chatter. It ensures that it will only be vended as a proxy, contacts the server, and thus gets to know the partner to which it will send messages from it’s TextField. Incoming messages are appended to the TextArea.

The server is more complicated. The main program creates and registers a ChatRoom, represented as a Vector of active Chatter partners:

Page 46: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

46

jdo/5/chat/ChatRoom.java

/** list of persons chatting. */public class ChatRoom extends Vector implements Room { /** starts a thread accepting calls and one thread per call. */ public static void main (String args []) { if (args != null && args.length > 0) try {

Tcp.register(args[0], new ChatRoom(), "chat.Room"); } catch (Exception e) { e.printStackTrace(); } } /** create a person to multiplex utterances. @return definitely a proxy. */ public synchronized Chat join (Chat withMe) { Person person = new Person(withMe); ((Proxy)withMe).proxy("chat.Chat", person); return person; }

If a new(?) Chatter arrives, a Person is created for him and returned by proxy.

The Person registers in the ChatRoom and starts a thread to pass messages from the ChatRoom to it’s Chatter.

A Person is another Vector, namely a queue of messages not yet output. If a Person receives a message from it’s Chatter it attaches it to all queues in the ChatRoom — including to itself.

Each thread reads it’s own queue and outputs the messages one after another. In the end it removes the Person from the ChatRoom.

The solution is very elegant if Person is a member class: A Person knows it’s ChatRoom as it’s creator and can therefore use ChatRoom.this to access the Vector in which each Person is registered.

Page 47: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

47

jdo/5/chat/ChatRoom.java

/** implements message queue to a single person in the room. */ public class Person extends Vector implements Chat, Runnable { private final Chat partner; /** add myself to room, start thread to dequeue messages to me. */ Person (Chat partner) { this.partner = partner; ChatRoom.this.addElement(this); new Thread(this).start(); } /** queue message with each person (including myself) in the room. */ public void tell (String message) { synchronized(ChatRoom.this) { for (int n = 0; n < ChatRoom.this.size(); ++ n) {

Person person = (Person)ChatRoom.this.elementAt(n); synchronized(person) { person.addElement(message); person.notify(); }}

} } /** dequeue messages to me. */ public void run () { for (;;) {

String message;synchronized(this) { while (size() == 0) try { wait(); } catch (InterruptedException e) { } message = (String)firstElement(); removeElementAt(0);}try { partner.tell(message);} catch (Throwable t) { break; }

} ChatRoom.this.removeElement(this); } }}

Page 48: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

48

Asynchronous AccessUnfortunately the solution is not working at all if it is operated with the Connection from section 1.4, page 9, and the TCP transport layer from section 1.6, page 40.

In the arithmetic service (Distributed Objects, page 31) a server calls it’s client only while the client is expecting an answer from the server, i.e., while the client is executing receive(). In the chat service, however, the client is waiting for input from it’s view and the server still wants to send it a message from another participant.

For the chat service to work, the implementation discussed in the table in Summary, page 34, has to be much more symmetrical:

Because the second sequence of Person can be triggered in parallel threads, the Connection has to virtually permit forward() and receive() in parallel threads on both sides simultaneously:

It seems that little enough is going to change. But because of independent threads now on each side Request and Reply or Throw objects can arrive in any order.

Fortunately, each Message has already been numbered (Messages, page 23) so that request and reply can be paired off: Message gets extended just like Id (Identification, page 15) so that it will be only be found in each Connection based on it’s sequence number.

Chatter actionPerformed { tell }

Person tell { other.addElement }

ChatProxy tell { forward }

ChatCaller forward { tell }

Client forward { wire.forward }

Server abstract forward

Chatter tell { ta.append }

Person tell { }

ChatCaller forward { tell }

ChatProxy tell { forward }

Server abstract forward

Client forward { wire.forward }

Connection forward { write } write { out.writeObject } receive { read; server.forward } read { in.readObject }

Connection receive { read; server.forward } read { in.readObject } forward { write } write { out.writeObject }

Request ReplyThrow

Out writeObject { replaceObject } replaceObject { id }

In readObject { resolveObject } resolveObject { id.object }

CallerPeer object { wire.locate }ClientPeer object { super.locate.object }

ServerPeer object { wire.locate }

Page 49: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

49

ImplementationAs before the Connection receives it’s streams through open(). Following that, a new method run() must be called to start operations:

jdo/7/jdo/Connection.java

/** creates and starts threads; must be called to start operation. @param block if true, does not return until closed. If this is synchronized, the system deadlocks. */ public void run (boolean block) { check(); sender = new Sender(); if (block) try { sender.join(); } catch (InterruptedException e) { } }

/** shuts down, tries to close streams and permit peer recycling. */ public synchronized void close () { if (clients != null) { clients = null; servers = null; serverByObject = null; classes = null; classLoader = null; sender.interrupt(); if (in != null) try { in.close(); } catch (Exception e) { } if (out != null) try { out.close(); } catch (Exception e) { } in = null; out = null; } }

run() creates a thread sender which has to worry about receiving and executing. It is possible to let run() block until sender ends. Alternatively one can call close() and thus terminate sender explicitly. Execution of a request may issue a callback and, therefore, executes in it’s own thread:

read() and write() now need to work independently of one another and must not use the attribute synchronized to block the entire Connection. Instead, read() is synchronized on in and write() on out.

any thread:

forward: write Request wait

run: forever read if Reply/Throw post, notify else

execute Request write Reply/Throw

out

in

out

Sender

Page 50: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

50

forward() passes a Request to the sender and then can assume that the corresponding answer comes back:

jdo/7/jdo/Connection.java

/** implements AnyProxy.method(). @param client provides proxy's peer, leads to receiver. @param method to be called for server's object. @param args array of arguments, will be wired. @return resulting object, null for void, can be a proxy. */ Object forward (Client client, int method, Object[] args) throws Throwable { Request request = new Request(new CallerPeer(client.id), method, args); Message message = sender.forward(request); if (message instanceof Reply) return ((Reply)message).result; throw ((Throw)message).throwable; }

The sender has to remember the Request and transfer it. It blocks the caller of forward(), until the appropriate answer comes back:

jdo/7/jdo/Connection.java

// ------------------------------------------------------------------ threading

private Sender sender; // writes requests, receives messages

private final class Sender extends Thread { { setName("sender"); setDaemon(true); start(); } private Hashtable requests = new Hashtable(); // awaiting replies /** writes request to network, waits for matching reply. */ Message forward (Request request) throws Throwable { Object[] reply = new Object[] { request, null }; synchronized(reply) { requests.put(request, reply); write(request);

try { do reply.wait(); while (reply[1] == null);} catch (InterruptedException e) { close(); throw e; }

} return (Message)reply[1]; }

Of course, sender and receiver are both Daemon threads.

Page 51: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

51

The sender thread continuously tries to read answers. If it gets a Request instead, it passes it to a new Receiver:

jdo/7/jdo/Connection.java

/** reads messages from network, executes requests as threads and matches replies with outstanding requests. */ public void run () { if (Boolean.getBoolean("trace")) System.err.println("sender started"); Throwable t = new Error("sender closing"); try {

for (;;) { Message message = read(); if (message == null) break; if (message instanceof Request) new Receiver((Request)message); else { Object[] reply = (Object[])requests.remove(message); if (reply == null) throw new Error(message+": unmatched"); synchronized(reply) { reply[1] = message; reply.notify(); } }}

} catch (IOException e) { t = e; } catch (ClassNotFoundException e) { t = e; } oops(t); if (Boolean.getBoolean("trace")) System.err.println("sender ends"); } private void oops (Throwable t) { synchronized(requests) {

for (Enumeration e = requests.elements(); e.hasMoreElements(); ) { Object[] reply = (Object[])e.nextElement(); synchronized(reply) { reply[1] = new Throw((Request)reply[0], t); reply.notify(); }}

} } }

If there really is an answer, i.e., a Reply or a Throw, it is matched to the corresponding Request and the waiting thread is notified, so that it terminates sender.forward() and thus forward() and thus delivers the result.

Before the thread is terminated it uses oops() to ensure that each open Request is answered with a Throw.

Page 52: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

52

The Receiver has to execute a Request locally. It records it during construction

jdo/7/jdo/Connection.java

/** local execution. */ private final class Receiver extends Thread { private final Request request; Receiver (Request request) { super("receiver"); this.request = request; setDaemon(true); start(); }

and executes it with a private thread:

jdo/7/jdo/Connection.java

/** executes request, writes reply to network. */ public void run () { try { Server server = (Server)request.receiver; Message reply; try { if (java.lang.Boolean.getBoolean("trace")) java.lang.System.err.println("invoking: "+request); reply = new Reply(request,

server.forward(request.method, request.args)); } catch (Throwable th) { reply = new Throw(request, th); } write(reply); } catch (IOException e) { close(); } if (Boolean.getBoolean("trace")) System.err.println("receiver ends"); } }

Unfortunately this requires one thread per Request because if the Request results in a callback the thread is blocked until the callback is answered — if a single thread handles all Request objects the system blocks on nested callbacks (thanks Elmar!).

Page 53: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

53

Transport Layer: TCP

Establishing a connection is simplified because receive() no longer has to be run with an explicit thread in the server:

jdo/7/jdo/Tcp.java

/** connects and builds thread for top-level server object, writes server as first object to reply line. This should not be called twice for the same name. @param name designates server's TCP port, can be "0". @param server the top-level server object. @param interfaceName the interface name supported by server;

for client-side proxy classes, <tt>Proxy</tt> is appended,for server-side proxy classes, <tt>Caller</tt> is appended.

*/ public static synchronized void register (String name, final Object server, final String interfaceName) throws IOException { final ServerSocket service = new ServerSocket(Integer.parseInt(name)); new Thread() { public void run () { for (;;)

try { Socket call = service.accept(); if (Boolean.getBoolean("trace")) System.err.println("called: "+call.getInetAddress().getHostName()

+":"+call.getPort()); final Connection wire = new Connection(); Connection.Out out = wire.new Out(call.getOutputStream()); Connection.In in = wire.new In(call.getInputStream()); wire.open(in, out);

Server s = wire.proxy(interfaceName, server); out.writeObject(s); // client receives server's proxy first wire.run(false); } catch (IOException e) { System.err.println(e+": while accepting"); }

} }.start(); if (Boolean.getBoolean("trace")) System.err.println("registered: "+service.getLocalPort()

+": "+interfaceName+" "+server); }

All that is required is a non-blocking call of run(). The server itself can then wait for another new connection.

Page 54: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

54

The client, too, makes a non-blocking call to run() — locate() is supposed to return a proxy and not block:

jdo/7/jdo/Tcp.java

/** connects to top-level server object, reads server as first object from reply line. @param name "[host:]portnumber". @return client-side proxy from a class interfaceName+Proxy. */ public static synchronized Client locate (String name) throws IOException, ClassNotFoundException { int n = name.indexOf(":"); Socket socket = n > 0 && n < name.length() ? new Socket(name.substring(0,n), Integer.parseInt(name.substring(n+1))) : new Socket("localhost", Integer.parseInt(name));

if (Boolean.getBoolean("trace")) System.err.println("called: "+socket.getInetAddress().getHostName() +":"+socket.getPort()); final Connection wire = new Connection(); Connection.In in = wire.new In(socket.getInputStream()); Connection.Out out = wire.new Out(socket.getOutputStream()); wire.open(in, out); Client client = (Client)in.readObject(); wire.run(false); if (Boolean.getBoolean("trace")) System.err.println("located: "+name+" "+client); return client; }}

Now all examples work unchanged — the synchronous Time and Cpu clients do not notice the Daemon threads at all.

In a production version of this system one should be able to choose between the implementation with and without threads because a solution without threads should be more efficient.

Page 55: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

55

2Distributed Objects

The techniques to distribute objects to different processes and hosts from the preceding chapter are offered in various programming languages and with different design goals, e.g.:

For a ‘‘pure’’ subset of RMI there is even interoperability with CORBA.

2.1 Remote Method InvocationRemote Method Invocation (RMI) permits calling methods for objects in other processes (JVMs) and on other hosts:

Certain components of such an application can be generated automatically:

PDO Portable Distributed Objectsvery transparent, for Objective C on Mach, Solaris, HPUX and Windows.

RMI Remote Method Invocationrelatively transparent, for Java on the JVM.

CORBA Common Object Request Broker Architecturenot transparent, between many languages and platforms.

rmic generates stub and skeleton classes for a server class. The stub represents the server at the client, the skeleton waits for calls at the server.

rmiregistry is a simple local name service delivering stubs, which answers local and remote queries.

Application

RMI-System

Client Server

Stub Skeleton

Remote Reference Layer

Transport Layer

Page 56: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

56

2.2 Time ServiceClient and server agree on an interface which must extend Remote. All methods must be declared to throw RemoteException:

rmi/time/Time.java

// RMI time service interface declarationpackage time;import java.rmi.Remote;import java.rmi.RemoteException;/** declares RMI time service. */public interface Time extends Remote { /** name under which the service is registered. */ final String id = "Timed"; /** @return current time. */ String getTime () throws RemoteException;}

The client uses a host name and a well-known string (which should probably contain the package name) to construct a URL-like string and contacts rmiregistry with lookup() of Naming to receive the stub:

rmi/time/Client.java

// RMI time service clientpackage time;import java.rmi.Naming;import java.rmi.RMISecurityManager;/** <tt>java time.Client [host]</tt><br> connects to time service. -Drmi.secure=true to load security manager. */public class Client {

public static void main (String args []) { String host = args != null && args.length > 0 ? args[0] : "localhost"; String id = "//"+ host +"/"+ Time.id; try { if (Boolean.getBoolean("rmi.secure")) System.setSecurityManager(new RMISecurityManager()); System.out.println(((Time)Naming.lookup(id)).getTime()); } catch (Exception e) { System.err.println(e); System.exit(1); } }}

The host name can be specified on the command line — rmiregistry must run on the same host as the server, but the client can be on a different host.

If classes are loaded from the server (see below), a SecurityManager is required — RMISecurityManager is the typical one to use. It should restrict the rights given to classes loaded through RMI.

Page 57: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

57

The service should extend UnicastRemoteObject:

rmi/time/TimeService.java

// RMI time service serverpackage time;import java.rmi.RemoteException;import java.rmi.server.UnicastRemoteObject;import java.util.Date;/** implements time service. -Drmi.log=true to trace. */public class TimeService extends UnicastRemoteObject implements Time { /** required because of the exception. */ public TimeService () throws RemoteException { if (Boolean.getBoolean("rmi.log")) setLog(System.err); }

public String getTime () throws RemoteException { return new Date().toString(); }}

All externally visible methods can cause a RemoteException; therefore, a constructor must be implemented.

As a subclass of RemoteServer the service can use setLog() to start a trace.

Page 58: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

58

2.3 ToolsA server may need to install a SecurityManager — if it receives callbacks — and it creates a service object and uses rebind() of Naming to inform the local rmiregistry. A program to start servers can use an interface name from the command line:

rmi/rmi/Server.java

// RMI service serverpackage rmi;import java.io.IOException;import java.net.InetAddress;import java.net.MalformedURLException;import java.net.UnknownHostException;import java.rmi.Naming;import java.rmi.NotBoundException;import java.rmi.Remote;import java.rmi.RemoteException;import java.rmi.RMISecurityManager;/** <tt>java rmi.Server interface [codebase]</tt><br> creates interfaceService(), binds it for interface.id; optionally runs a ClassServer. */public class Server { /** starts and registers a service; optionally starts a ClassServer. @param args [0] interface-name, must contain String id; [1] optional: path or archive.jar or archive.zip. */ public static void main (String args []) { if (args != null && args.length > 0) { if (Boolean.getBoolean("rmi.secure")) System.setSecurityManager(new RMISecurityManager()); try { if (args.length > 1) classServer(args[1], false); String id = (String)Class.forName(args[0]).getField("id").get(null);

Remote service = (Remote)Class.forName(args[0]+"Service").newInstance(); Naming.rebind(id, service); } catch (Exception e) { System.err.println(e); System.exit(1); } } else { System.err.println("usage: java rmi.Server interface [codebase]"); System.exit(1); } }

Page 59: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

59

If rmiregistry cannot find the stub classes on it’s own CLASSPATH, it will use the server’s property java.rmi.server.codebase (a list of URLs separated by spaces) to retrieve them. The codebase should use a protocol such as HTTP. Sun makes a simple web server for class files available. Modified versions of this server can optionally be instantiated with the server to supply class files from an archive or a subtree:

rmi/rmi/Server.java

/** starts a ClassServer, sets java.rmi.server.codebase system property. @param codebase path or archive.jar or archive.zip. @param daemon true to run as daemon threads. */ public static void classServer (String codebase, boolean daemon)

throws IOException, UnknownHostException { String host = InetAddress.getLocalHost().getHostAddress(); ClassServer classServer; if (codebase.endsWith(".zip") || codebase.endsWith(".jar")) classServer = new JarServer(0, codebase, daemon); else classServer = new ClassFileServer(0, codebase, daemon); codebase = "http://"+host+":"+classServer.getPort()+"/"; System.setProperty("java.rmi.server.codebase", codebase); System.err.println("codebase "+codebase); }

The abstract base class opens a ServerSocket on a given port and starts a listening thread:

rmi/rmi/ClassServer.java

// ClassServerpackage rmi;import java.io.BufferedReader;import java.io.DataOutputStream;import java.io.InputStreamReader;import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;/** provide .class file bytecodes over HTTP, patterned after Sun's implementation. */public abstract class ClassServer implements Runnable { protected ServerSocket call; // incoming connections protected boolean daemon; // run threads as daemons /** @param port to listen on. @param daemon true to run as daemon threads. */ protected ClassServer (int port, boolean daemon) throws IOException { call = new ServerSocket(port); this.daemon = daemon; newListener(); } /** provide bytecodes for class name. */ public abstract byte[] getBytes (String className)

throws IOException, ClassNotFoundException; /** @return port to call. */ public int getPort () { return call.getLocalPort(); }

Subclasses must provide bytes through getBytes().

Page 60: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

60

The listener thread may optionally be a daemon. It accepts a call, starts another thread for the next call and tries to answer a request:

rmi/rmi/ClassServer.java

/** make listener. */ protected void newListener () { Thread t = new Thread(this); if (daemon) t.setDaemon(true); t.start(); } /** listen for and answer one request, spawn next listener. */ public void run () { Socket data; try { // get call data = call.accept(); } catch (IOException e) { System.err.println("ClassServer: "+e); return; } if (Boolean.getBoolean("rmi.trace")) System.err.println("accept: "+data); newListener(); // set up for next connection

try { DataOutputStream out = new DataOutputStream(data.getOutputStream()); try { BufferedReader in =

new BufferedReader(new InputStreamReader(data.getInputStream()));String path = getPath(in);

byte[] bytecodes = getBytes(path); try { // assume HTTP >= 1.0 out.writeBytes("HTTP/1.0 200 OK\r\n"); out.writeBytes("Content-Length: "+bytecodes.length+"\r\n"); out.writeBytes("Content-Type: application/java\r\n\r\n"); out.write(bytecodes); out.flush(); if (Boolean.getBoolean("rmi.trace")) System.err.println("sent: "+bytecodes.length); } catch (IOException ie) { // didn't finish sending if (Boolean.getBoolean("rmi.trace")) System.err.println("sent: "+ie);

return; } } catch (Exception e) { // can't get the file out.writeBytes("HTTP/1.0 400 "+e.getMessage()+"\r\n"); out.writeBytes("Content-Type: text/html\r\n\r\n"); out.flush(); if (Boolean.getBoolean("rmi.trace")) System.err.println("sent: "+e); } } catch (IOException ioe) { // i/o trouble System.err.println("ClassServer: "+ioe); } finally { try { data.close(); } catch (IOException e) { } } }

Page 61: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

61

A separate method getPath() extracts the file name from the HTTP request and formats it as a class name:

rmi/rmi/ClassServer.java

/** @return full class name. */ protected static String getPath (BufferedReader in) throws IOException { String line = in.readLine(); if (Boolean.getBoolean("rmi.trace")) System.err.println(line); String path = ""; if (line.startsWith("GET /")) {// GET /x/y.class HTTP/1.0 line = line.substring(5, line.length()).trim(); int index = line.indexOf(".class"); if (index != -1) path = line.substring(0, index).replace('/', '.'); } do // rest of header line = in.readLine(); while (line.length() != 0); if (path.length() > 0) return path; throw new IOException("Malformed request"); }}

Subclasses receive the class name in getBytes() and must respond with the bytecodes of the class. This design circumvents complications with file name separators, etc.

Page 62: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

62

ClassFileServer supplies bytes from a file in a tree below a directory:

rmi/rmi/ClassFileServer.java

// ClassFileServerpackage rmi;import java.io.DataInputStream;import java.io.File;import java.io.FileInputStream;import java.io.IOException;/** provide .class file bytecodes from file, patterned after Sun's implementation. -Drmi.trace=true to see served files. */public class ClassFileServer extends ClassServer { protected String folder; /** @param port to listen on. @param folder relative to which full class name must be found. @param daemon true to run as daemon threads. */ public ClassFileServer (int port, String folder, boolean daemon)

throws IOException { super(port, daemon); this.folder = folder; } /** provide bytecodes for class name from file. */ public byte[] getBytes (String className)

throws IOException, ClassNotFoundException { if (Boolean.getBoolean("rmi.trace")) System.err.println("reading: "+className); File file = new File(folder + File.separator +

className.replace('.', File.separatorChar)+".class"); int length = (int)file.length(); if (length == 0) throw new IOException(className+": empty"); else { DataInputStream in = new DataInputStream(new FileInputStream(file)); byte[] result = new byte[length]; in.readFully(result); return result; } } /** stand-alone server. */ public static void main (String args []) { if (args != null && args.length >= 2) { try { new ClassFileServer(Integer.parseInt(args[0]), args[1], false); } catch (IOException e) { System.err.println("ClassFileServer: "+e); } } else System.err.println("usage: java classd.ClassFileServer port folder"); System.exit(1); }}

Page 63: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

63

JARServer supplies bytes from a .jar or .zip file using java.util.zip.ZipFile:

rmi/rmi/JARServer.java

// JarServerpackage rmi;import java.io.DataInputStream;import java.io.File;import java.io.IOException;import java.util.zip.ZipEntry;import java.util.zip.ZipFile;/** provide .class file bytecodes from jar/zip archive, patterned after Sun's implementation. -Drmi.trace=true to see served files. */public class JarServer extends ClassServer { protected ZipFile archive; /** @param port to listen on. @param archive with jar/zip files. @param daemon true to run as daemon threads. */ public JarServer (int port, String archive, boolean daemon)

throws IOException { super(port, daemon); this.archive = new ZipFile(archive); } /** provide bytecodes for class name from file. */ public byte[] getBytes (String className)

throws IOException, ClassNotFoundException { if (Boolean.getBoolean("rmi.trace")) System.err.println("reading: "+className); ZipEntry file = archive.getEntry(

className.replace('.', '/')+".class"); int length = (int)file.getSize(); if (length == 0) throw new IOException(className+": empty"); else { DataInputStream in = new DataInputStream(archive.getInputStream(file)); byte[] result = new byte[length]; in.readFully(result); return result; } } /** stand-alone server. */ public static void main (String args []) { if (args != null && args.length >= 2) { try { new JarServer(Integer.parseInt(args[0]), args[1], false); } catch (IOException e) { System.err.println("JarServer: "+e); } } else System.err.println("usage: java classd.JarServer port archive"); System.exit(1); }}

Page 64: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

64

CompilationFirst, client and service are compiled using javac. Then rmic is used to create from each service class (not interface) the necessary _Stub and _Skel classes:

$ CLASSPATH=.. javac Client.java$ CLASSPATH=.. javac TimeService.java$ CLASSPATH=.. rmic -keepgenerated -d .. time.TimeService

Depending on the version, rmic places the class files in a folder based on the package which can be relative to a folder specified using -d. The generated sources are erased by default.

ExecutionFor execution one starts rmiregistry and then server and client:

$ CLASSPATH=.. rmiregistry &$ CLASSPATH=.. java rmi.Server time.Time &$ CLASSPATH=.. java time.ClientTue Oct 31 16:24:08 EST 2000$ kill 2503$ kill 2495

The trace is not terribly useful. rmiregistry and the server have to be terminated explicitly.

DeploymentArchives can be used to precisely show which classes are really required by client, rmiregistry, and server:

$ cd .. && \> jar cf time/client.jar time/Client.class time/Time.class$ cd .. && \> jar cf time/reg.jar time/TimeService_Stub.class time/Time.class$ cd .. && \> jar cf time/server.jar time/TimeService.class time/TimeService_Skel.class \

time/TimeService_Stub.class time/Time.class rmi/*.class

The class file server and the class rmi.Server to start and register the service are in the package rmi. They are required in server.jar to start the service.

rmi.Server sets java.rmi.server.codebase to tell rmiregistry where the _Stub classes should be loaded from:

$ CLASSPATH=/ rmiregistry &

$ CLASSPATH=server.jar java rmi.Server time.Time reg.jar &codebase http://129.21.38.149:1074/

$ CLASSPATH=client.jar java -Drmi.secure=true time.ClientTue Oct 31 16:32:02 EST 2000

$ kill 5939$ kill 5931

Setting -Drmi.trace=true traces the class file server and shows that it immediately looks for the _Stub class: During bind() the server sends to rmiregistry a URL list as a property java.rmi.server.codebase (archives, or folders terminated with /, separated by blanks) followed by the serialized stub which rmiregistry later sends to the client. If rmiregistry cannot find the _Stub class on it’s CLASSPATH it tries the URL list, i.e., it will ask the class file server.

Page 65: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

65

The client receives the stub from rmiregistry. Only if rmiregistry had to consult the URL list, will it send it on to the client — otherwise the client is assumed to have the stub class files on it’s CLASSPATH. The URL list should be absolute and use HTTP or FTP. If something is changed, rmiregistry may have to be restarted to propagate the change.

Page 66: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

66

PolicyIf any SecurityManager is used, for Java 2 a policy file must be written. It takes some tuning to produce a reasonably restrictive one:

$ cat $HOME/.java.policygrant codeBase "file:/Local/Users/axel/-" { permission java.net.SocketPermission "*:1099", "connect"; permission java.net.SocketPermission "*:1024-", "accept,connect"; permission java.io.FilePermission "/Local/Users/axel/-", "read"; permission java.util.PropertyPermission "*", "read,write"; permission java.lang.RuntimePermission "modifyThreadGroup"; permission java.lang.RuntimePermission "loadLibrary.*"; permission java.lang.RuntimePermission "modifyThread";};

This entry permits local programs to connect to rmiregistry on any host on port 1099, and to act as servers and clients on any port to any host. Local files my be read, properties read and changed at will, thread manipulations are granted, and libraries may be loaded into the JVM.

The Time service only requires the first two permissions; the remaining permissions were gathered while the remaining examples were developed.

It does not seem possible to install an interactive SecurityManager, delegate decisions to an RMISecurityManager and only query the user for the permissions that need to be granted explicitly — if an RMISecurityManager object is not installed, it seems to have no permissions built-in whatsoever.

Page 67: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

67

2.4 Arithmetic ServiceThe problem was introduced in Distributed Objects, page 31: A Cpu service evaluates arithmetic expressions which it gets from a Terms service:

rmi/cpu/Cpu.java

// RMI cpu service interface declarationpackage cpu;import java.rmi.Remote;import java.rmi.RemoteException;/** declares RMI cpu service. */public interface Cpu extends Remote { /** name under which the service is registered. */ final String id = "Cpud"; /** retrieves int array from 'terms' and computes and stores a[0] a[1] a[2] a[3] a[4] ... where the a[odd] must be + - * % /. Various exceptions can happen... */ Result cpu (Terms terms) throws RemoteException, Exception;}

rmi/cpu/Terms.java

// RMI cpu service's argument interface definitionpackage cpu;import java.rmi.Remote;import java.rmi.RemoteException;/** declares RMI cpu service's argument service. */public interface Terms extends Remote { /** provided by the Cpu client, see {@link Cpu#cpu(cpu.Terms) cpu()}. */ int[] terms () throws RemoteException;}

rmi/cpu/Result.java

// RMI cpu service's result interface declarationpackage cpu;import java.rmi.Remote;import java.rmi.RemoteException;/** declares RMI cpu service's result. */public interface Result extends Remote { /** lets Cpu deliver result of previous computation. */ int result (Result cpu) throws RemoteException;}

A Cpu returns itself so that there might be another callback for the true result.

The example demonstrates callbacks with proxies on both sides. Additionally, a proxy is sent back to it’s original.

Page 68: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

68

Service

rmi/cpu/CpuService.java

// RMI cpu service serverpackage cpu;import java.rmi.RemoteException;import java.rmi.server.UnicastRemoteObject;/** implements cpu service. -Drmi.log=true to trace. */public class CpuService extends UnicastRemoteObject implements Cpu, Result { /** required because of the exception. */ public CpuService () throws RemoteException { if (Boolean.getBoolean("rmi.log")) setLog(System.err); }

private int result; public Result cpu (Terms terms) throws Exception { int[] t = terms.terms(); result = t[0]; for (int n = 1; n < t.length; n += 2) switch (t[n]) { case '+': result += t[n+1]; continue; case '-': result -= t[n+1]; continue; case '*': result *= t[n+1]; continue; case '/': result /= t[n+1]; continue; case '%': result %= t[n+1]; continue; default: throw new Exception(t[n]+": unexpected"); } return this; } /** BUG: a funny thing happened on the way to the client... */ public int result (Result cpu) { if (cpu != this) System.err.println("service: "+this+" != "+cpu); return result; }}

The service implements Cpu and Result. It must be started with a SecurityManager if it obtains the stubs from the client.

result() contains a test that should be trivially true...

Page 69: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

69

Client

rmi/cpu/Client.java

// RMI cpu service clientpackage cpu;import rmi.Server;import java.rmi.Remote;import java.rmi.RemoteException;import java.rmi.server.UnicastRemoteObject;/** <tt>java cpu.Client [[host] codebase]</tt><br> connects to cpu service, provides terms. -Drmi.log=true to trace. -Drmi.secure=true to load security manager. */public class Client extends UnicastRemoteObject implements Terms { /** required because of the exception. */ public Client () throws RemoteException { if (Boolean.getBoolean("rmi.log")) setLog(System.err); }

public int[] terms () throws RemoteException { return jobs[job]; }

private static int job; private static int[] jobs[] = { { 2, '+', 3 }, // 0: ok { 2, '/', 0 }, // 1: zero div { 2, '+' }, // 2: array bounds { 2, '?' } // 3: unexpected }; /** runs each row of global array <tt>Client.jobs</tt>. */ protected void run (Cpu cpu) { for (job = 0; job < jobs.length; ++ job) try {

Result server = cpu.cpu(this);System.out.println(server.result(server));

} catch (Exception e) { System.err.println("client: "+e); } }

public static void main (String args []) { try { Cpu cpu = (Cpu)Server.lookup(Cpu.id, args); Client client = new Client(); client.run(cpu); UnicastRemoteObject.unexportObject(client, true); } catch (Exception e) { System.out.println(e); System.exit(1); } Server.dumpThreads(true); // RMI Reaper stays behind... System.exit(0); }}

unexportObject() in UnicastRemoteObject should terminate the server thread that exports the Terms — unfortunately one of the RMI threads is not a daemon and survives.

Page 70: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

70

ToolsIf the client exports objects it, too, should provide a codebase. Either a central, stand-alone class file server (or Web server) provides all stubs, or the client can be set up to run yet another class file server thread. Code in rmi.Server can be shared:

rmi/rmi/Server.java

/** locates a service; optionally starts a ClassServer. @param id service's registry name. @param args optional, [0] optional: host name; [1] path or archive.jar or archive.zip. */ public static Remote lookup (String id, String args []) throws UnknownHostException, IOException, NotBoundException, RemoteException, MalformedURLException { if (Boolean.getBoolean("rmi.secure")) System.setSecurityManager(new RMISecurityManager()); String host = "localhost"; if (args != null) switch (args.length) { case 0: break; case 1: classServer(args[0], true); break; default: host = args[0]; classServer(args[1], true); break; } return Naming.lookup("//"+ host +"/"+ id); }

Being able to dump all threads might be useful for debugging:

rmi/rmi/Server.java

/** dump active threads. @param user true to see only user threads. */ public static void dumpThreads (boolean user) { ThreadGroup tg = Thread.currentThread().getThreadGroup(); for (ThreadGroup p = tg.getParent(); p != null; p = tg.getParent()) tg = p; Thread thread [] = new Thread[tg.activeCount()]; int nThreads = tg.enumerate(thread); for (int n = 0; n < nThreads; ++ n) if (!user || !thread[n].isDaemon()) System.err.println(thread[n]+" daemon: "+thread[n].isDaemon()); }}

Page 71: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

71

DeploymentBoth, server and client, are now exporting stubs — this can be set up with two class file servers and four archives:

Client and server need their own stubs for checking and their own skeletons for execution. For startup they also need the rmi tools package. The other archives are used to export the stubs to the peer.

If make is used, there is a fairly systematic way to write the rules for assembling the archives:

J = .java# extensionsC = .classZ = .jar

p = cpu# package namei = Cpu$J Result$J Terms$J# interfaces, don't cleanc = Client_Skel$J# client-sidecs = Client_Stub$J# client-side for server-siders = CpuService_Stub$J# rmiregistry and server-sides = CpuService_Skel$J# server-side

# combine (outer) class lists for archives

client= Client$C $(c:$J=$C) $(cs:$J=$C) $(i:$J=$C)cstubs = $(cs:$J=$C) $(i:$J=$C)reg = $(rs:$J=$C) $(i:$J=$C)server = CpuService$C $(s:$J=$C) $(rs:$J=$C) $(i:$J=$C)

# make archives

client$Z: $(client) ; cd .. && \$(JAR) cf $p/$@ `for i in $(client); do echo $p/$$i; done` rmi/*$C

cstubs$Z: $(cstubs) ; cd .. && \$(JAR) cf $p/$@ `for i in $(cstubs); do echo $p/$$i; done`

reg$Z: $(reg) ; cd .. && \$(JAR) cf $p/$@ `for i in $(reg); do echo $p/$$i; done`

server$Z: $(server) ; cd .. && \$(JAR) cf $p/$@ `for i in $(server); do echo $p/$$i; done` rmi/*$C

client.jar: cpu/Client.class cpu/Client_Skel.class cpu/Client_Stub.class cpu/Cpu.class cpu/Result.class cpu/Terms.class rmi/*.class

server.jar: cpu/CpuService.class cpu/CpuService_Skel.class cpu/CpuService_Stub.class cpu/Cpu.class cpu/Result.class cpu/Terms.class rmi/*.class

cstubs.jar: cpu/Client_Stub.class cpu/Cpu.class cpu/Result.class cpu/Terms.class

reg.jar: cpu/CpuService_Stub.class cpu/Cpu.class cpu/Result.class cpu/Terms.class

Page 72: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

72

Execution can look as follows:

$ CLASSPATH=/ rmiregistry &

$ CLASSPATH=server.jar java -Drmi.secure=true rmi.Server cpu.Cpu reg.jar &codebase http://129.21.38.149:1090/

$ CLASSPATH=client.jar java -Drmi.secure=true cpu.Client cstubs.jarcodebase http://129.21.38.149:1100/service: cpu.CpuService[RemoteStub [ref: [endpoint:[129.21.38.149:1091] (local),objID:[0]]]] != cpu.CpuService_Stub[RemoteStub [ref: [endpoint: [129.21.38.149:1091](remote),objID:[0]]]]5client: java.lang.ArithmeticException: / by zeroclient: java.lang.ArrayIndexOutOfBoundsExceptionclient: java.lang.Exception: 63: unexpectedThread[RMI Reaper,5,system] daemon: falseThread[main,5,main] daemon: false

$ kill 9567$ kill 9559

The server seems to receive (mistakenly?) a Server_Stub and not itself as an argument from the client in result().

If the class file server is traced, one sees that the client is serving it’s stub to the server.

Page 73: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

73

2.5 Chat ServiceThe chat service from section 1.8, page 44, works pretty unchanged with RMI; asynchronous calls are possible on both sides.

rmic seems to have trouble with inner classes; therefore Person is specified as a top-level class with package access.

2.6 ActivationA server can be registered with rmid to be started on demand.

2.7 RMI and CORBAto be done

Page 74: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

74

Page 75: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

75

3CORBA and IDL

CORBA is the Common Object Request Broker Architecture of the Object Management Group (OMG). Basically this is a specification for distributed objects which is independent of manufacturers, platforms, and even programming languages. Simple values are transferred and objects are always represented by proxies. CORBA aims at the building of heterogeneous, distributed systems; language transparency of the distribution is not a goal.

IDL is CORBA’s specification language. Classes (interface), instance variables (attribute), methods and errors (exception) are declared using IDL. A compiler maps the specification to a programming language and generates code for the communication infrastructure. The OMG has specified mappings from IDL to Ada, C, C++, COBOL, Java, and SmallTalk, and there is even a mapping from Java/RMI back to IDL.

To support C++, IDL permits multiple inheritance, which is simulated in Java using aggregates (tie, delegate pattern). C++ provides only rudimentary runtime type information; therefore, an IDL compiler implements an extensive type code system.

CORBA’s central feature is to always be able to map between strings and objects (object_to_string, string_to_object). This is supported by an Object Request Broker (ORB) in each application which communicates with the other applications. Object identities are supposed to be persistent, i.e., the strings can be stored in files to be used later.

Server objects are registered with the ORB (connect). Depending on the CORBA version, there can be a Basic Object Adapter (BOA) or a Portable Object Adapter (POA), which deliver requests and thus implement a thread model, on which it depends in turn, if callbacks or asynchronous dialogs are possible.

CORBA has some predefined services. In particular there is a NameService, which implements a hierarchical name space which maps paths to NamingContext and other objects (as strings). Once the first NamingContext object is available as the root of a NameService, one can access the objects registered there and thus gain access to other services.

Objects are always passed across the net and stored in files as strings, either in the hexadecimal IOR notation (which can be decoded using iordump) or as a URL for the Internet Inter ORB Protocol (IIOP), for a NameService for example

iioploc://host:port/NameServicecorbaloc::host:port/NameService

Page 76: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

76

3.1 Development Cycle: Time ServiceAs an example for CORBA development the time service of section 1.1, page 3, is implemented in C++ and Java using JDK 1.3 and ORBacus 3.3.2; the latter can be used with JDK 1.2 as well.

Interface DefinitionFirst the interface for the server objects is declared:

corba/time/time.idl

// time service interface definitioninterface Time { string getTime ();};

The syntax of this IDL file is similar to C++ and Java and the same commenting conventions apply. Each declaration must be terminated with a semicolon.

interface describes the class of a server; superclasses are specified with : just as in C++. string is a data type which is mapped in C++ to a (dynamic) char* and in Java to String.

To protect a namespace, several interface declarations can be collected in a module. In C++ the module name is used as a prefix for class names, in Java it is mapped to a package. Depending on the IDL compiler, however, this can be set during compilation as well:

ORBacus 3.3.2 C++ $ idl --no-type-codes Time.idlORBacus 3.3.2 Java $ jidl --output-dir .. --tie --package time time.idlJDK 1.3.0 $ idlj -td .. -fallTIE -pkgPrefix Time time time.idl

Depending on the language and the IDL compiler, many files are generated:

In particular in Java, unfortunately, the file and class names are different, depending on the manufacturer.

Time.h, Time.cpp base class with getTime()Time_skel.h, Time_skel.cpp abstract base class for Time server

Time.java proxy interface with getTime()TimeHelper.java class with conversion methodsTimeHolder.java class with Time value for inout and out parameters

_TimeImplBase.java abstract base class for Time server_TimeImplBase_tie.java base class for server, multiple inheritanceTime_Tie.java ... using JDK 1.3.0TimeOperations.java interface with getTime()

StubForTime.java proxy of a Time server

Page 77: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

77

C++ ServantTo implement the server in C++, one first implements a servant class which performs the actual service:

corba/time++/Time_impl.h

// patterned after Hello_impl.h#ifndef TIME_IMPL_H#define TIME_IMPL_H

#include <Time_skel.h>

class Time_impl : public Time_skel {public: virtual char* getTime();};

#endif

corba/time++/Time_impl.cpp

// patterned after Hello_impl.cpp#include <OB/CORBA.h>#include <Time_impl.h>#include <string.h>#include <time.h>

char* Time_impl::getTime () { time_t now = time(NULL); char* result = CORBA_string_dup(ctime(&now)); result[strlen(result)-1] = '\0'; return result;}

The base class Time_skel is created by the IDL compiler and it declares getTime() as abstract. C++ manages memory explicitly; therefore, the result string has to be stored in dynamic memory. Except for that, the distribution through CORBA is not really visible in the servant.

Page 78: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

78

C++ ServerThe server arranges for the environment and usually registers the servant with the NameService. It is more complicated:

corba/time++/Server.cpp

// CORBA time service server, patterned after ORBacus' Hello World#include <OB/CORBA.h>#include <OB/Util.h>#include <OB/CosNaming.h>#include <Time_impl.h>#include <stdio.h>

int main (int argc, char* argv[], char*[]) { try { CORBA_ORB_var orb = CORBA_ORB_init(argc, argv); // create ORB CORBA_BOA_var boa = orb->BOA_init(argc, argv); // create BOA

Time_var timeServer = new Time_impl; // create servant

CORBA_Object_var obj; // get root naming context try { obj = orb->resolve_initial_references("NameService"); } catch (const CORBA_ORB::InvalidName&) { fprintf(stderr, "%s: cannot resolve NameService\n", argv[0]); return 1; } if (CORBA_is_nil(obj)) { fprintf(stderr, "%s: NameService is nil\n", argv[0]); return 1; }

CosNaming_NamingContext_var nc = CosNaming_NamingContext::_narrow(obj); if (CORBA_is_nil(nc)) { fprintf(stderr, "%s: NameService is no NamingContext\n", argv[0]); return 1; }

CosNaming_Name aName; // bind the server aName.length(1); aName[0].id = CORBA_string_dup("Timed"); aName[0].kind = CORBA_string_dup(""); nc->bind(aName, timeServer);

// run the server boa->impl_is_ready(CORBA_ImplementationDef::_nil()); } catch (CORBA_SystemException& ex) { OBPrintException(ex); return 1; } return 0;}

First an ORB, a BOA, and a servant are created, and the latter is registered with the NameService. If this all works, the BOA takes over the main loop.

Page 79: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

79

Java ServantIn Java the servant can be created as an inner class of the server:

corba/time/Server.java

// CORBA time service serverpackage time;

import java.util.Date;import java.util.Properties;import org.omg.CORBA.ORB;import org.omg.CosNaming.NameComponent;import org.omg.CosNaming.NamingContext;import org.omg.CosNaming.NamingContextHelper;/** <tt>java time.Server [-tie]</tt><br> implements server's request loop, optionally using TIE paradigm. */public class Server { /** actual time server, must be derived from idlj-generated class. */ public static class Servant extends _TimeImplBase { public String getTime () { return new Date().toString(); } }

For simple inheritance one directly extends the base class _TimeImplBase which is created by the IDL compiler.

With the delegate pattern, inheritance can be chosen at will, because the servant is later wrapped into a tie class which redirects all calls:

corba/time/Server.java

/** actual time server, derived from arbitrary class. */ public static class Servant_Tie implements TimeOperations { public String getTime () { return "tie: "+new Date().toString(); } }

TimeOperations is the interface containing getTime(), which the IDL compiler creates and which is already implemented with abstract methods in _TimeImplBase.

Page 80: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

80

Java ServerThe main program creates an ORB and a servant, wraps the delegate pattern servant (if any) with a tie object, and registers the result with the NameService:

corba/time/Server.java

/** creates and registers servant, waits forever for requests. */ public static void main (String args []) { try { ORB orb = ORB.init(args, null); // create and initialize application ORB Object boa = null; // basic object adapter, if supported try { boa = orb.getClass().getMethod("BOA_init",

new Class[] { args.getClass(), Properties.class }) .invoke(orb, new Object[] { args, null });

} catch (Exception e) { System.err.println(e+": ignored"); }

Time timeServer; if (args != null && args.length > 0 && args[0].startsWith("-t"))

timeServer = new Time_Tie(new Servant_Tie()); else timeServer = new Servant(); orb.connect(timeServer); // create server and register with ORB org.omg.CORBA.Object objRef = // get the root naming context

orb.resolve_initial_references("NameService"); NamingContext ncRef = NamingContextHelper.narrow(objRef);

// bind the server NameComponent path[] = { new NameComponent("Timed", "") }; ncRef.rebind(path, timeServer);

if (boa != null) // boa.impl_is_ready(null) try {

boa.getClass().getMethod("impl_is_ready", new Class[] { Class.forName("org.omg.CORBA.ImplementationDef") }) .invoke(boa, new Object[] { null });} catch (Exception e) { System.err.println(e); }

elsesynchronized (Server.class) { // sync on something to... Server.class.wait(); // ...wait for invocations}

} catch (Exception e) { e.printStackTrace(); } }}

Unfortunately, this code depends on the manufacturer of the ORB classes. Unlike ORBacus 3.3.2, the JDK 1.3.0 does not use an explicit BOA.

Java has no preprocessor; the boldfaced portions of the code demonstrate that a lot of trickery is required to make the same source compile with both suppliers.

Page 81: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

81

C++ ClientThe client is similar to the server. Again, an ORB is created and the NameService is contacted:

corba/time++/Client.cpp

// CORBA time client, patterned after ORBacus' Hello World#include <OB/CORBA.h>#include <OB/Util.h>#include <OB/CosNaming.h>#include <Time.h>#include <stdio.h>

int main (int argc, char* argv[], char*[]) { try { CORBA_ORB_var orb = CORBA_ORB_init(argc, argv); // create ORB CORBA_Object_var obj; // get root naming context try { obj = orb->resolve_initial_references("NameService"); } catch (const CORBA_ORB::InvalidName&) { fprintf(stderr, "%s: cannot resolve NameService\n", argv[0]); return 1; } if (CORBA_is_nil(obj)) { fprintf(stderr, "%s: NameService is nil\n", argv[0]); return 1; }

CosNaming_NamingContext_var nc = CosNaming_NamingContext::_narrow(obj); if (CORBA_is_nil(nc)) { fprintf(stderr, "%s: NameService is no NamingContext\n", argv[0]); return 1; } CosNaming_Name aName; // locate the server aName.length(1); aName[0].id = CORBA_string_dup("Timed"); aName[0].kind = CORBA_string_dup(""); Time_var timeServer = Time::_narrow(nc->resolve(aName)); if (CORBA_is_nil(timeServer)) { fprintf(stderr, "%s: no time server\n", argv[0]); return 1; }

printf("%s\n", timeServer->getTime()); // use the server } catch (CORBA_SystemException& ex) { OBPrintException(ex); return 1; } return 0;}

This time, resolve() is used to find a Time service. To account for multiple inheritance, the result has to be casted using _narrow().

Page 82: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

82

Java ClientIn Java the same steps are a bit simpler:

corba/java/Client.java

// CORBA time service clientpackage time;

import org.omg.CORBA.ORB;import org.omg.CosNaming.NameComponent;import org.omg.CosNaming.NamingContext;import org.omg.CosNaming.NamingContextHelper;

/** <tt>java time.Client [host]</tt> */public class Client { public static void main (String args []) { try { ORB orb = ORB.init(args, null); // create and initialize the ORB org.omg.CORBA.Object objRef = // and get the root naming context

orb.resolve_initial_references("NameService"); NamingContext ncRef = NamingContextHelper.narrow(objRef);

// now look for the time server NameComponent path[] = { new NameComponent("Timed", "") }; Time timeServer = TimeHelper.narrow(ncRef.resolve(path));

System.out.println(timeServer.getTime()); } catch (Exception e) { e.printStackTrace(); } }}

A client only needs a BOA for callbacks or incoming requests.

Page 83: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

83

ExecutionA NameService is required for execution. JDK 1.3 uses options to configure and locate it:

JDK 1.3 $ tnameserv -ORBInitialPort 1111 &Initial Naming Context:IOR:000000000000002849444c3a6f6d672e6f72672f436f734e616d696e672f4e616d696e67436f6e746578743a312e3000000000010000000000000058000101000000000f3133312e3137332e31332e3231300000043d000000000018afabcafe00000002ee17a9a40000000800000000000000000000000100000001000000140000000000010020000000000001010000000000TransientNameServer: setting port for initial object references to: 1111Ready.JDK 1.3 $ java time.Server -ORBInitialHost localhost -ORBInitialPort 1111 &java.lang.NoSuchMethodException: ignoredJDK 1.3 $ java time.Client -ORBInitialHost localhost -ORBInitialPort 1111Sat Nov 06 12:40:18 GMT+01:00 1999

tnameserv outputs the object describing the name service. The output could be stored in a file and input to a client ORB directly. The hexadecimal description contains the IP address of the host, in this case 131.173.13.210.

ORBacus uses a file, command line options, or Java properties to configure:

ORBacus C++ $ cfg=’-ORBDefaultInitRef corbaloc::localhost:1111 -ORBtrace_level 2 -OAnumeric’ORBacus C++ $ nameserv $cfg -OAport 1111 &[Accepting connections on port 1111]Running in non-persistent mode.ORBacus C++ $ server $cfg &[Accepting connections on port 1271][New connection to 127.0.0.1:1111][New connection from 127.0.0.1:1272]ORBacus C++ $ client $cfg[New connection to 127.0.0.1:1111][New connection from 127.0.0.1:1273][New connection to 131.173.250.22:1271][New connection from 131.173.250.22:1274]Sat Nov 6 12:52:38 1999[Closing connection to 127.0.0.1:1111][Closing connection from 127.0.0.1:1273][Closing connection to 131.173.250.22:1271]

Within ORBacus, arbitrary combinations of C++ and Java are possible for NameService and Time; the NameService can be started from Java, too:

ORBacus Java $ CLASSPATH=OBNaming.jar:OB.jar \> java com.ooc.CosNaming.Server -OAport 1111[Accepting connections on port 1111]Running in non-persistent mode.

Page 84: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

84

Interoperability [to be done]Auch JDK 1.3beta und ORBacus sind bis zu einem gewissen Grad interoperabel:

JDK 1.3beta $ tnameserv -ORBInitialPort 1111 &JDK 1.3beta $ java time.Server -ORBInitialPort 1111 &JDK 1.3beta $ java ns -ORBInitialPort 1111 -r : > venus

ns ist ein Hilfsprogramm zur Kommunikation mit einem NameService. Hier wird der erste NamingContext des JDK-Name-Servers tnameserv auf dem Rechner venus in eine Datei geschrieben.

ORBacus Java $ java ns -ORBconfig config -b venus < venusORBacus Java $ java ns -ORBconfig config -l : -l venuso venus IOR:0000000000000028...venus o Timed IOR:000000000000000d49...

Auf dem Rechner penny wird dieses Objekt dann in den ORBacus-Name-Server als venus eingetragen; anschließend wird der Eintrag auf penny und dann der Eintrag für Timed auf venus ausgegeben. Sorgt man dafür, daß der Client einen NamingContext verfolgt, indem man folgenden Code einfügt

corba/time/Client.java

// assume name services are bound... if (args != null && args.length > 0 && !args[0].startsWith("-")) {

NameComponent path[] = { new NameComponent(args[0], "") }; ncRef = NamingContextHelper.narrow(ncRef.resolve(path)); }

dann kann man von ORBacus auch einen JDK-Server aufrufen:

ORBacus Java $ java -Dorg.omg.CORBA.ORBClass=com.ooc.CORBA.ORB \> -Dorg.omg.CORBA.ORBSingletonClass=com.ooc.CORBA.ORBSingleton \> time.Client venus -ORBconfig config[New connection to 127.0.0.1:1111][New connection from 127.0.0.1:1325][New connection to 131.173.13.210:1136][New connection to 131.173.13.210:1166]Sat Nov 06 15:44:13 GMT+01:00 1999[Closing connection from 127.0.0.1:1325]

Page 85: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

85

3.2 Callbacks: Arithmetic ServiceThe arithmetic service from Distributed Objects, page 31, is an example for callbacks and a more elaborate interface:

corba/cpu/cpu.idl

// cpu service interface definitionsmodule cpu { interface Terms; // forward declaration typedef long term; // common data element typedef sequence<term> termseq; // unbounded array /** a remote super computer... */ interface Cpu { /** current result. */ readonly attribute term result; /** exceptions: need to wrap RuntimeException. */ exception Botch { string message; }; /** retrieves Integer array from 'terms' and computes and stores

a[0] a[1] a[2] a[3] a[4] ... where the a[odd] must be + - * % /.Various exceptions can happen...

*/ Cpu cpu (in Terms terms) raises (Botch); }; /** program string for the super computer... */ interface Terms { readonly attribute termseq terms; };};

The two interfaces are collected in a module which is mapped to a Java package.

If interfaces reference each other, one has to be declared first and elaborated later.

typedef can be used to create problem-specific types. typedef must be used, e.g., to use a sequence (an unbounded array) as a parameter.

IDL uses almost the same primitive types as Java and C++. However, int is missing from IDL, long is mapped to int, and long long is mapped to long. Aggregates are sequence and string, with an optional bound, and struct and union with an explicit tag, and more-dimensional arrays with fixed dimensions.

An attribute is an instance variable which will be mapped through access methods. readonly prevents write access.

Errors are declared using exception; they are relatively complicated to manage. In this case, the relevant classes end up in a Java package called CpuPackage.

For parameters the transport direction is specified: in, inout, or out. The Java method sees the actual object only in the first case, the Helper class generated by the IDL compiler must be used otherwise.

A method can be declared oneway; in this case it is called asynchronously.

Page 86: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

86

Cpu ServantExcept for implementing the result and passing the Terms, the Cpu servant remains the same. [Subclassing was avoided to show CORBA more directly.]

corba/cpu/Server.java

/** actual cpu server, retargeted from jdo-version. */ public static class Servant implements CpuOperations { private int result; Cpu wrapper; // must be set from the outside public int result () { return result; } public Cpu cpu (Terms terms) throws Botch { try {

int[] t = terms.terms();result = t[0];for (int n = 1; n < t.length; n += 2) switch (t[n]) { case '+': result += t[n+1]; continue; case '-': result -= t[n+1]; continue; case '*': result *= t[n+1]; continue; case '/': result /= t[n+1]; continue; case '%': result %= t[n+1]; continue; default: throw new Botch("unknown operator: "+t[n]); }

return wrapper; } catch (RuntimeException e) { throw new Botch(e.toString()); } } }

There is a catch with the delegate pattern: The Cpu servant is supposed to return itself as a result. This means that the tie object must be sent, and this does not exist when the servant is created. wrapper, therefore, must be set externally, prior to use.

Unfortunately, CORBA transmits a RuntimeException in Java in such a fashion that it is quite difficult to recover the original reason. This is circumvented using a CORBA exception Botch.

The servant implementation definitely is aware that it is distributed with CORBA.

Page 87: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

87

ServerThe main program creates and registers a servant and waits for calls:

corba/cpu/Server.java

/** creates and registers servant, waits forever for requests. */ public static void main (String args []) { try { ORB orb = ORB.init(args, null); // create and initialize application ORB Object boa = null; // basic object adapter, if supported try { boa = orb.getClass().getMethod("BOA_init",

new Class[] { args.getClass(), Properties.class }) .invoke(orb, new Object[] { args, null });

} catch (Exception e) { System.err.println(e+": ignored"); } Servant cpu = new Servant(); Cpu cpuServer = new Cpu_Tie(cpu); cpu.wrapper = cpuServer; orb.connect(cpuServer); // create server and register with ORB org.omg.CORBA.Object objRef = // get the root naming context

orb.resolve_initial_references("NameService"); NamingContext ncRef = NamingContextHelper.narrow(objRef);

// bind the server NameComponent path[] = { new NameComponent("Cpud", "") }; ncRef.rebind(path, cpuServer);

if (boa != null) // boa.impl_is_ready(null) try {

boa.getClass().getMethod("impl_is_ready", new Class[] { Class.forName("org.omg.CORBA.ImplementationDef") }) .invoke(boa, new Object[] { null });} catch (Exception e) { System.err.println(e); }

elsesynchronized (Server.class) { // sync on something to... Server.class.wait(); // ...wait for invocations}

} catch (Exception e) { e.printStackTrace(); } }

Nothing changes in comparison to the time server, except that wrapper must be set in the servant.

It appears that the class names for the tie classes are not standardized. JDK 1.3.0 creates Cpu_Tie as a class of it’s own, ORBacus derives _CpuImplBase_tie from _CpuImplBase. This would show up in the source of the server — it can be hidden using sed.

Page 88: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

88

Terms ServantThe main point of this example is that the client offers the data as a Terms object, which under CORBA is only sent by proxy:

corba/cpu/Client.java

/** provides a set of terms depending on global variable <tt>job</tt>. */ public static class Servant implements TermsOperations { public int[] terms () { return jobs[job]; } } private static int job; private static int[] jobs[] = { { 2, '+', 3 }, // 0: ok { 2, '/', 0 }, // 1: zero div { 2, '+' }, // 2: array bounds { 2, '?' } // 3: unexpected }; /** runs each row of global array <tt>jobs</tt>. */ protected static void run (Cpu cpu, Terms terms) { for (job = 0; job < jobs.length; ++ job) try {

System.out.println(cpu.cpu(terms).result()); } catch (Botch b) { System.err.println(b+": "+b.message); } catch (Exception e) { System.err.println(e); } }

One can see that sequence<long> is turned into an int[] — if need be, this could be discovered by reading the generated file TermsOperations.java. The mapping from IDL to Java has been pretty much fixed by the OMG.

Again, run() has some jobs processed by the service that depend on job. A CORBA exception is mapped to an exception class in Java which has to be caught explicitly. It is probably wise to catch a RuntimeException and send it as a separate CORBA exception.

Page 89: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

89

ClientUnlike the Time client, the Cpu client has to arrange for callbacks. Depending on the manufacturer, this requires a BOA and there has to be a Terms servant, which must be wrapped and registered with the ORB — but not the NameService.

corba/cpu/Client.java

/** <tt>java cpu.Client</tt><br> retargeted from jdo-version. */ public static void main (String args []) { try { ORB orb = ORB.init(args, null); // create and initialize the ORB Object boa = null; // basic object adapter, if supported try { boa = orb.getClass().getMethod("BOA_init",

new Class[] { args.getClass(), Properties.class }) .invoke(orb, new Object[] { args, null });

} catch (Exception e) { System.err.println(e+": ignored"); } org.omg.CORBA.Object objRef = // get the root naming context

orb.resolve_initial_references("NameService"); NamingContext ncRef = NamingContextHelper.narrow(objRef);

// now look for the cpu server NameComponent path[] = { new NameComponent("Cpud", "") }; Cpu cpuServer = CpuHelper.narrow(ncRef.resolve(path));

Terms termsServer = new Terms_Tie(new Servant()); orb.connect(termsServer); // create and register terms server if (boa != null) // boa.init_servers() try {

boa.getClass().getMethod("init_servers", new Class[0]).invoke(boa, new Object[0]);

} catch (Exception e) { System.err.println(e); }

run(cpuServer, termsServer); if (boa != null) // boa.deactivate_impl(null) try {

boa.getClass().getMethod("deactivate_impl", new Class[] { Class.forName("org.omg.CORBA.ImplementationDef") }) .invoke(boa, new Object[] { null });} catch (Exception e) { System.err.println(e); }

} catch (Exception e) { e.printStackTrace(); } }

For callbacks to work, the BOA needs a suitable thread model and the servers have to be initialized (once). This, however, results in a non-daemon thread, which may have to be disposed of, using the BOA, before the client terminates.

Page 90: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

90

3.3 Symmetric Communication: Chat ServiceAs an example of a more or less symmetrical service there is yet another implementation of the chat service of section 3.3, page 90:

corba/chat/chat.idl

// chat service interface definitionsmodule chat { interface Chat; // forward declaration /** a chat server. */ interface Room { void join (inout Chat withMe); // inout used for testing... }; /** a chat client. */ interface Chat { oneway void tell (in string message); };};

For demonstration purposes, join() uses an inout parameter (it could produce a result instead), and tell() is oneway.

In this example the server produces a new subserver for each new client which communicates asynchronously with the client.

Page 91: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

91

ChatRoom and PersonThe ChatRoom creates new servers which it has to wrap and register with the ORB:

corba/chat/Server.java

/** actual chat server, retargeted from jdo-version. */ public static class ChatRoom extends Vector implements RoomOperations { protected ORB orb; /** remember ORB to connect persons. */ public ChatRoom (ORB orb) { this.orb = orb; } /** create a person to multiplex utterances. */ public synchronized void join (ChatHolder withMe) { // inout withMe.value = new Chat_Tie(new Person(withMe.value)); orb.connect(withMe.value); } /** implements message queue to a single person in the room. */ public class Person extends Vector implements ChatOperations, Runnable { private final Chat partner; /** add myself to room, start thread to dequeue messages to me.

*/ Person (Chat partner) {

this.partner = partner; ChatRoom.this.addElement(this);new Thread(this).start();

} /** queue message with each person (including myself) in the room.

*/ public void tell (String message) {

synchronized(ChatRoom.this) { for (int n = 0; n < ChatRoom.this.size(); ++ n) { Person person = (Person)ChatRoom.this.elementAt(n); synchronized(person) { person.addElement(message); person.notify(); } }}

} /** dequeue messages to me.

*/ public void run () {

for (;;) { String message; synchronized(this) { while (size() == 0) try { wait(); } catch (InterruptedException e) { } message = (String)firstElement(); removeElementAt(0); } try { partner.tell(message); } catch (Throwable t) { break; }}ChatRoom.this.removeElement(this);

} } }

Page 92: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

92

A Person finds out about it’s Chat partner, logs itself in and out of the ChatRoom, and forwards messages from the ChatRoom.

The message queue is implemented using Vector because ORBacus can already be used with the JDK 1.1.

Server

corba/chat/Server.java

/** creates and registers ChatRoom, waits forever for requests. */ public static void main (String args []) { try { ORB orb = ORB.init(args, null); // create and initialize the ORB Object boa = null; // basic object adapter, if supported try { boa = orb.getClass().getMethod("BOA_init",

new Class[] { args.getClass(), Properties.class }) .invoke(orb, new Object[] { args, null });

} catch (Exception e) { System.err.println(e+": ignored"); } Room chatRoom = new Room_Tie(new ChatRoom(orb)); orb.connect(chatRoom); // create server and register with ORB org.omg.CORBA.Object objRef = // get the root naming context

orb.resolve_initial_references("NameService"); NamingContext ncRef = NamingContextHelper.narrow(objRef);

// bind the server NameComponent path[] = { new NameComponent("Chatd", "") }; ncRef.rebind(path, chatRoom);

if (boa != null) // boa.impl_is_ready(null) try {

boa.getClass().getMethod("impl_is_ready", new Class[] { Class.forName("org.omg.CORBA.ImplementationDef") }) .invoke(boa, new Object[] { null });} catch (Exception e) { System.err.println(e); }

elsesynchronized (Server.class) { // sync on something to... Server.class.wait(); // ...wait for invocations}

} catch (Exception e) { e.printStackTrace(); } }

The server once again needs an ORB and possibly a BOA for the main loop.

Page 93: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

93

ChatterThe client’s servant is based on the Abstract Window Toolkit:

corba/chat/Client.java

/** actual chat AWT client, retargeted from jdo-version. */ public static class Chatter extends Frame implements ChatOperations { protected TextArea ta = new TextArea(); protected TextField tf = new TextField(); Chat crowd; // must be set...

{ add(ta, BorderLayout.CENTER); ta.setEditable(false); add(tf, BorderLayout.SOUTH); tf.addActionListener(new ActionListener() { public void actionPerformed (ActionEvent ae) { if (crowd != null) { crowd.tell(tf.getText()); tf.selectAll(); } } }); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); pack(); show(); }

public void tell (String message) { ta.append(message+"\n"); } }

Just as for the arithmetic client, see section 3.2, page 85, the Chatter needs to know it’s Person in the ChatRoom, so that the TextField can send the input there.

Page 94: Distributed Objects - Computer Scienceats/do-2000-1/pdf/skript.pdf · 1.4 Distributed Objects With Java Pipes Symmetry Objects tend to talk back; executing a message usually requires

94

ClientThe chat client is similar to the arithmetic client. Once again, if necessary, a BOA is created to start a thread which should be terminated later:

corba/chat/Client.java

/** <tt>java chat.Client [host]</tt> */ public static void main (String args []) { try { ORB orb = ORB.init(args, null); // create and initialize the ORB Object boa = null; // basic object adapter, if supported try { boa = orb.getClass().getMethod("BOA_init",

new Class[] { args.getClass(), Properties.class }) .invoke(orb, new Object[] { args, null });

} catch (Exception e) { System.err.println(e+": ignored"); }

org.omg.CORBA.Object objRef = // get the root naming contextorb.resolve_initial_references("NameService");

NamingContext ncRef = NamingContextHelper.narrow(objRef);

// assume name services are bound... if (args != null && args.length > 0 && !args[0].startsWith("-")) {

NameComponent path[] = { new NameComponent(args[0], "") }; ncRef = NamingContextHelper.narrow(ncRef.resolve(path)); }

// now look for the chatRoom server NameComponent path[] = { new NameComponent("Chatd", "") }; Room chatRoom = RoomHelper.narrow(ncRef.resolve(path));

Chatter chatter = new Chatter(); // create and register chat server Chat chat = new Chat_Tie(chatter); orb.connect(chat); // create and register chat server

if (boa != null) // boa.init_servers() try {

boa.getClass().getMethod("init_servers", new Class[0]).invoke(boa, new Object[0]);

} catch (Exception e) { System.err.println(e); }

// now join the crowd ChatHolder ch = new ChatHolder(chat); chatRoom.join(ch); chatter.crowd = ch.value; } catch (Exception e) { e.printStackTrace(); } }

A ChatHolder is necessary to deal with the inout parameter of join().

Tie classes are useful even for simple inheritance, but one needs to watch out so that all the right references are built.