Page #492 (36.5. Developing Three-Tier Applications Using RMI)

 
[Page 1255 ( continued )]

36.6. RMI Callbacks

In a traditional client/server system, a client sends a request to a server, and the server processes the request and returns the result to the client. The server cannot invoke the methods on a client. One of the important benefits of RMI is that it supports callbacks , which enable the server to invoke methods on the client. With the RMI callback feature, you can develop interactive distributed applications.


[Page 1256]

In §25.9, "Case Studies: Distributed TicTacToe Games," you developed a distributed TicTacToe game using stream socket programming. The following example demonstrates the use of the RMI callback feature to develop an interactive TicTacToe game.

All the examples you have seen so far in this chapter have simple behaviors that are easy to model with classes. The behavior of the TicTacToe game is somewhat complex. To create the classes to model the game, you need to study and understand it and distribute the process appropriately between client and server.

Clearly the client should be responsible for handling user interactions, and the server should coordinate with the client. Specifically, the client should register with the server, and the server can take two and only two players. Once a client makes a move, it should notify the server; the server then notifies the move to the other player. The server should determine the status of the game, that is, whether the game has been won or drawn, and should notify the players. The server should also coordinate the turns, that is, which client has the turn at a given time. The ideal approach for notifying a player is to invoke a method in the client that sets appropriate properties in the client or sends messages to a player. Figure 36.7 illustrates the relationship between clients and server.

Figure 36.7. The server coordinates the activities with the clients.

All the calls a client makes can be encapsulated in one remote interface named TicTacToe (Listing 36.7), and all the calls the server invokes can be defined in another interface named CallBack (Listing 36.8).

Listing 36.7. TicTacToeInterface.java
(This item is displayed on pages 1256 - 1257 in the print version)
 1   import   java.rmi.*; 2 3    public interface   TicTacToeInterface   extends   Remote{  4  /**  5  * Connect to the TicTacToe server and return the token.  6  * If the returned token is ' ', the client is not connected to  7  * the server  8  */  9    public char   connect(CallBack client)   throws   RemoteException;  10 

[Page 1257]
 11  /** A client invokes this method to notify the server of its move*/  12    public void   myMove(   int   row,   int   column,   char   token)  13    throws   RemoteException;  14 } 

Listing 36.8. CallBack.java
 1   import   java.rmi.*; 2 3    public interface   CallBack   extends   Remote  { 4  /** The server notifies the client for taking a turn */  5    public void   takeTurn(   boolean   turn)   throws   RemoteException;  6 7  /** The server sends a message to be displayed by the client */  8    public void   notify(java.lang.String message)  9    throws   RemoteException;  10 11  /** The server notifies a client of the other player's move */  12    public void   mark(   int   row,   int   column,   char   token)  13    throws   RemoteException;  14 } 

What does a client need to do? The client interacts with the player. Assume that all the cells are initially empty, and that the first player takes the X token and the second player takes the O token. To mark a cell, the player points the mouse to the cell and clicks it. If the cell is empty, the token (X or O) is displayed. If the cell is already filled, the player's action is ignored.

From the preceding description, it is obvious that a cell is a GUI object that handles mouse-click events and displays tokens. The candidate for such an object could be a button or a panel. Panels are more flexible than buttons . The token (X or O) can be drawn on a panel in any size , but it only can be displayed as a text on a button.

Let Cell be a subclass of JPanel . You can declare a grid to be an array Cell[][] cell = new Cell[3][3] for modeling the game. How do you know the state of a cell ( marked or not)? You can use a property named marked of the boolean type in the Cell class. How do you know whether the player has a turn? You can use a property named myTurn of boolean . This property (initially false ) can be set by the server through a callback.

The Cell class is responsible for drawing the token when an empty cell is clicked, so you need to write the code for listening to the MouseEvent and for painting the shape for tokens X and O. To determine which shape to draw, introduce a variable named marker of the char type. Since this variable is shared by all the cells in a client, it is preferable to declare it in the client and to declare the Cell class as an inner class of the client so that this variable will be accessible to all the cells.

Now let us turn our attention to the server side. What does the server need to do? The server needs to implement TicTacToeInterface and notify the clients of the game status. The server has to record the moves in the cells and check the status every time a player makes a move. The status information can be kept in a array of char . You can implement a method named isFull() to check whether the board is full and a method named isWon(token) to check whether a specific player has won.

