3 PLANEAMIENTO ESTRATÉGICO
3.3 A NÁLISIS FODA
Distributed systems that support only a pure client/server model sometimes give system designers fits. In many applications, such as banking, the pure client/server model fits quite well, since the client always initiates requests, and the server handles them and passes the data back until the transaction is completed.
In other applications, you need the server to be able to invoke methods in the client as well. This is called peer-to- peer since both objects take on the role of client and server.
The observer-observable model is often needed in distributed systems. The interaction in the model occurs in both directions. Consequently, you need the observer to behave as a client to register itself with the observable and then behave as a server so the observable can invoke the update method in the observer.
If an object can only be a client or a server and not both, you can still implement the observer-observable model but the methods are ugly. The observer could periodically poll the observable to see whether it changes. But this would put a tremendous burden on the observable, since it spends a lot of time telling the observers that it hasn't changed. The observable could also set up a waitForChange method that blocks until the observable changes. This could result in a large number of threads on the observable just sitting around waiting for a change.
It consumes less network resources than the polling method because there are no "have you changed?" "No." messages flying back and forth. This is still a less-than-optimal solution, however.
For one thing, suppose the observable changes in the time that it takes the observer to call waitForChange again. Should it keep track of whether things have changed since the last call? If so, that's extra work. If not, the observer may miss changes.
Chapter 16 -- Creating 3-Tier Distributed Applications with RMI
The RMI system allows an object to be both a client and a server, relieving you of many of these headaches. Typically, one object starts out as the server and one starts out as the client. At some point, the client invokes a method on the server and passes a stub back to the client, and the client also becomes a server.
You might, for example, have a server that sends periodic updates of information. A client registers with the server telling it what information it wants and passes the client's stub to the server. Whenever the server has new
information, it invokes a method in what was originally the client via the stub. Figure 16.5 shows the relationship between two objects in a peer-to-peer stock-quoting system.
Figure 16.5 : The stock-quote server uses RMI to send quotes to its clients.
Listing 16.8 shows a remote interface for a stock-quoting system that invokes a method in its clients to deliver stock quotes.
Listing 16.8 Source Code for StockQuoteServer.java
package stocks;
// Defines a remote interface for a stock quoting system. // Stock quotes are delivered to remote objects through the // StockQuoteClient interface.
public interface StockQuoteServer extends java.rmi.Remote {
// addWatch tells the server that the client wants quotes for // a certain stock.
public void addWatch(StockQuoteClient client, String stock) throws java.rmi.RemoteException, StockQuoteException; // removeWatch tells the server that the client no longer wants // to watch a certain stock.
public void removeWatch(StockQuoteClient client, String stock) throws java.rmi.RemoteException, StockQuoteException; // removeClient tells the server that the client no longer wants // to watch any stocks.
public void removeClient(StockQuoteClient client)
throws java.rmi.RemoteException, StockQuoteException;
// getStockList returns an array of all the stocks that can be watched public String[] getStockList()
Chapter 16 -- Creating 3-Tier Distributed Applications with RMI
}
Listing 16.9 shows the StockQuoteClient interface that the StockQuoteServer uses to notify its clients of new quotes
Listing 16.9 Source Code for StockQuoteClient.java
package stocks;
// Defines a callback interface for the StockQuoteServer so // it can notify its clients of new stock quotes.
public interface StockQuoteClient extends java.rmi.Remote {
public void quote(StockQuote quote) throws java.rmi.RemoteException; }
Rather than putting the individual elements of a stock quote into the method definition, the stock quotes are passed around in a StockQuote object. If the system expands the information in the stock quote, it still works with the existing clients, as long as it doesn't remove or rename any fields. This lets you build an extensible system without having to change all your existing clients at once. If you change the quote method, however, all the clients have to change. Listing 16.10 shows the StockQuote object.
Listing 16.10 Source Code for StockQuote.java
package stocks;
// Defines the information contained in a stock quote for the // StockQuoteClient interface.
public class StockQuote {
public String stock; // the stock name public double amount; // the last price public double change; // the last change public StockQuote()
{ }
Chapter 16 -- Creating 3-Tier Distributed Applications with RMI
public StockQuote(String stock, double amount, double change) { this.stock = stock; this.amount = amount; this.change = change; } }
The stock-quote system defines its own exceptions. You should always do this for your systems if you intend to throw any exceptions outside the standard ones in Java.
StockQuoteException serves as the base class for all specific exceptions in the stock-quote system. There is only one specific exception defined: UnknownStockException.
Again, if you can define a specific exception, do it. Don't heap everything into one generic exception. Listings 16.11 and 16.12 show StockQuoteException and UnknownStockException.
Listing 16.11 Source Code for StockQuoteException.java
package stocks;
// Defines a generic exception for the stock quoting system public class StockQuoteException extends Exception
{ public StockQuoteException() { } public StockQuoteException(String str) { super(str); } }
Listing 16.12 Source Code for UnknownStockException.java
package stocks;
// Defines an exception for an unknown stock.
Chapter 16 -- Creating 3-Tier Distributed Applications with RMI { public UnknownStockException() { } public UnknownStockException(String str) { super(str); } }
Distributed systems have their own unique little problems. When you invoke a method on an object locally, you don't worry about whether or not the method will be invoked. If you get an exception, you know that there was an error within the method and not a problem invoking the method.
There are, however, many things in a distributed system that can stand between a client and the remote method it is invoking. When you get a RemoteException, you don't know what the problem is. The network could have had a temporary failure, the server program could have died, or the machine the server was running on could have died. Listing 16.13 shows the addWatch method from the StockQuoteServerImpl class included on the CD for this book. This method is invoked by clients to subscribe to stock quotes. The first parameter to the addWatch method is a reference to the client (actually, a stub for communicating with the client). The server saves this reference for later use when it goes to publish new stock quotes. The StockQuoteServerImpl keeps a table of clients for each stock, because a stock can have multiple clients (a client of a stock receives quotes for that stock).
Listing 16.13 addWatch Method for StockQuoteServerImpl.java
// addWatch adds a client to the list of clients watching a stock public void addWatch(StockQuoteClient client, String stock) throws java.rmi.RemoteException, StockQuoteException
{
// If we don't know about the stock, throw an exception if (stocks.get(stock) == null) {
throw new UnknownStockException(stock); }
// Get the container of clients watching this stock
Vector clients = (Vector) stockClients.get(stock); // If no clients are watching, create the container
if (clients == null) {
Chapter 16 -- Creating 3-Tier Distributed Applications with RMI
clients = new Vector(); clients.addElement(client);
stockClients.put(stock, clients);
// Only add the client if it isn't already there. We don't want to // double-update clients.
} else if (!clients.contains(client)) { clients.addElement(client);
} }
One of the most important things you must handle when performing callbacks is figuring out when a client has
disconnected. The StockQuoteServerImpl class uses a very simple technique-when the server sends a stock quote to a client that results in an exception, the server disconnects the client. Listing 16.14 shows the publishQuote method from the StockQuoteServerImpl. Notice that when the publishQuote method catches an exception when publishing the quote, it does not immediately remove the client. Instead, it stores the reference to the client in a separate vector. This is necessary because an enumeration can become confused if you remove elements from a vector while you are enumerating through it.
Listing 16.14 publishQuote Method from StockQuoteServerImpl.java
// publishQuote sends a stock quote to every client who is watching protected void publishQuote(StockQuote quote)
{
// Get the list of clients for the stock
Vector v = (Vector) stockClients.get(quote.stock); // If there are no clients, we're done
if (v == null) return;
Enumeration e = v.elements();
// When we get an exception sending a notification to a client, we // remove the client. We don't do it until we've sent all the
// notifications however. We store them in badClients until then. Vector badClients = null;
while (e.hasMoreElements()) {
StockQuoteClient client = (StockQuoteClient) e.nextElement();
Chapter 16 -- Creating 3-Tier Distributed Applications with RMI
// send the quote to the client try {
client.quote(quote);
// If we get an error, add the client to the list of bad clients } catch (java.rmi.RemoteException oops) {
if (badClients == null) {
badClients = new Vector(); }
badClients.addElement(client); }
}
// If there were any bad clients, remove them if (badClients != null) { e = badClients.elements(); while (e.hasMoreElements()) { clearClient( (StockQuoteClient) e.nextElement()); } } }
To do peer-to-peer RMI from an applet, you have to create another object to be the server for method invocations to the applet. You can't remotely call methods in a subclass of Applet because you must inherit from RemoteServer. Since Java doesn't allow multiple inheritance, you must define another object to handle the incoming remote method invocations. If you really want to invoke methods in the applet, the special object you create can just turn around and invoke methods in the applet.
Listing 16.15 shows a simple stock-quote client that receives all stock quotes from the stock-quote server.
Listing 16.15 Source Code for StockQuoter.java
package stocks;
import java.rmi.server.UnicastRemoteServer; import java.rmi.server.StubSecurityManager;
// This class is a client of the StockQuoteServer. It acts as a // server too, since the StockQuoteServer invokes the update method // in this object.
Chapter 16 -- Creating 3-Tier Distributed Applications with RMI
public class StockQuoter extends UnicastRemoteServer implements StockQuoteClient { public StockQuoter() throws java.rmi.RemoteException { }
// When we receive a stock quote, just print out the information public void quote(StockQuote stockQuote)
throws java.rmi.RemoteException {
System.out.println(stockQuote.stock+": "+stockQuote.amount+ "("+stockQuote.change+")");
}
public static void main(String[] args) {
// Always use a security manager for RMI.
System.setSecurityManager(new StubSecurityManager()); try {
// Get a stub to the stock quoting system
StockQuoteServer server = (StockQuoteServer) java.rmi.Naming.lookup("StockQuotes");
// Create an instance of this object to receive the incoming stock quotes StockQuoter quoter = new StockQuoter();
// Get a list of all the stock we can watch
String[] stocks = server.getStockList(); // Subscribe to each stock
for (int i=0; i < stocks.length; i++) { server.addWatch(quoter, stocks[i]); }
} catch (Exception e) {
System.out.println("Got exception: "+e); e.printStackTrace();
} }
Chapter 16 -- Creating 3-Tier Distributed Applications with RMI