AhmadLang / Java, How To Program, 2004
.pdf
263 } // end class TicTacToeClient
Figure 24.16. Test class for Tic-Tac-Toe client.
(This item is displayed on page 1153 in the print version)
1// Fig. 24.16: TicTacToeClientTest.java
2// Tests the TicTacToeClient class.
3import javax.swing.JFrame;
4
5public class TicTacToeClientTest
6{
7public static void main( String args[] )
8{
9TicTacToeClient application; // declare client application
11// if no command line args
12if ( args.length == 0 )
13application = new TicTacToeClient( "127.0.0.1" ); // localhost
14else
15application = new TicTacToeClient( args[ 0 ] ); // use args
16
17application.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
18} // end main
19} // end class TicTacToeClientTest
Figure 24.17. Sample outputs from the client/server Tic-Tac-Toe program.
(This item is displayed on pages 1153 - 1154 in the print version)
[View full size image]
TicTacToeServer Class
As the TicTacToeServer receives each client connection, it creates an instance of inner-class Player (lines 182301 of Fig. 24.13) to process the client in a separate thread. These threads enable the clients to play the game independently. The first client to connect to the server is player X and the second is player O.
Player X makes the first move. The server maintains the information about the board so it can determine whether a player's move is valid or invalid.
[Page 1145]
We begin with a discussion of the server side of the Tic-Tac-Toe game. When the TicTacToeServer application executes, the main method (lines 712 of Fig. 24.14) creates a TicTacToeServer object called application. The constructor (lines 3469 of Fig. 24.13) attempts to set up a ServerSocket. If successful, the program displays the server window, then main invokes the TicTacToeServer method execute (lines 72100). Method execute loops twice, blocking at line 79 each time while waiting for a client connection. When a client connects, line 79 creates a new Player object to manage the connection as a separate thread, and line 80 executes the Player in the runGame thread pool.
[Page 1146]
When the TicTacToeServer creates a Player, the Player constructor (lines 192208) receives the Socket object representing the connection to the client and gets the associated input and output streams. Line 201 creates a Formatter (see Chapter 28) by wrapping it around the output stream of the socket. The
Player's run method (lines 219297) controls the information that is sent to and received from the client. First, it passes to the client the character that the client will place on the board when a move is made (line 225). Line 226 calls Formatter method flush to force this output to the client. Line 241 suspends player X's thread as it starts executing, because player X can move only after player O connects.
After player O connects, the game can be played, and the run method begins executing its while statement (lines 264283). Each iteration of this loop reads an integer (line 269) representing the location where the client wants to place a mark, and line 272 invokes the TicTacToeServer method validateAndMove (declared at lines 118163) to check the move. If the move is valid, line 275 sends a message to the client to this effect. If not, line 280 sends a message indicating that the move was invalid. The program maintains board locations as numbers from 0 to 8 (0 through 2 for the first row, 3 through 5 for the second row and 6 through 8 for the third row).
[Page 1147]
Method validateAndMove (lines 118163 in class TicTacToeServer) allows only one player at a time to move, thereby preventing them from modifying the state information of the game simultaneously. If the Player attempting to validate a move is not the current player (i.e., the one allowed to make a move), it is placed in a wait state until its turn to move. If the position for the move being validated is already occupied on the board, validMove returns false. Otherwise, the server places a mark for the player in its local representation of the board (line 142), notifies the other Player object (line 146) that a move has been made (so that the client can be sent a message), invokes method signal (line 152) so that the waiting Player (if there is one) can validate a move and returns true (line 159) to indicate that the move is valid.
TicTacToeClient Class
Each TicTacToeClient application (Fig. 24.15) maintains its own GUI version of the Tic-Tac-Toe board on which it displays the state of the game. The clients can place a mark only in an empty square on the board. Inner class Square (lines 205262 of Fig. 24.15) implements each of the nine squares on the board. When a TicTacToeClient begins execution, it creates a JTextArea in which messages from the server and a representation of the board using nine Square objects are displayed. The startClient method (lines 80100) opens a connection to the server and gets the associated input and output streams from the Socket object. Lines 8586 make a connection to the server. Class TicTacToeClient implements interface Runnable so that a separate thread can read messages from the server. This approach enables the user to interact with the board (in the event-dispatch thread) while waiting for messages from the server. After establishing the connection to the server, line 99 executes the client with the worker ExecutorService. The run method (lines 103124) controls the separate thread of execution. The method first reads the mark character (X or O) from the server (line 105), then loops continuously (lines 121125) and reads messages
from the server (line 124). Each message is passed to the processMessage method (lines 129156) for processing.
[Page 1152]
If the message received is "Valid move.", lines 134135 display the message "Valid move, please wait." and call method setMark (lines 173184) to set the client's mark in the current square (the one in which the user clicked) using SwingUtilities method invokeLater to ensure that the GUI updates occur in the event-dispatch thread. If the message received is "Invalid move, try again.", line 139 displays the message so that the user can click a different square. If the message received is "Opponent moved.", line 145 reads an integer from the server indicating where the opponent moved, and lines 149150 place a mark in that square of the board (again using SwingUtilities method invokeLater to ensure that the GUI updates occur in the event-dispatch thread). If any other message is received, line 155 simply displays the message. Figure 24.17 shows sample screen captures of two applications interacting via the
TicTacToeServer.
[Page 1154]
24.9. Security and the Network
As much as we look forward to writing a great variety of powerful network-based applications, our efforts may be limited because of security concerns. Many Web browsers, such as Mozilla and Microsoft Internet Explorer, by default prohibit Java applets from doing file processing on the machines on which they execute. Think about it. A Java applet is designed to be sent to your browser via an HTML document that could be downloaded from any Web server in the world. Often you will know very little about the sources of Java applets that will execute on your system. To allow these applets free rein with your files could be disastrous.
A more subtle situation occurs with limiting the machines to which executing applets can make network connections. To build truly collaborative applications, we would ideally like to have our applets communicate with machines almost anywhere. The Java security manager in a Web browser often restricts an applet so that it can communicate only with the machine from which it was originally downloaded.
These restrictions may seem too strict. However, the Java Security API now provides capabilities for digitally signed applets that will enable browsers to determine whether an applet is downloaded from a trusted source. A trusted applet can be given additional access to the computer on which it is executing. The features of the Java Security API and additional networking capabilities are discussed in our text Advanced Java 2 Platform How to Program.
[Page 1155]
24.10. Case Study: DeitelMessenger Server and Client
Chat rooms have become common on the Internet. They provide a central location where users can chat with each other via short text messages. Each participant can see all messages that the other users post, and each user can post messages. This section presents our capstone networking case study, which integrates many of the Java networking, multithreading and Swing GUI features we have learned thus far to build an online chat system. We also introduce multicasting, which enables an application to send DatagramPackets to groups of clients. After reading this section, you will be able to build more significant networking applications.
24.10.1. DeitelMessengerServer and Supporting Classes
DeitelMessengerServer (Fig. 24.18) is the heart of the online chat system. This class appears in package com.deitel.messenger.sockets.server. Chat clients can participate in a chat by connecting to the
DeitelMessengerServer. Method startServer (lines 2053) launches DeitelMessengerServer. Lines 2829 create a ServerSocket to accept incoming network connections. Recall that the ServerSocket constructor takes as its first argument the port on which the server should listen for incoming connections. Interface SocketMessengerConstants (Fig. 24.20) declares the port number as the constant SERVER_PORT to ensure that the server and the clients use the correct port number.
Figure 24.18. DeitelMessengerServer for managing a chat room.
(This item is displayed on pages 1155 - 1156 in the print version)
1 // Fig. 24.18: DeitelMessengerServer.java
2 // DeitelMessengerServer is a multi-threaded, socketand
3// packet-based chat server.
4package com.deitel.messenger.sockets.server;
6import java.net.ServerSocket;
7import java.net.Socket;
8import java.io.IOException;
9import java.util.concurrent.Executors;
10import java.util.concurrent.ExecutorService;
12import com.deitel.messenger.MessageListener;
13import static com.deitel.messenger.sockets.SocketMessengerConstants.*;
15public class DeitelMessengerServer implements MessageListener
16{
17private ExecutorService serverExecutor; // executor for server
19// start chat server
20public void startServer()
21{
22// create executor for server runnables
23serverExecutor = Executors.newCachedThreadPool();
25try // create server and manage new clients
26{
27// create ServerSocket for incoming connections
28ServerSocket serverSocket =
29 |
new ServerSocket( SERVER_PORT, 100 ); |
30 |
|
31 |
System.out.printf( "%s%d%s", "Server listening on port ", |
32 |
SERVER_PORT, " ..." ); |
33 |
|
34// listen for clients constantly
35while ( true )
36{
37 |
// accept new client connection |
38 |
Socket clientSocket = serverSocket.accept(); |
Connection received from: /127.0.0.1
Interface SocketMessengerConstants (Fig. 24.20) declares constants for use in the various classes that make up the Deitel messenger system. Classes can access these static constants by using a static import as shown in Fig. 24.22.
Figure 24.20. SocketMessengerConstants declares constants for use in the
DeitelMessengerServer and DeitelMessenger.
1 // Fig. 24.20: SocketMessengerConstants.java
2 // SocketMessengerConstants defines constants for the port numbers
3// and multicast address in DeitelMessenger
4package com.deitel.messenger.sockets;
5
6public interface SocketMessengerConstants
7{
8// address for multicast datagrams
9public static final String MULTICAST_ADDRESS = "239.0.0.1";
11// port for listening for multicast datagrams
12public static final int MULTICAST_LISTENING_PORT = 5555;
14// port for sending multicast datagrams
15public static final int MULTICAST_SENDING_PORT = 5554;
17// port for Socket connections to DeitelMessengerServer
18public static final int SERVER_PORT = 12345;
20// String that indicates disconnect
21public static final String DISCONNECT_STRING = "DISCONNECT";
23// String that separates the user name from the message body
24public static final String MESSAGE_SEPARATOR = ">>>";
26// message size (in bytes)
27public static final int MESSAGE_SIZE = 512;
28} // end interface SocketMessengerConstants
[Page 1158]
Line 9 declares the String constant MULTICAST_ADDRESS, which contains the address to which a MulticastSender (Fig. 24.23) should send messages. This address is one of the addresses reserved for multicast, which we describe in the discussion of Fig. 24.23. Line 12 declares the integer constant MULTICAST_LISTENING_PORTthe port on which clients should listen for new messages. Line 15 declares the integer constant MULTICAST_SENDING_PORTthe port to which a MulticastSender should post new messages at the MULTICAST_ADDRESS. Line 18 declares the integer constant SERVER_PORTthe port on which DeitelMessengerServer listens for incoming client connections. Line 21 declares String constant
DISCONNECT_STRING, which is the String that a client sends to DeitelMessengerServer when the user wishes to leave the chat room. Line 24 declares String constant MESSAGE_SEPARATOR, which separates the user name from the message body. Line 27 specifies the maximum message size in bytes.
Many different classes in the Deitel messenger system receive messages. For example, DeitelMessengerServer receives messages from clients and delivers them to all chat room participants. As we will see, the user interface for each client also receives messages and displays them to the users. Each class that receives messages implements interface MessageListener (Fig. 24.21). The interface (from package com.deitel.messenger) declares method messageReceived, which allows an implementing class to receive chat messages. Method messageReceived takes two string arguments representing the name of the sender and the message body, respectively.
