Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

AhmadLang / Java, How To Program, 2004

.pdf
Скачиваний:
626
Добавлен:
31.05.2015
Размер:
51.82 Mб
Скачать

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();

39

 

40

// create MessageReceiver for receiving messages from client

41

serverExecutor.execute(

42

new MessageReceiver( this, clientSocket ) );

43

 

44

// print connection information

45

System.out.println( "Connection received from: " +

46

clientSocket.getInetAddress() );

47} // end while

48} // end try

49catch ( IOException ioException )

50{

51ioException.printStackTrace();

52} // end catch

53} // end method startServer

55// when new message is received, broadcast message to clients

56public void messageReceived( String from, String message )

57{

58// create String containing entire message

59String completeMessage = from + MESSAGE_SEPARATOR + message;

61// create and start MulticastSender to broadcast messages

62serverExecutor.execute(

63new MulticastSender( completeMessage.getBytes() ) );

64} // end method messageReceived

65} // end class DeitelMessengerServer

Lines 3547 listen continuously for new client connections. Line 38 invokes ServerSocket method accept to wait for and accept a new client connection. Lines 4142 create and start a new MessageReceiver for the

client. Class MessageReceiver (Fig. 24.22) of package com.deitel.messenger.sockets.server implements

Runnable and listens for incoming messages from a client. The first argument to the MessageReceiver constructor is a MessageListener (Fig. 24.21), to which messages from the client should be delivered. Class

DeitelMessengerServer implements interface MessageListener (line 15) of package com.deitel.messenger and therefore can pass the this reference to the MessageReceiver constructor.

When each MessageReceiver receives a new message from a client, the MessageReceiver passes the message to a MessageListener through method messageReceived (lines 5664). Line 59 concatenates the from string with the separator >>> and the message body. Lines 6263 create and start a new

MulticastSender to deliver completeMessage to all clients. Class MulticastSender (Fig. 24.23) of package com.deitel.messenger.sockets.server uses multicasting as an efficient mechanism for sending one message to multiple clients. We discuss the details of multicasting shortly. Method main (lines 711 of Fig. 24.19) creates a new DeitelMessengerServer instance and starts the server.

[Page 1157]

Figure 24.19. Test class for DeitelMessengerServer.

1// Fig. 24.19: DeitelMessengerServerTest.java

2// Test the DeitelMessengerServer class.

3package com.deitel.messenger.sockets.server;

5public class DeitelMessengerServerTest

6{

7public static void main ( String args[] )

8{

9DeitelMessengerServer application = new DeitelMessengerServer();

10application.startServer(); // start server

11} // end main

12} // end class DeitelMessengerServerTest

Server listening on port 12345 ...

Connection received from: /127.0.0.1 Connection received from: /127.0.0.1

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.

Figure 24.21. MessageListener interface that declares method messageReceived for receiving new chat messages.

1 // Fig. 24.21: MessageListener.java

2 // MessageListener is an interface for classes that wish to

3// receive new chat messages.

4package com.deitel.messenger;

6public interface MessageListener

7{

8// receive new chat message

9public void messageReceived( String from, String message );

10} // end interface MessageListener

DeitelMessengerServer uses instances of class MessageReceiver (Fig. 24.22) from package com.deitel.messenger.sockets.server to listen for new messages from each client. Class MessageReceiver implements interface Runnable. This enables DeitelMessengerServer to create an object of class MessageReceiver to run in a separate thread for each client, so that messages from multiple clients can be handled concurrently. When DeitelMessengerServer receives a new client connection, DeitelMessengerServer creates a new MessageReceiver for the client, then continues listening for new client connections. The MessageReceiver listens for messages from a single client and passes them back to

the DeitelMessengerServer through method messageReceived.

[Page 1161]

Figure 24.22. MessageReceiver for listening for new messages from DeitelMessengerServer clients in separate threads.

(This item is displayed on pages 1159 - 1161 in the print version)

1

// Fig.

24.22: MessageReceiver.java

 

2

//

MessageReceiver is a Runnable that listens

for messages from a

3

//

particular client and delivers messages to

a MessageListener.

4

package

com.deitel.messenger.sockets.server;

 

5

 

 

 

 

6import java.io.BufferedReader;

7import java.io.IOException;

8import java.io.InputStreamReader;

9import java.net.Socket;

10import java.net.SocketTimeoutException;

11import java.util.StringTokenizer;

12

13import com.deitel.messenger.MessageListener;

14import static com.deitel.messenger.sockets.SocketMessengerConstants.*;

16public class MessageReceiver implements Runnable

17{

18private BufferedReader input; // input stream

19private MessageListener messageListener; // message listener

20private boolean keepListening = true; // when false, ends runnable

22// MessageReceiver constructor

23public MessageReceiver( MessageListener listener, Socket clientSocket )

24{

25// set listener to which new messages should be sent

26messageListener = listener;

27

28try

29{

30// set timeout for reading from client

31clientSocket.setSoTimeout( 5000 ); // five seconds

33// create BufferedReader for reading incoming messages

34input = new BufferedReader( new InputStreamReader(

35

clientSocket.getInputStream() ) );

36

} // end try

37catch ( IOException ioException )

38{

39ioException.printStackTrace();

40} // end catch

41} // end MessageReceiver constructor

43// listen for new messages and deliver them to MessageListener

44public void run()

45{

46String message; // String for incoming messages

48// listen for messages until stopped

49while ( keepListening )

50{

51try

52{

53

message = input.readLine(); // read message from client

54} // end try

55catch ( SocketTimeoutException socketTimeoutException )

56{

57

continue; // continue to next iteration to keep listening

58} // end catch

59catch ( IOException ioException )

60{

61

ioException.printStackTrace();

62

break;

63

} // end catch

64

 

65// ensure non-null message

66if ( message != null )

67{

68

// tokenize message to retrieve user name and message body

69

StringTokenizer tokenizer = new StringTokenizer(

70

 

message, MESSAGE_SEPARATOR );

71

 

 

72

// ignore messages that do not contain a user

73

// name and message body

74

if

( tokenizer.countTokens() == 2 )

75

{

 

76

 

// send message to MessageListener

77

 

messageListener.messageReceived(

78

 

tokenizer.nextToken(), // user name

79

 

tokenizer.nextToken() ); // message body

80

}

// end if

81

else

82

{

 

83

 

// if disconnect message received, stop listening

84

 

if ( message.equalsIgnoreCase(

85

 

MESSAGE_SEPARATOR + DISCONNECT_STRING ) )

86

 

stopListening();

87

}

// end else

88} // end if

89} // end while

91try

92{

93input.close(); // close BufferedReader (also closes Socket)

94} // end try

95catch ( IOException ioException )

96{

97ioException.printStackTrace();

98} // end catch

99} // end method run

100

101// stop listening for incoming messages

102public void stopListening()

103{

104keepListening = false;

105} // end method stopListening

106} // end class MessageReceiver

The MessageReceiver constructor (lines 2341) takes a MessageListener as its first argument. The