Earlier in the chapter we mentioned some of the situations that might lead you to use message passing rather than remote objects to handle the agent-to-agent communication in your distributed system. To get a more concrete feeling for the differences between a message-passing system and one based on remote objects, this section describes an RMI implementation of our chess game.
The RMI implementation of the chess game uses two remote objects: a remote chess player and a remote chess move. The interface for the RMIChessMove object is shown in Example 6-18. It has essentially the same interface as the ChessMove class in our earlier examples.
package dcj.examples.message; import java.rmi.RemoteException; // Interface for an RMI-based chess move public interface RMIChessMove { public String from() throws RemoteException; public void setFrom(String f) throws RemoteException; public String to() throws RemoteException; public void setTo(String t) throws RemoteException; public int checkFlag() throws RemoteException; public void setCheckFlag (String f) throws RemoteException; }
Example 6-19 shows the RMIChessMoveImpl class, which is the server implementation of the chess move object.
package dcj.examples.message; import java.rmi.*; public class RMIChessMoveImpl implements RMIChessMove extends Remote { String fromPos; String toPos; int checkFlag; public RMIChessMoveImpl(String from, String to, int ckFlag) { fromPos = from; toPos = to; checkFlag = ckFlag; } public String from() throws RemoteException { return fromPos; } public void setFrom(String f) throws RemoteException { fromPos = f; } public String to() throws RemoteException { return toPos; } public void setTo(String t) throws RemoteException { toPos = t; } public int checkFlag() throws RemoteException { return checkFlag; } public void setCheckFlag(String f) throws RemoteException { checkFlag = f; } }
Example 6-20 shows the interface for the RMIChessPlayer. It has essentially the same interface as the earlier ChessPlayer, except that we've changed the nextMove() method to return the player's move as an RMIChessMove object, rather than returning a boolean flag and filling in method arguments to indicate the move. We've also added a gameOver() method, which reveals whether the player has finished the game it was playing (win or lose).
package dcj.examples.message; import java.rmi.RemoteException; // Interface for an RMI-based chess player public interface RMIChessPlayer { public boolean acceptMove(RMIChessMove m) throws RemoteException; public RMIChessMove nextMove() throws RemoteException; public void moveAccepted(RMIChessMove m) throws RemoteException; public void conceded() throws RemoteException; }
The server implementation of the RMIChessPlayer is shown in Example 6-21 as the RMIChessPlayerImpl. Again, it's very similar to the ChessPlayer implementation, with the addition of the booleangameOver data member, the exceedingly simple gameOver() method, and a main() method. The main() method on the RMIChessPlayerImpl takes the place of the ChessServer in our message-passing chess game. If we call it with a single command line argument, then that argument is treated as the name used to register a local RMIChessPlayerImpl object with the RMI Naming service. Once the local player is registered with the RMI registry, the method loops waiting for a remote player to start a game, checking the local player to see when its game is over before quitting. If we invoke the class with a local player name plus a remote host name and remote player name on the command line, then the main() method tries to look up a remote RMIChessPlayer on that host under that name. If it finds one, it mediates a game between the remote and local players, iteratively calling each player's nextMove() and acceptMove() methods.
package dcj.examples.message; // Server implementation of our RMI-based chess player public class RMIChessPlayerImpl implements RMIChessPlayer extends Remote { // Our opponent protected RMIChessPlayer opponent = null; // Game-over indicator protected boolean gameOver = false; // Data structures for maintaining chess board state // ... public static final int CHECK = 0; public static final int CHECKMATE = 1; public static final int NO_CHECK = 2; public RMIChessPlayerImpl() { // Initialize chess board } public boolean acceptMove(RMIChessMove m) throws RemoteException { // Check validity of requested move. // If valid, apply to chess board and return true. // If invalid, return false. // ... return true; } public boolean nextMove(RMIChessMove m) throws RemoteException { // Generate our next move based on the current // state of the game board, and put it into the // ChessMove passed in. If no move in this round, // return false. // ... return true; } public void moveAccepted(RMIChessMove m) throws RemoteException { // Our move was accepted as valid, apply it to the // game board... // ... } public void conceded() throws RemoteException { // We've won! gameOver = true; } public static void main(String argv[]) { String playerName = argv[0]; // Create the chess player for our side of the game RMIChessPlayerImpl me = new RMIChessPlayerImpl(); // If we've been given an opponent, try to start // a game with them... if (argv.size > 2) { String oppName = argv[1]; String oppHost = argv[2]; RMIChessPlayer opponent = (RMIChessPlayer)Naming.lookup("rmi://" + oppHost + "/" + oppName); RMIChessMoveImpl myMove = new RMIChessMoveImpl(); RMIChessMoveImpl theirMove = new RMIChessMoveImpl(); while (!gameOver) { if (opponent.nextMove(theirMove)) { while (!me.acceptMove(theirMove) && opponent.nextMove(theirMove)) { // Don't have to do anything, the while // loop conditions do all the work. } } if (me.nextMove(myMove)) { while (!opponent.acceptMove(myMove) && me.nextMove(myMove)) {} } } } else { // No arguments telling us where our opponent is, so // we register the local player and wait for them to come // to us. // Bind the player to their name in the registry Naming.rebind(playerName, player); // Now wait for another player to engage us in a game. while(1) { wait(); } } } }
In this version of our chess game, the MessageHandler and the various Message subclasses have been replaced by the remote method-calling services provided by RMI. The RMI-based chess game is also easier to extend with new features--we just need to add new methods to our RMIChessPlayer interface. In the worst case, we'll need to define some new remote objects to support new features. In the message-passing version, we would need to extend the ChessPlayer interface, create new subclasses of Message to support the new functions, and update the ChessServer class to support the new message types. What we've lost in the RMI version is some flexibility in terms of the low-level communication protocol. Since RMI is encapsulating all of the network-level details of the remote method calls, we can't control the data protocol directly, as we do with message passing. If RMI's network protocol imposes too much overhead on our distributed system, there's little we can do about it except minimize the data members and method arguments on our remote objects. And if we're faced with a firewall that blocks the RMI protocol (perhaps for reasons that are known only to the network operator), then our distributed system is stopped dead in its tracks. With a simple (some would say barebones) message-passing system, we can directly control the format of both the serialized data and the communication protocol to suit our needs, and we can get our messages through using relatively "unadorned" IP packets.
Copyright © 2001 O'Reilly & Associates. All rights reserved.