Once a client is connected to the server, the server notifies the client which token to use; that is, X for the first client, and O for the second. Once a client notifies the server of its move, the server checks the game status and notifies the clients.

Now the most critical question is how the server notifies a client. You know that a client invokes a server method by creating a server stub on the client side. A server cannot directly invoke a client, because the client is not declared as a remote object. The CallBack interface was created to facilitate the server's callback to the client. In the implementation of CallBack , an instance of the client is passed as a parameter in the constructor of CallBack . The client creates an instance of CallBack and passes its stub to the server, using a remote method named connect() defined in the server. The server then invokes the client's method through a CallBack instance. The triangular relationship of client, CallBack implementation, and server is shown in Figure 36.8.


[Page 1258]
Figure 36.8. The server receives a CallBack stub from the client and invokes the remote methods defined in the CallBack interface, which can invoke the methods defined in the client.

Here are the steps to complete the example.

1.
Create TicTacToeImpl.java (Listing 36.9) to implement TicTacToeInterface . Add a main method in the program to register the server with the RMI.

Listing 36.9. TicTacToeImpl.java
(This item is displayed on pages 1258 - 1260 in the print version)
 1   import   java.rmi.*; 2   import   java.rmi.server.*; 3   import   java.rmi.registry.*; 4   import   java.rmi.registry.*; 5 6    public class   TicTacToeImpl   extends   UnicastRemoteObject  7    implements   TicTacToeInterface  { 8  // Declare two players, used to call players back  9   private   CallBack player1 =   null   ; 10   private   CallBack player2 =   null   ; 11 12  // board records players' moves  13   private char   [][] board =   new char   [   3   ][   3   ]; 14 15  /** Constructs TicTacToeImpl object and exports it on default port.  16  */  17   public   TicTacToeImpl()   throws   RemoteException { 18   super   (); 19 } 20 21  /** Constructs TicTacToeImpl object and exports it on specified  22  * port.  23  * @param port The port for exporting  24  */  

[Page 1259]
 25   public   TicTacToeImpl(   int   port)   throws   RemoteException { 26   super   (port); 27 } 28 29  /**  30  * Connect to the TicTacToe server and return the token.  31  * If the returned token is ' ', the client is not connected to  32  * the server  33  */  34    public char   connect(CallBack client)   throws   RemoteException  { 35   if   (player1 ==   null   ) { 36  // player1 (first player) registered  37 player1 = client; 38 player1.notify(   "Wait for a second player to join"   ); 39   return     'X'   ; 40 } 41   else if   (player2 ==   null   ) { 42  // player2 (second player) registered  43 player2 = client; 44 player2.notify(   "Wait for the first player to move"   ); 45 player2.takeTurn(   false   ); 46 player1.notify(   "It is my turn (X token)"   ); 47 player1.takeTurn(   true   ); 48   return     '0'   ; 49 } 50   else   { 51  // Already two players  52 client.notify(   "Two players are already in the game"   ); 53   return     ' '   ; 54 } 55 } 56 57  /** A client invokes this method to notify the server of its move*/  58    public void   myMove(   int   row,   int   column,   char   token)  59    throws   RemoteException  { 60  // Set token to the specified cell  61 board[row][column] = token; 62 63  // Notify the other player of the move  64   if   (token ==   'X'   ) 65 player2.mark(row, column,   'X'   ); 66   else   67 player1.mark(row, column,   '0'   ); 68 69  // Check if the player with this token wins  70   if   (isWon(token)) { 71   if   (token ==   'X'   ) { 72 player1.notify(   "I won!"   ); 73 player2.notify(   "I lost!"   ); 74 player1.takeTurn(   false   ); 75 } 76   else   { 77 player2.notify(   "I won!"   ); 78 player1.notify(   "I lost!"   ); 79 player2.takeTurn(   false   ); 80 } 81 } 82   else if   (isFull()) { 83 player1.notify(   "Draw!"   ); 84 player2.notify(   "Draw!"   ); 85 } 

[Page 1260]
 86   else if   (token ==   'X'   ) { 87 player1.notify(   "Wait for the second player to move"   ); 88 player1.takeTurn(   false   ); 89 player2.notify(   "It is my turn, (0 token)"   ); 90 player2.takeTurn(   true   ); 91 } 92   else if   (token ==   '0'   ) { 93 player2.notify(   "Wait for the first player to move"   ); 94 player2.takeTurn(   false   ); 95 player1.notify(   "It is my turn, (X token)"   ); 96 player1.takeTurn(   true   ); 97 } 98 } 99 100  /** Check if a player with the specified token wins */  101    public boolean   isWon(   char   token)  { 102   for   (   int   i =     ; i <   3   ; i++) 103   if   ((board[i][     ] == token) && (board[i][   1   ] == token) 104 && (board[i][   2   ] == token)) 105   return true   ; 106 107   for   (   int   j =     ; j <   3   ; j++) 108   if   ((board[     ][j] == token) && (board[   1   ][j] == token) 109 && (board[   2   ][j] == token)) 110   return true   ; 111 112   if   ((board[     ][     ] == token) && (board[   1   ][   1   ] == token) 113 && (board[   2   ][   2   ] == token)) 114   return true   ; 115 116   if   ((board[     ][   2   ] == token) && (board[   1   ][   1   ] == token) 117 && (board[   2   ][     ] == token)) 118   return true   ; 119 120   return false   ; 121 } 122 123  /** Check if the board is full */  124    public boolean   isFull()  { 125   for   (   int   i =     ; i <   3   ; i++) 126   for   (   int   j =     ; j <   3   ; j++) 127   if   (board[i][j] ==   '\u0000'   ) 128   return false   ; 129 130   return true   ; 131 } 132 133   public static void   main(String[] args) { 134   try   { 135  TicTacToeInterface obj =   new   TicTacToeImpl();  136  Registry registry = LocateRegistry.getRegistry();  137  registry.rebind(   "TicTacToeImpl"   , obj);  138 System.out.println(   "Server "   + obj +   " registered"   ); 139 } 140   catch   (Exception ex) { 141 ex.printStackTrace(); 142 } 143 } 144 } 


[Page 1261]
2.
Create CallBackImpl.java (Listing 36.10) to implement the CallBack interface.

Listing 36.10. CallBackImpl.java
 1   import   java.rmi.*; 2   import   java.rmi.server.*; 3 4    public class   CallBackImpl   extends   UnicastRemoteObject  5    implements   CallBack  { 6  // The client will be called by the server through callback  7   private   TicTacToeClientRMI thisClient; 8 9  /** Constructor */  10   public   CallBackImpl(Object client)   throws   RemoteException { 11 thisClient = (TicTacToeClientRMI)client; 12 } 13 14  /** The server notifies the client for taking a turn */  15    public void   takeTurn(   boolean   turn)   throws   RemoteException  { 16 thisClient.setMyTurn(turn); 17 } 18 19  /** The server sends a message to be displayed by the client */  20    public void   notify(String message)   throws   RemoteException  { 21 thisClient.setMessage(message); 22 } 23 24  /** The server notifies a client of the other player's move */  25    public void   mark(   int   row,   int   column,   char   token)  26    throws   RemoteException  { 27 thisClient.mark(row, column, token); 28 } 29 } 

3.
Create an applet TicTacToeClientRMI (Listing 36.11) for interacting with a player and communicating with the server. Enable it to run standalone.

Listing 36.11. TicTacToeClientRMI.java
(This item is displayed on pages 1261 - 1264 in the print version)
 1   import   java.rmi.*; 2   import   java.awt.*; 3   import   java.awt.event.*; 4   import   javax.swing.*; 5   import   javax.swing.border.*; 6   import   java.rmi.registry.Registry; 7   import   java.rmi.registry.LocateRegistry; 8 9    public class   TicTacToeClientRMI   extends   JApplet  { 10  // marker is used to indicate the token type  11   private char   marker; 12 13  // myTurn indicates whether the player can move now  14   private boolean   myTurn =   false   ; 15 16  // Each cell can be empty or marked as '0' or 'X'  17   private   Cell[][] cell; 18 19  // ticTacToe is the game server for coordinating with the players  20   private   TicTacToeInterface ticTacToe; 21 22  // Border for cells and panel  23   private   Border lineBorder = 24 BorderFactory.createLineBorder(Color.yellow,   1   ); 

[Page 1262]
 25 26   private   JLabel jlblStatus =   new   JLabel(   "jLabel1"   ); 27   private   JLabel jlblIdentification =   new   JLabel(); 28 29   boolean   isStandalone =   false   ; 30 31  /** Initialize the applet */  32   public void   init() { 33 JPanel jPanel1 =   new   JPanel(); 34 jPanel1.setBorder(lineBorder); 35 jPanel1.setLayout(   new   GridLayout(   3   ,   3   ,   1   ,   1   )); 36 37 add(jlblStatus, BorderLayout.SOUTH); 38 add(jPanel1, BorderLayout.CENTER); 39 add(jlblIdentification, BorderLayout.NORTH); 40 41  // Create cells and place cells in the panel  42 cell =   new   Cell[   3   ][   3   ]; 43   for   (   int   i =     ; i <   3   ; i++) 44   for   (   int   j =     ; j <   3   ; j++) 45 jPanel1.add(cell[i][j] =   new   Cell(i, j)); 46 47   try   { 48 initializeRMI(); 49 } 50   catch   (Exception ex) { 51 ex.printStackTrace(); 52 } 53 } 54 55  /** Initialize RMI */  56   protected boolean   initializeRMI()   throws   Exception { 57 String host =   ""   ; 58   if   (!isStandalone) host = getCodeBase().getHost(); 59 60   try   { 61  Registry registry = LocateRegistry.getRegistry(host);  62  ticTacToe = (TicTacToeInterface) registry.lookup(   "TicTacToeImpl"   );  63 System.out.println(   "Server object "   + ticTacToe +   " found"   ); 64 } 65   catch   (Exception ex) { 66 System.out.println(ex); 67 } 68 69  // Create callback for use by the server to control the client  70  CallBackImpl callBackControl =   new   CallBackImpl(   this   );  71 72   if   ( 73 (marker = ticTacToe.connect((CallBack)callBackControl)) !=   ' '   ) 74 { 75 System.out.println(   "connected as "   + marker +   " player."   ); 76 jlblIdentification.setText(   "You are player "   + marker); 77   return true   ; 78 } 79   else   { 80 System.out.println(   "already two players connected as "   ); 81   return false   ; 82 } 83 } 84 

[Page 1263]
 85  /** Set variable myTurn to true or false */  86    public void   setMyTurn(   boolean   myTurn)  { 87   this   .myTurn = myTurn; 88 } 89 90  /** Set message on the status label */  91    public void   setMessage(String message)  { 92 jlblStatus.setText(message); 93 } 94 95  /** Mark the specified cell using the token */  96    public void   mark(   int   row,   int   column,   char   token)  { 97 cell[row][column].setToken(token); 98 } 99 100  /** Inner class Cell for modeling a cell on the TicTacToe board */  101    private class   Cell   extends   JPanel  { 102  // marked indicates whether the cell has been used  103   private boolean   marked =   false   ; 104 105  // row and column indicate where the cell appears on the board  106   int   row, column; 107 108  // The token for the cell  109   private char   token; 110 111  /** Construct a cell */  112   public   Cell(   final int   row,   final int   column) { 113   this   .row = row; 114   this   .column = column; 115 addMouseListener(   new   MouseAdapter() { 116   public void   mouseClicked(MouseEvent e) { 117   if   (myTurn && !marked) { 118  // Mark the cell  119 setToken(marker); 120 121  // Notify the server of the move  122   try   { 123 ticTacToe.myMove(row, column, marker); 124 } 125   catch   (RemoteException ex) { 126 System.out.println(ex); 127 } 128 } 129 } 130 }); 131 132 setBorder(lineBorder); 133 } 134 135  /** Set token on a cell (mark a cell) */  136   public void   setToken(   char   c) { 137 token = c; 138 marked =   true   ; 139 repaint(); 140 } 141 142  /** Paint the cell to draw a shape for the token */  143   protected void   paintComponent(Graphics g) { 144   super   .paintComponent(g); 

[Page 1264]
 145 146  // Draw the border  147 g.drawRect(     ,     , getSize().width, getSize().height); 148 149   if   (token ==   'X'   ) { 150 g.drawLine(   10   ,   10   , getSize().width -   10   , 151 getSize().height -   10   ); 152 g.drawLine(getSize().width -   10   ,   10   ,   10   , 153 getSize().height -   10   ); 154 } 155   else if   (token ==   '0'   ) { 156 g.drawOval(   10   ,   10   , getSize().width -   20   , 157 getSize().height -   20   ); 158 } 159 } 160 } 161 162  /** Main method */  163   public static void   main(String[] args) { 164 TicTacToeClientRMI applet =   new   TicTacToeClientRMI(); 165  applet.isStandalone =   true;    166 applet.init(); 167 applet.start(); 168 JFrame frame =   new   JFrame(); 169 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 170 frame.setTitle(   "TicTacToeClientRMI"   ); 171 frame.add(applet, BorderLayout.CENTER); 172 frame.setSize(   400   ,   320   ); 173 frame.setVisible(   true   ); 174 } 175 } 

4.
Follow the steps below to run this example.

4.1. Start RMI Registry by typing " start rmiregistry " at a DOS prompt from the book directory.

4.2. Start the server TicTacToeImpl using the following command at C:\book directory:

  C:\book>java TicTacToeImpl  

4.3. Run the client TicTacToeClientRMI as an application or an applet. A sample run is shown in Figure 36.9.

Figure 36.9. Two players play each other through the RMI server.

TicTacToeInterface defines two remote methods, connect(CallBack client) and myMove(int row, int column, char token) . The connect method plays two roles: one is to pass a CallBack stub to the server, and the other is to let the server assign a token for the player. The myMove method notifies the server that the player has made a specific move.


[Page 1265]

The CallBack interface defines three remote methods, takeTurn(boolean turn) , notify(String message) , and mark(int row, int column, char token) . The takeTurn method sets the client's myTurn property to true or false . The notify method displays a message on the client's status label. The mark method marks the client's cell with the token at the specified location.

TicTacToeImpl is a server implementation for coordinating with the clients and managing the game. The variables player1 and player2 are instances of CallBack , each of which corresponds to a client, passed from a client when the client invokes the connect method. The variable board records the moves by the two players. This information is needed to determine the game status. When a client invokes the connect method, the server assigns a token X for the first player and O for the second player, and only two players are accepted by the server. You can modify the program to accept additional clients as observers. See Exercise 36.7 for more details.

Once two players are in the game, the server coordinates the turns between them. When a client invokes the myMove method, the server records the move and notifies the other player by marking the other player's cell. It then checks to see whether the player wins or whether the board is full. If neither conditions applies and therefore the game continues, the server gives a turn to the other player.

The CallBackImpl implements the CallBack interface. It creates an instance of TicTacToeClientRMI through its constructor. The CallBackImpl relays the server request to the client by invoking the client's methods. When the server invokes the takeTurn method, CallBackImpl invokes the client's setMyTurn() method to set the property myTurn in the client. When the server invokes the notify() method, CallBackImpl invokes the client's setMessage() method to set the message on the client's status label. When the server invokes the mark method, CallBackImpl invokes the client's mark method to mark the specified cell.

TicTacToeClientRMI can run as a standalone application or as an applet. The initializeRMI method is responsible for creating the URL for running as a standalone application or as an applet, for locating the TicTacToeImpl server stub, for creating the CallBack server object, and for connecting the client with the server.

Interestingly, obtaining the TicTacToeImpl stub for the client is different from obtaining the CallBack stub for the server. The TicTacToeImpl stub is obtained by invoking the lookup() method through the RMI registry, and the CallBack stub is passed to the server through the connect method in the TicTacToeImpl stub. It is a common practice to obtain the first stub with the lookup method, but to pass the subsequent stubs as parameters through remote method invocations.

Since the variables myTurn and marker are defined in TicTacToeClientRMI , the Cell class is defined as an inner class within TicTacToeClientRMI in order to enable all the cells in the client to access them. Exercise 36.7 suggests alternative approaches that implement the Cell as a non-inner class.

 


Introduction to Java Programming-Comprehensive Version
Introduction to Java Programming-Comprehensive Version (6th Edition)
ISBN: B000ONFLUM
EAN: N/A
Year: 2004
Pages: 503

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net