william grosso, [email protected] (c) 1999, all rights reserved basic rmi
TRANSCRIPT
William Grosso, [email protected] (C) 1999, all rights reserved
Basic RMI
Remote Method InvocationBasic ExampleActivationDouble-Checked Locking Socket FactoriesSystem Properties
Recall: SocketsGood
Efficient, well-understood Language/platform independent Easy to customize for security and
compressionLess Good
Not very abstract “Stream” data model isn’t very OO Relies on application-level protocol being
defined (and implemented)
The VisionIf the code on both sides is Java, then
the way to send data across the wire should also “be Java”
Send messages to objects (independent of location)
Caution: In the long-run, this is a pipe dream Objects across the wire have a much
greater latency, and can fail in ways that in-process objects cannot
What is RMI ?[R]emote [M]ethod [I]nvocationA way to send messages to objects
over the network Built on top of sockets Friendly and easy to use for Java
programmersAn entire framework for building
distributed applicationsInfrastructure for the really cool stuff
Definition Layering
java.net
RMI
Jini
Javaspaces
Implementation Layering
java.net
RMI
Jini
Javaspaces
Servlets
EJB
Shared Assumptionsabout the world
Our Example
Dispatcher
Client
Client
ClientLimo
Limo
Limo
Putting this on a Network
Dispatcher
Client
Client
ClientLimo
Limo
Limo
Stubs and Skeletons
Dispatcher
Client
Limo
Limo Stub
Dispatcher Stub
Dispatcher SkeletonLimo Skeleton
Making a Callclient clientSideStub serverSide
Skeletonserver
fooforward call along wire
foo
client "thinks" it is talking directly to server
All the arguments (and the method name) get wrapped up and sent over the wire
Skeleton unwraps everything and calls the servers implementatiof foo
Basic Development ProcedureDefine InterfacesDefine Serializable Value ObjectsStop and Think about Object Identity Implement ServerImplement ClientGenerate Stubs and Skeletons Write Security Policy(Run System)
Our Classes in UML
Remote
RemoteObject
UnicastRemoteObject Dispatcher Limo
DispatcherImpl LimoImpl
DriverLocationPassenger
Serializable
RemoteServer
Our Main Interfacespackage grosso.rmi;import java.rmi.*;
public interface Limo extends Remote { public void setDispatcher(Dispatcher d) throws RemoteException; public Location getLocation() throws RemoteException; public boolean getIsCarryingPassenger() throws RemoteException; public boolean wantPassenger(Passenger passenger) throws RemoteException; public boolean pickupPassenger(Passenger passenger) throws RemoteException; public Driver getDriver() throws RemoteException;}
package grosso.rmi;import java.rmi.*;
public interface Dispatcher extends Remote { public Limo hailLimo(Passenger passenger) throws RemoteException; public void limoAvailable(Limo limo)throws RemoteException; public void limoUnavailable(Limo limo)throws RemoteException;}
Serializable, Primitive, RemoteThe only value types referred to in the
interfaces are: Serializable objects Instances of a class that implements the
Remote interface Primitive values (byte, int, ...)
You can have non-serializable, non-remote objects in your applications. But they cannot be referred to in the code RMI will touch
Serialized ObjectsAre only used as arguments to method
calls (cannot be messaged)Are sent “by value”
The RMI mechanism uses a subclass of ObjectOutputStream to serialize the instance and send it over the wire
The receiving JVM creates a duplicate of the original object using the serialized information
A single ObjectOutputStream is used per method call
Remote ObjectsThe Remote interface is a marker
interface Objects which implement Remote are
“passed by reference”Typically you extend it by another
interface (declaring the server methods)
And then implement it by subclassing from UnicastRemoteObject
Remote
RemoteMethodDeclarations
LocalImpl
UnicastRemoteObject
Pass by Reference ?How do you pass a pointer to an object
in another process space?You don’t. Remote objects have stubs
and skeletons. RMI serializes out the Remote object
RMI uses a subclass of ObjectOutputStreamwhich over-rides replaceObject to swap the stub in
E.g. you serialize out a stub and send it over the wire
Object Identity is an IssueSuppose I callWhat happens in the server object ?
10 instances of Passenger are created They all have the same data inside them
(assuming a single-threaded client) But they are not the same object
They give different answers to hashcode and equals
Good practice is to over-ride equals and hashcode in your (immutable) value objects
for (int i = 0; i<10;i++) { limo.pickupPassenger(passenger)}
Locationpublic class Location implements Serializable { private int _x; private int _y; public Location(Point p) { _x = p.x; _y = p.y; }
// more constructors, getX() and getY() defined//(but, mercifully, omitted from slide)
public boolean equals(Object object) { if (object instanceof Location) { Location location = (Location)object; return ((_x == location.getX()) && (_y == location.getY())); } return false; } public String toString(){ return ("X:" + _x +"Y:" + _y); } public int hashCode() { return toString().hashCode(); }}
Passengerpublic class Passenger implements Serializable { private Location _startingLocation; private Location _destination;
public Passenger(Location startingLocation, Location destination) { _startingLocation = startingLocation; _destination = destination; }// getStartingLocation and getDestination defined but omitted
public boolean equals(Object object){ if (object instanceof Passenger ) { Passenger passenger= (Passenger)object; return ((_startingLocation.equals(passenger.getStartingLocation()))
&& (_destination.equals(passenger.getDestination()))); } return false; } public String toString() { return "Start:" + _startingLocation.toString() +
":end:" + _destination.toString(); } public int hashCode() { return toString().hashCode(); }}
Basic Development ProcedureDefine InterfacesDefine Serializable Value ObjectsStop and think about object identity Implement ServerImplement ClientGenerate Stubs and Skeletons Write Security PolicyRun System
Two Types of ServersNamed, universally locateable ones
Implement Remote (and extend UnicastRemoteObject)
Listed in a registry somewhere Example: DispatcherImpl
Servers that clients must be told about Implement Remote (and extend
UnicastRemoteObject) But not in listed in any registry Example: LimoImpl
Distinction is not in the ServerUsually, there is some factory object,
or launching code, that knows whether a given server needs to be registered
You can’t tell from the interface, and usually can’t tell from the implementation, how a server will make itself available This is part of the “background
assumptions” which appeared on our architecture diagram
NamingSimple interface to (already running)
registry Implemented via 5 static methods
public static String[] list(String name) public static Remote lookup(String name) public static void bind(String name, Remote obj) public static void rebind(String name, Remote obj) public static void unbind(String name)
What’s in a NameAll the arguments to Naming take a
String with the following format: Host defaults to the local machine Port defaults to 1099 name is intended to be human readable
”Find the rmiregistry listening on [port] of [machine]. Register me there as [name].” ”Find the rmiregistry listening on [port] of
[machine]. Get me the registered oject named [name]”
//host:port/name
DispatcherImplpublic class DispatcherImpl extends UnicastRemoteObject implements Dispatcher{ private ArrayList _availableLimos;
public DispatcherImpl() throws RemoteException { _availableLimos= new ArrayList(); } public Limo hailLimo(Passenger passenger) throws RemoteException{ Collections.shuffle(_availableLimos); Iterator i = _availableLimos.iterator(); while(i.hasNext()){ Limo limo = (Limo) i.next(); if (limo .pickupPassenger(passenger)){ return limo; } } return null; } public void limoAvailable(Limo limo) throws RemoteException{ if (false==_availableLimos.contains(limo)) { _availableLimos.add(limo); } } public void limoUnavailable(Limo limo) throws RemoteException{ _availableLimos.remove(limo); }}
LaunchDispatcher
package grosso.rmi;import java.rmi.server.*;import java.rmi.*;
public class LaunchDispatcher { public static void main(String[] args) { System.setSecurityManager(new RMISecurityManager()); try { Dispatcher dispatcher = new DispatcherImpl(); Naming.rebind("Dispatcher ", dispatcher); } catch (Exception e){} }}
LimoImplpackage grosso.rmi;import java.rmi.*;import java.rmi.server.*;
public class LimoImpl extends UnicastRemoteObject implements Limo { private boolean _havePassenger; private Dispatcher _dispatcher; private Location _currentLocation; private Driver _driver; public LimoImpl(Dispatcher dispatcher, Driver driver)
throws RemoteException { _havePassenger = false; _dispatcher = dispatcher; _driver = driver; dispatcher.limoAvailable(this); }
public Location getLocation() throws RemoteException { return _currentLocation; }
public boolean getIsCarryingPassenger() throws RemoteException { return _havePassenger; }
LimoImpl IIpublic void setDispatcher(Dispatcher dispatcher) throws RemoteException{ if ((false==_havePassenger) &&(null!=_dispatcher)) { _dispatcher.limoUnavailable(this); if(null!=dispatcher){ dispatcher.limoAvailable(this); } } _dispatcher = dispatcher; }
public boolean wantPassenger(Passenger passenger) throws RemoteException { return (false ==_havePassenger); // a very simple model of
// cabbie decision making }
public Driver getDriver() throws RemoteException { return _driver; }}
LimoImpl III
public boolean pickupPassenger(Passenger passenger) throws RemoteException
{ if (true==_havePassenger) { return false; } _havePassenger = true; _dispatcher.limoUnavailable(this); _currentLocation=passenger.getDestination(); _dispatcher.limoAvailable(this); return true; }}
LaunchLimopublic class LaunchLimo { public static void main(String[] args) {// In reality, we'd probably do something a little cleverer // here (use more command line args or use a factory server) System.setSecurityManager(new RMISecurityManager()); try { Dispatcher dispatcher = (Dispatcher) Naming.lookup("Dispatcher");
// name bootstrap issue for (int currentTaxiDriver=0; currentTaxiDriver < args.length;
currentTaxiDriver++) { Driver driver = new Driver(args[currentTaxiDriver]); Limo currentLimo = new LimoImpl(dispatcher, driver); System.out.println("Driver " + driver.name + " is on the road"); } System.out.println("All drivers have been launched"); } catch (Exception e){} }}
Registration Timing Sequence
launchLimo dispatcher registry particularLimo:LimoImpl
Naming
lookup(dispatcher)
create
add in limo
SimpleClientpublic class SimpleClient extends SelfCleaningFrame{ private JTextArea _reportsBack; private JTextField _startX; private JTextField _startY; private JTextField _destinationX; private JTextField _destinationY; private JButton _hailCab;
public SimpleClient(){ // set up GUI. The key point here is that all the action is // keyed around an event on the hailcab button. }
public static void main(String[] args){ System.setSecurityManager(new RMISecurityManager()); new SimpleClient().show(); }}
SimpleClientII private class ButtonAction implements ActionListener { public void actionPerformed(ActionEvent e){ try { Dispatcher dispatcher = (Dispatcher) Naming.lookup("Dispatcher"); Limo limo = dispatcher.hailLimo(getPassenger()); if (null!=limo) { Driver driver = limo.getDriver(); _reportsBack.append("Limo is driven by " + driver.name +"\n"); } else { _reportsBack.append("No limos available \n"); } } catch (Exception ee){} }
private Passenger getPassenger() { Location startingLocation = new Location(1,4); Location destination = new Location(2,5); return new Passenger(startingLocation, destination); } }
Method Call Timing Sequence
client Naming dispatcher limo
lookup
hailLimo
pickup passenger
returns limo
Generating Stubs / SkeletonsRMIC -- [RMI] [C]ompilerAutomatically builds stubs and
skeletons from .class files of server implementations“rmic -d outputpath fully.qualified.class.name”
Running The Current System
start java -Djava.security.policy=java.policy grosso.rmitalk.LaunchLimo Bob Al Fred Kerry
Start the registry going
Launch the Dispatcher
Launch the Limos
Run the Client App
start rmiregistry
start java -Djava.security.policy=java.policy grosso.rmi.LaunchDispatcher
java -Djava.security.policy=java.policy grosso.rmi.SimpleClient
SimpleClient
What We didDefine InterfacesDefine Serializable Value ObjectsStop and think about object identity Implement ServerImplement ClientGenerate Stubs and Skeletons Write Security PolicyRun System
We’ll talk about this another day
Homework Make this a multi-threaded applicationStart with dispatcher-- should be able
to handle multiple requests Limos will have to be able to handle
multiple simultaneous requests as wellBe efficient ! Synchronizing pickupPassenger()
is not a good idea
You may need to be careful with limoAvailable() / limoUnavailable() as well
Making Things More SecureSo what we’ve been talking about is a
way for objects to be transparently made persistent
This involved Sending serialized object states over the wire Lots of reflection The occasional loading of bytecode from
serversWe didn’t really discuss this because only so
much fits in an overview. But part of the magic is that class definitions travel across the wire too
Socket FactoriesOne way to make things more secure
is not to use the standard (“cleartext”) sockets.
Fortunately, this is really easy to do Simply write the socket factory objects And rewrite the constructor in the server
objects that will use the custom sockets
The Factories
public class ClientSocketFactory implements RMIClientSocketFactory, Serializable { public Socket createSocket(String host, int port) throws IOException { return new CompressingSocket(host, port); }}
public class ServerSocketFactory implements RMIServerSocketFactory, Serializable{ public ServerSocket createServerSocket(int port) throws IOException { return new CompressingServerSocket(port); } }
Changing Dispatcher
public class DispatcherImpl extends UnicastRemoteObject implements Dispatcher { private ArrayList _availableLimos;
public DispatcherImpl() throws RemoteException { super(0, new ClientSocketFactory(), new ServerSocketFactory()); _availableLimos= new ArrayList();// binding to registry happens in Factory code }
public Limo hailLimo(Passenger passenger) throws RemoteException{ Collections.shuffle(_availableLimos); Iterator i = _availableLimos.iterator(); while(i.hasNext()) {
Why are the Factories Serializable ?When a connection is made,
Naming.lookup is called
The factory is serialized and downloaded to the registry and, from there, to the client
This is actually a little tricky-- you have to relax the security policy at the registry for this to work
Dispatcher dispatcher = (Dispatcher) Naming.lookup("Dispatcher");
Security Policy Teaser
grant { permission java.net.SocketPermission ":0-", "accept,connect,listen";};
Useful System PropertiesSystem Property Values Usage Noteshttp.proxyHost internet address Set on the client side, to let RMI know that HTTP
tunneling is available (from the internet addresslisted). What will happen is that RMI will attempt tomake connections in the normal way. If that fails, itwill attempt to use HTTP tunnelling to send themessage. This usually winds up devolving to
http://internetaddress:80/java-rmi.cgi?port=xxx?arg1=value1?....
java.rmi.dgc.leaseValue number of milliseconds How long is a default lease for ?
java.rmi.server.hostname internet address The issue here is—how does RMI figure out wherethe server is. Which starts out life as “how does theserver figure out where it is ?”This used to be a call to a naming server, but waschanged in JDK1.1 to be a native call instead. Saidnative call can sometimes return the wrong answer(e.g. an unqualified host name).
If you set this property, RMI will use this valueinstead of the value the server returns.
java.rmi.server.useLocalHostname true, false Setting this to true (it defaults to false) and no valuefor java.rmi.server.hostname will force RMI to use afully qualified hostname obtained from a nameservice.
java.rmi.server.codebase url Set on servers. This allows clients to download theclass definitions (e.g. bytecode) on the fly. The wayRMI does this is: First: Check the local classpath Second: Use contextual classpath (e.g. codebase tag in applets) Third: Use the url indicated by this flag.
Useful System PropertiesSystem Property Values Usage Notes
java.rmi.server.logCalls true, false Defaults to false. If true, all calls to remote objectswill be logged in a log file. For greater control overthe log file, use RemoteServer’s getLog() andsetlog(...) methods.
Activation sets this to true for the VMs it launches.java.security.policy filesun.rmi.transport.connectionTimeout number of milliseconds How long before an attempt to connect will timeout
.(SUN specific)sun.rmi.server.activation.debugExec true, false When Activation launches a VM, this prints out all
the parameters used. Can be handy for spotting aconfiguration error. (SUN specific)
Advanced Questions
Why does rmic require an implementation rather than an interface to generate stubs and skeletons ?
Server objects can support multiple interfaces, but only have one skeleton/stub pair.
Doing things this way allows reflective calls to succeed on the client side. E.g. the stub has all the information that is necessary to determine whether or not a server implements a particular interface (otherwise using instanceof or other forms of reflection would require a round-trip across the wire)
AA
ReferencesThe RMI FAQ:
http://java.sun.com/products/jdk/rmi/faq.html
Archives of the RMI mailing list
http://archives.java.sun.com/archives/rmi-users.html
The RMI Specification
http://java.sun.com/products/jdk/1.2/docs/guide/rmi/spec/rmiTOC.doc.html