Remote Objects
Download
Report
Transcript Remote Objects
Basic RMI
William Grosso, [email protected] (C) 1999, all rights reserved
Remote Method Invocation
Basic Example
Activation
Double-Checked Locking
Socket Factories
System Properties
Recall: Sockets
Good
Efficient, well-understood
Language/platform independent
Easy to customize for security and compression
Less Good
Not very abstract
“Stream” data model isn’t very OO
Relies on application-level protocol being
defined (and implemented)
The Vision
If 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]nvocation
A way to send messages to objects over the
network
Built on top of sockets
Friendly and easy to use for Java programmers
An entire framework for building distributed
applications
Infrastructure for the really cool stuff
Definition Layering
Javaspaces
Jini
RMI
java.net
Implementation Layering
Javaspaces
EJB
Jini
RMI
Servlets
java.net
Our Example
Limo
Dispatcher
Client
Limo
Client
Shared Assumptions
about the world
Limo
Client
Putting this on a Network
Limo
Dispatcher
Client
Limo
Client
Limo
Client
Stubs and Skeletons
Dispatcher
Limo Stub
Dispatcher Skeleton
Limo Skeleton
Limo
Dispatcher Stub
Client
Making a Call
client
clientSideStub
serverSide
Skeleton
server
foo
forward 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 implem entatiof
foo
Basic Development Procedure
Define Interfaces
Define Serializable Value Objects
Stop and Think about Object Identity
Implement Server
Implement Client
Generate Stubs and Skeletons
Write Security Policy
(Run System)
Our Classes in UML
RemoteObject
Remote
RemoteServer
UnicastRemoteObject
Dispatcher
Limo
Serializable
DispatcherImpl
LimoImpl
Passenger
Location
Driver
Our Main Interfaces
package 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, Remote
The 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 Objects
Are 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 Objects
The 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
UnicastRemoteObject
RemoteMethodDeclarations
LocalImpl
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 Issue
for (int i = 0; i<10;i++) {
limo.pickupPassenger(passenger)
}
Suppose I call
What 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
Location
public 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();
}
}
Passenger
public 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 Procedure
Define Interfaces
Define Serializable Value Objects
Stop and think about object identity
Implement Server
Implement Client
Generate Stubs and Skeletons
Write Security Policy
Run System
Two Types of Servers
Named, 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 Server
Usually, 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
Naming
Simple 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 Name
All the arguments to Naming take a String
with the following format: //host:port/name
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]”
DispatcherImpl
public 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){}
}
}
LimoImpl
package 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 II
public 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;
}
}
LaunchLimo
public 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
Naming
dispatcher
lookup(dispatcher)
create
add in lim o
registry
particular
Lim o:LimoImpl
SimpleClient
public 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
lookup
hailLimo
pickup passenger
returns limo
limo
Generating Stubs / Skeletons
RMIC -- [RMI] [C]ompiler
Automatically builds stubs and skeletons
from .class files of server implementations
“rmic -d outputpath fully.qualified.class.name”
Running The Current System
Start the registry going
start rmiregistry
Launch the Dispatcher
start java -Djava.security.policy=java.policy grosso.rmi.LaunchDispatcher
Launch the Limos
start java -Djava.security.policy=java.policy grosso.rmitalk.LaunchLimo Bob Al Fred Kerry
Run the Client App
java -Djava.security.policy=java.policy grosso.rmi.SimpleClient
SimpleClient
What We did
Define Interfaces
Define Serializable Value Objects
Stop and think about object identity
Implement Server
Implement Client
Generate Stubs and Skeletons
We’ll talk about this another day
Write Security Policy
Run System
Homework
Make this a multi-threaded application
Start with dispatcher-- should be able to
handle multiple requests
Limos will have to be able to handle multiple
simultaneous requests as well
Be efficient ! Synchronizing pickupPassenger() is not
a good idea
You may need to be careful with limoAvailable()
/ limoUnavailable() as well
Making Things More Secure
So 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 servers
We 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 Factories
One 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
Dispatcher dispatcher = (Dispatcher) Naming.lookup("Dispatcher");
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
Security Policy Teaser
grant {
permission java.net.SocketPermission ":0-", "accept,connect,listen";
};
Useful System Properties
System Property
http.proxyHost
Values
internet address
Usage Notes
Set on the client side, to let RMI know that HTTP
tunneling is available (from the internet address
listed). What will happen is that RMI will attempt to
make connections in the normal way. If that fails, it
will attempt to use HTTP tunnelling to send the
message. This usually winds up devolving to
http://internetaddress:80/javarmi.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 where
the server is. Which starts out life as “how does the
server figure out where it is ?”
This used to be a call to a naming server, but was
changed in JDK1.1 to be a native call instead. Said
native call can sometimes return the wrong answer
(e.g. an unqualified host name).
If you set this property, RMI will use this value
instead of the value the server returns.
java.rmi.server.useLocalHostname
true, false
java.rmi.server.codebase
url
Setting this to true (it defaults to false) and no value
for java.rmi.server.hostname will force RMI to use a
fully qualified hostname obtained from a name
service.
Set on servers. This allows clients to download the
class definitions (e.g. bytecode) on the fly. The way
RMI 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 Properties
System Property
Values
Usage Notes
java.rmi.server.logCalls
true, false
Defaults to false. If true, all calls to remote objects
will be logged in a log file. For greater control over
the log file, use RemoteServer’s getLog() and
setlog(...) methods.
java.security.policy
sun.rmi.transport.connectionTimeout
file
number of milliseconds
sun.rmi.server.activation.debugExec
true, false
Activation sets this to true for the VMs it launches.
How long before an attempt to connect will timeout
.(SUN specific)
When Activation launches a VM, this prints out all
the parameters used. Can be handy for spotting a
configuration error. (SUN specific)
Advanced Questions
Q
A
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)
References
The 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