- •Contents
- •Preface
- •Introduction to Computers, the Internet and the Web
- •1.3 Computer Organization
- •Languages
- •1.9 Java Class Libraries
- •1.12 The Internet and the World Wide Web
- •1.14 General Notes about Java and This Book
- •Sections
- •Introduction to Java Applications
- •2.4 Displaying Text in a Dialog Box
- •2.5 Another Java Application: Adding Integers
- •2.8 Decision Making: Equality and Relational Operators
- •Introduction to Java Applets
- •3.2 Sample Applets from the Java 2 Software Development Kit
- •3.3 A Simple Java Applet: Drawing a String
- •3.4 Two More Simple Applets: Drawing Strings and Lines
- •3.6 Viewing Applets in a Web Browser
- •3.7 Java Applet Internet and World Wide Web Resources
- •Repetition)
- •Class Attributes
- •5.8 Labeled break and continue Statements
- •5.9 Logical Operators
- •Methods
- •6.2 Program Modules in Java
- •6.7 Java API Packages
- •6.13 Example Using Recursion: The Fibonacci Series
- •6.16 Methods of Class JApplet
- •Class Operations
- •Arrays
- •7.6 Passing Arrays to Methods
- •7.8 Searching Arrays: Linear Search and Binary Search
- •Collaboration Among Objects
- •8.2 Implementing a Time Abstract Data Type with a Class
- •8.3 Class Scope
- •8.4 Controlling Access to Members
- •8.5 Creating Packages
- •8.7 Using Overloaded Constructors
- •8.9 Software Reusability
- •8.10 Final Instance Variables
- •Classes
- •8.16 Data Abstraction and Encapsulation
- •9.2 Superclasses and Subclasses
- •9.5 Constructors and Finalizers in Subclasses
- •Conversion
- •9.11 Type Fields and switch Statements
- •9.14 Abstract Superclasses and Concrete Classes
- •9.17 New Classes and Dynamic Binding
- •9.18 Case Study: Inheriting Interface and Implementation
- •9.19 Case Study: Creating and Using Interfaces
- •9.21 Notes on Inner Class Definitions
- •Strings and Characters
- •10.2 Fundamentals of Characters and Strings
- •10.21 Card Shuffling and Dealing Simulation
- •Handling
- •Graphics and Java2D
- •11.2 Graphics Contexts and Graphics Objects
- •11.5 Drawing Lines, Rectangles and Ovals
- •11.9 Java2D Shapes
- •12.12 Adapter Classes
- •Cases
- •13.3 Creating a Customized Subclass of JPanel
- •Applications
- •Controller
- •Exception Handling
- •14.6 Throwing an Exception
- •14.7 Catching an Exception
- •Multithreading
- •15.3 Thread States: Life Cycle of a Thread
- •15.4 Thread Priorities and Thread Scheduling
- •15.5 Thread Synchronization
- •15.9 Daemon Threads
- •Multithreading
- •Design Patterns
- •Files and Streams
- •16.2 Data Hierarchy
- •16.3 Files and Streams
- •Networking
- •17.2 Manipulating URIs
- •17.3 Reading a File on a Web Server
- •17.4 Establishing a Simple Server Using Stream Sockets
- •17.5 Establishing a Simple Client Using Stream Sockets
- •17.9 Security and the Network
- •18.2 Loading, Displaying and Scaling Images
- •18.3 Animating a Series of Images
- •18.5 Image Maps
- •18.6 Loading and Playing Audio Clips
- •18.7 Internet and World Wide Web Resources
- •Data Structures
- •19.4 Linked Lists
- •20.8 Bit Manipulation and the Bitwise Operators
- •Collections
- •21.8 Maps
- •21.9 Synchronization Wrappers
- •21.10 Unmodifiable Wrappers
- •22.2 Playing Media
- •22.3 Formatting and Saving Captured Media
- •22.5 Java Sound
- •22.8 Internet and World Wide Web Resources
- •Hexadecimal Numbers
990 |
Networking |
Chapter 17 |
59). Lines 56–57 use HyperlinkEvent method getEventType to determine the type of the HyperlinkEvent. Class HyperlinkEvent contains public inner class
EventType that defines three hyperlink event types: ACTIVATED (the user clicked a hyperlink to change Web pages), ENTERED (the user moved the mouse over a hyperlink) and EXITED (the user moved the mouse away from a hyperlink). If a hyperlink was ACTIVATED, line 58 uses HyperlinkEvent method getURL to obtain the URL represented by the hyperlink. Method toString converts the returned URL to a String format that can be passed to utility method getThePage.
Software Engineering Observation 17.1
A JEditorPane generates HyperlinkEvents only if it is uneditable.
17.4 Establishing a Simple Server Using Stream Sockets
The two examples discussed so far use high-level Java networking capabilities to communicate between applications. In those examples, it was not the Java programmer’s responsibility to establish the connection between a client and a server. The first program relied on the Web browser to communicate with a Web server. The second program relied on a JEditorPane to perform the connection. This section begins our discussion of creating your own applications that can communicate with one another.
Establishing a simple server in Java requires five steps. Step 1 is to create a ServerSocket object. A call to the ServerSocket constructor such as
ServerSocket server = new ServerSocket( port, queueLength );
registers an available port number and specifies a maximum number of clients that can wait to connect to the server (i.e., the queueLength). The port number is used by clients to located the server application on the server computer. This often is called the handshake point. If the queue is full, the server refuses client connections. The preceding statement establishes the port where the server waits for connections from clients (a process known as binding the server to the port). Each client will ask to connect to the server on this port.
Programs manage each client connection with a Socket object. After binding the server to a port with a ServerSocket (Step 2), the server listens indefinitely (or blocks) for an attempt by a client to connect. To listen for a client, the program calls ServerSocket method accept, as in
Socket connection = server.accept();
This statement returns a Socket object when a connection with a client is established. Step 3 is to get the OutputStream and InputStream objects that enable the
server to communicate with the client by sending and receiving bytes. The server sends information to the client via an OutputStream object. The server receives information from the client via an InputStream object. To obtain the streams, the server invokes method getOutputStream on the Socket to get a reference to the OutputStream associated with the Socket and invokes method getInputStream on the Socket to get a reference to the InputStream associated with the Socket.
The OutputStream and InputStream objects can be used to send or receive individual bytes or sets of bytes with the OutputStream method write and the InputStream method read, respectively. Often it is useful to send or receive values of primitive
Chapter 17 |
Networking |
991 |
data types (such as int and double) or Serializable class data types (such as String) rather than sending bytes. In this case, we can use the techniques of Chapter 16 to chain other stream types (such as ObjectOutputStream and ObjectInputStream) to the OutputStream and InputStream associated with the Socket. For example,
ObjectInputStream input =
new ObjectInputStream( connection.getInputStream() );
ObjectOutputStream output =
new ObjectOutputStream( connection.getOutputStream() );
The beauty of establishing these relationships is that whatever the server writes to the ObjectOutputStream is sent via the OutputStream and is available at the client’s InputStream and whatever the client writes to its OutputStream (with a corresponding ObjectOutputStream) is available via the server’s InputStream.
Step 4 is the processing phase, in which the server and the client communicate via the InputStream and OutputStream objects. In Step 5, when the transmission is complete, the server closes the connection by invoking the close method on the Socket and on the corresponding streams.
Software Engineering Observation 17.2
With sockets, network I/O appears to Java programs to be identical to sequential file I/O. Sockets hide much of the complexity of network programming from the programmer.
Software Engineering Observation 17.3
With Java’s multithreading, we can create multithreaded servers that can manage many si- multaneous connections with many clients; this multithreaded-server architecture is precisely what popular network servers use.
Software Engineering Observation 17.4
A multithreaded server can take the Socket returned by each call to accept and create a new thread that manages network I/O across that Socket, or a multithreaded server can maintain a pool of threads (a set of already existing threads) ready to manage network I/O across the new Sockets as they are created.
Performance Tip 17.2
In high-performance systems in which memory is abundant, a multithreaded server can be implemented to create a pool of threads that can be assigned quickly to handle network I/O across each new Socket as it is created. Thus, when the server receives a connection, the server need not incur the overhead of thread creation.
17.5 Establishing a Simple Client Using Stream Sockets
Establishing a simple client in Java requires four steps. In Step 1, we create a Socket to connect to the server. The Socket constructor establishes the connection to the server. For example, the statement
Socket connection = new Socket( serverAddress, port );
uses the Socket constructor with two arguments—the server’s Internet address (serverAddress) and the port number. If the connection attempt is successful, this statement returns
992 |
Networking |
Chapter 17 |
a Socket. A connection attempt that fails throws an instance of a subclass of IOException, so many programs simply catch IOException. An UnknownHostException occurs when a server address indicated by a client cannot be resolved. A ConnectException is thrown when an error occurs while attempting to connect to a server.
In Step 2, the client uses Socket methods getInputStream and getOutputStream to obtain references to the Socket’s InputStream and OutputStream. As we mentioned in the preceding section, often it is useful to send or receive values of primitive data types (such as int and double) or class data types (such as String and Employee) rather than sending bytes. If the server is sending information in the form of actual data types, the client should receive the information in the same format. Thus, if the server sends values with an ObjectOutputStream, the client should read those values with an ObjectInputStream.
Step 3 is the processing phase in which the client and the server communicate via the InputStream and OutputStream objects. In Step 4, the client closes the connection when the transmission is complete by invoking the close method on the Socket and the corresponding streams. When processing information sent by a server, the client must determine when the server is finished sending information so the client can call close to close the Socket connection. For example, the InputStream method read returns the value –1 when it detects end-of-stream (also called EOF—end-of-file). If an ObjectInputStream is used to read information from the server, an EOFException occurs when the client attempts to read a value from a stream on which end-of-stream is detected.
17.6 Client/Server Interaction with Stream Socket Connections
The applications of Fig. 17.4 and Fig. 17.5 use stream sockets to demonstrate a simple client/server chat application. The server waits for a client connection attempt. When a client application connects to the server, the server application sends a String object (remember that Strings are Serializable) indicating that the connection was successful to the client. Then the client displays the message. Both the client and the server applications contain JTextFields, which allow the user to type a message and send it to the other application. When the client or the server sends the String “TERMINATE”, the connection between the client and the server terminates. Then the server waits for the next client to connect. The definition of class Server appears in Fig. 17.4. The definition of class Client appears in Fig. 17.5. The screen captures showing the execution between the client and the server are shown as part of Fig. 17.5.
Class Server’s constructor (lines 25–58) creates the GUI of the application (a JTextField and a JTextArea). The Server object displays its output in a JTextArea. When the main method (lines 186–194) executes, it creates an instance of class Server, specifies the window’s default close operation and calls method runServer (defined at lines 61–97).
Method runServer does the work of setting up the server to receive a connection and processing the connection when it occurs. The method creates a ServerSocket called server (line 68) to wait for connections. The ServerSocket is set up to listen for a connection from a client at port 5000. The second argument to the constructor is the number of connections that can wait in a queue to connect to the server (100 in this example). If the queue is full when a client attempts to connect, the server refuses the connection.
Chapter 17 |
Networking |
993 |
1// Fig. 17.4: Server.java
2 // Set up a Server that will receive a connection 3 // from a client, send a string to the client,
4 // and close the connection.
5
6 // Java core packages
7 import java.io.*;
8 import java.net.*;
9import java.awt.*;
10 import java.awt.event.*;
11
12// Java extension packages
13import javax.swing.*;
14
15public class Server extends JFrame {
16private JTextField enterField;
17private JTextArea displayArea;
18private ObjectOutputStream output;
19private ObjectInputStream input;
20private ServerSocket server;
21private Socket connection;
22private int counter = 1;
23
24// set up GUI
25public Server()
26{
27super( "Server" );
29 Container container = getContentPane();
30
31// create enterField and register listener
32enterField = new JTextField();
33enterField.setEnabled( false );
34 |
|
35 |
enterField.addActionListener( |
36 |
|
37 |
new ActionListener() { |
38 |
|
39 |
// send message to client |
40 |
public void actionPerformed( ActionEvent event ) |
41 |
{ |
42 |
sendData( event.getActionCommand() ); |
43 |
} |
44 |
|
45 |
} // end anonymous inner class |
46 |
|
47 |
); // end call to addActionListener |
48 |
|
49 |
container.add( enterField, BorderLayout.NORTH ); |
50 |
|
51// create displayArea
52displayArea = new JTextArea();
Fig. 17.4 Server portion of a client/server stream-socket connection (part 1 of 4).
994 |
Networking |
Chapter 17 |
53 container.add( new JScrollPane( displayArea ),
54 BorderLayout.CENTER );
55
56setSize( 300, 150 );
57setVisible( true );
58}
59
60// set up and run server
61public void runServer()
62{
63// set up server to receive connections;
64// process connections
65try {
66 |
|
67 |
// Step 1: Create a ServerSocket. |
68 |
server = new ServerSocket( 5000, 100 ); |
69 |
|
70 |
while ( true ) { |
71 |
|
72 |
// Step 2: Wait for a connection. |
73 |
waitForConnection(); |
74 |
|
75 |
// Step 3: Get input and output streams. |
76 |
getStreams(); |
77 |
|
78 |
// Step 4: Process connection. |
79 |
processConnection(); |
80 |
|
81 |
// Step 5: Close connection. |
82 |
closeConnection(); |
83 |
|
84 |
++counter; |
85}
86}
87
88// process EOFException when client closes connection
89catch ( EOFException eofException ) {
90System.out.println( "Client terminated connection" );
91}
92
93// process problems with I/O
94catch ( IOException ioException ) {
95ioException.printStackTrace();
96}
97}
98
99 // wait for connection to arrive, then display connection info
100private void waitForConnection() throws IOException
101{
102displayArea.setText( "Waiting for connection\n" );
104// allow server to accept a connection
105connection = server.accept();
Fig. 17.4 Server portion of a client/server stream-socket connection (part 2 of 4).
Chapter 17 |
Networking |
995 |
106
107 displayArea.append( "Connection " + counter +
108 " received from: " +
109connection.getInetAddress().getHostName() );
110}
111
112// get streams to send and receive data
113private void getStreams() throws IOException
114{
115// set up output stream for objects
116output = new ObjectOutputStream(
117 |
connection.getOutputStream() ); |
118 |
|
119// flush output buffer to send header information
120output.flush();
121
122// set up input stream for objects
123input = new ObjectInputStream(
124 |
connection.getInputStream() ); |
125 |
|
126displayArea.append( "\nGot I/O streams\n" );
127}
128
129// process connection with client
130private void processConnection() throws IOException
131{
132// send connection successful message to client
133String message = "SERVER>>> Connection successful";
134output.writeObject( message );
135output.flush();
136
137// enable enterField so server user can send messages
138enterField.setEnabled( true );
139
140// process messages sent from client
141do {
142 |
|
143 |
// read message and display it |
144 |
try { |
145 |
message = ( String ) input.readObject(); |
146 |
displayArea.append( "\n" + message ); |
147 |
displayArea.setCaretPosition( |
148 |
displayArea.getText().length() ); |
149 |
} |
150 |
|
151 |
// catch problems reading from client |
152 |
catch ( ClassNotFoundException classNotFoundException ) { |
153 |
displayArea.append( "\nUnknown object type received" ); |
154 |
} |
155 |
|
156} while ( !message.equals( "CLIENT>>> TERMINATE" ) );
157}
158
Fig. 17.4 Server portion of a client/server stream-socket connection (part 3 of 4).
996 |
Networking |
Chapter 17 |
159// close streams and socket
160private void closeConnection() throws IOException
161{
162displayArea.append( "\nUser terminated connection" );
163enterField.setEnabled( false );
164output.close();
165input.close();
166connection.close();
167}
168
169// send message to client
170private void sendData( String message )
171{
172// send object to client
173try {
174 |
output.writeObject( "SERVER>>> " + message ); |
175 |
output.flush(); |
176displayArea.append( "\nSERVER>>>" + message );
177}
178
179// process problems sending object
180catch ( IOException ioException ) {
181displayArea.append( "\nError writing object" );
182}
183}
184
185// execute application
186public static void main( String args[] )
187{
188Server application = new Server();
190 |
application.setDefaultCloseOperation( |
191 |
JFrame.EXIT_ON_CLOSE ); |
192 |
|
193application.runServer();
194}
195
196 } // end class Server
Fig. 17.4 Server portion of a client/server stream-socket connection (part 4 of 4).
Software Engineering Observation 17.5
Port numbers can be between 0 and 65,535. Many operating systems reserve port numbers below 1024 for system services (such as e-mail and World Wide Web servers). Generally, these ports should not be specified as connection ports in user programs. In fact, some operating systems require special access privileges to use port numbers below 1024.
Line 73 calls method waitForConnection (lines 100–110) to wait for a client connection. After the connection is established, line 76 calls method getStreams (lines 113–127) to obtain references to the InputStream and OutputStream for the connection. Line 79 calls method processConnection to send the initial connection message to the client and to process all messages received from the client. Line 82 calls method closeConnection to terminate the connection with the client.
Chapter 17 |
Networking |
997 |
In method waitForConnection (lines 100–110), line 105 uses ServerSocket method accept to wait for a connection from a client and assigns the resulting Socket to connection. This method blocks until a connection is received (i.e., the thread in which accept is called stops executing until a client connects). Lines 107–109 output the host name of the computer that made the connection. Socket method getInetAddress returns an InetAddress object (package java.net) containing information about the client computer. InetAddress method getHostName returns the host name of the client computer. For example, if the Internet address of the computer is 127.0.0.1, the corresponding host name would be localhost.
Method getStreams (lines 113–127) obtains references to the InputStream and
OutputStream of the Socket and uses them to initialize an ObjectInputStream and an ObjectOutputStream, respectively. Notice the call to ObjectOutputStream method flush at line 120. This statement causes the ObjectOutputStream on the server to send a stream header to the corresponding client’s ObjectInputStream. The stream header contains information such as the version of object serialization being used to send objects. This information is required by the ObjectInputStream so it can prepare to receive those objects correctly.
Software Engineering Observation 17.6
When using an ObjectOutputStream and ObjectInputStream to send and receive objects over a network connection, always create the ObjectOutputStream first and flush the stream so the client’s ObjectInputStream can prepare to receive the data. This is required only for applications that communicate using ObjectOutputStream and ObjectInputStream.
Line 134 of method processConnection (lines 130–157) uses ObjectOutputStream method writeObject to send the string “SERVER>>> Connection successful” to the client. Line 135 flushes the output stream to ensure that the object is sent immediately; otherwise, the object could be held in an output buffer until more information is available to send.
Performance Tip 17.3
Output buffers typically are used to increase the efficiency of an application by sending larg- er amounts of data fewer times. The input and output components of a computer are typically much slower than the memory of the computer.
The do/while structure at lines 141–156 loops until the server receives the message “CLIENT>>> TERMINATE.” Line 145 uses ObjectInputStream method readObject to read a String from the client. Line 146 displays the message in the JTextArea. Lines 147–148 use JTextComponent method setCaretPosition to position the input cursor in the JTextArea after the last character in the JTextArea. This scrolls the JTextArea as text is appended to it.
When the transmission is complete, method processConnection returns and the program calls method closeConnection (lines 160–167) to close the streams associated with the Socket and close the Socket. Next, the server waits for the next connection attempt from a client by continuing with line 73 at the beginning of the while loop.
When the user of the server application enters a String in the JTextField and presses the Enter key, the program calls method actionPerformed (lines 40–43), reads the String from the JTextField and calls utility method sendData (lines 170–183).
998 |
Networking |
Chapter 17 |
Method sendData sends the String object to the client, flushes the output buffer and appends the same String to the JTextArea in the server window.
Notice that the Server receives a connection, processes the connection, closes the connection and waits for the next connection. A more likely scenario would be a Server that receives a connection, sets up that connection to be processed as a separate thread of execution, and waits for new connections. The separate threads that process existing connections can continue to execute while the Server concentrates on new connection requests.
Like class Server, class Client’s (Fig. 17.5) constructor creates the GUI of the application (a JTextField and a JTextArea). The Client object displays its output in a JTextArea. When the main method (line 175–188) executes, it creates an instance of class Client, specifies the window’s default close operation and calls method runClient (defined at lines 63–90). In this example, you can execute the client from any computer on the Internet and specify the Internet address or host name of the server computer as a command-line argument to the program. For example,
java Client 192.168.1.15
connects to the Server on the computer with Internet address 192.168.1.15.
1// Fig. 17.5: Client.java
2 // Set up a Client that will read information sent 3 // from a Server and display the information.
4
5 // Java core packages
6 import java.io.*;
7 import java.net.*;
8import java.awt.*;
9 import java.awt.event.*;
10
11// Java extension packages
12import javax.swing.*;
13
14public class Client extends JFrame {
15private JTextField enterField;
16private JTextArea displayArea;
17private ObjectOutputStream output;
18private ObjectInputStream input;
19private String message = "";
20private String chatServer;
21private Socket client;
22
23// initialize chatServer and set up GUI
24public Client( String host )
25{
26super( "Client" );
27
28// set server to which this client connects
29chatServer = host;
Fig. 17.5 Demonstrating the client portion of a stream-socket connection between a client and a server (part 1 of 5).
Chapter 17 |
Networking |
999 |
30
31 Container container = getContentPane();
32
33// create enterField and register listener
34enterField = new JTextField();
35enterField.setEnabled( false );
36 |
|
37 |
enterField.addActionListener( |
38 |
|
39 |
new ActionListener() { |
40 |
|
41 |
// send message to server |
42 |
public void actionPerformed( ActionEvent event ) |
43 |
{ |
44 |
sendData( event.getActionCommand() ); |
45 |
} |
46 |
|
47 |
} // end anonymous inner class |
48 |
|
49 |
); // end call to addActionListener |
50 |
|
51 |
container.add( enterField, BorderLayout.NORTH ); |
52 |
|
53// create displayArea
54displayArea = new JTextArea();
55container.add( new JScrollPane( displayArea ),
56 |
BorderLayout.CENTER ); |
57 |
|
58setSize( 300, 150 );
59setVisible( true );
60}
61
62// connect to server and process messages from server
63public void runClient()
64{
65// connect to server, get streams, process connection
66try {
67 |
|
68 |
// Step 1: Create a Socket to make connection |
69 |
connectToServer(); |
70 |
|
71 |
// Step 2: Get the input and output streams |
72 |
getStreams(); |
73 |
|
74 |
// Step 3: Process connection |
75 |
processConnection(); |
76 |
|
77 |
// Step 4: Close connection |
78closeConnection();
79}
80
Fig. 17.5 Demonstrating the client portion of a stream-socket connection between a client and a server (part 2 of 5).
1000 |
Networking |
Chapter 17 |
81// server closed connection
82catch ( EOFException eofException ) {
83System.out.println( "Server terminated connection" );
84}
85
86// process problems communicating with server
87catch ( IOException ioException ) {
88ioException.printStackTrace();
89}
90}
91
92// get streams to send and receive data
93private void getStreams() throws IOException
94{
95// set up output stream for objects
96output = new ObjectOutputStream(
97 |
client.getOutputStream() ); |
|
98 |
|
|
99 |
// flush output |
buffer to send header information |
100 |
output.flush(); |
|
101 |
|
|
102// set up input stream for objects
103input = new ObjectInputStream(
104 |
client.getInputStream() ); |
105 |
|
106displayArea.append( "\nGot I/O streams\n" );
107}
108
109// connect to server
110private void connectToServer() throws IOException
111{
112displayArea.setText( "Attempting connection\n" );
114// create Socket to make connection to server
115client = new Socket(
116 InetAddress.getByName( chatServer ), 5000 );
117
118// display connection information
119displayArea.append( "Connected to: " +
120client.getInetAddress().getHostName() );
121}
122
123// process connection with server
124private void processConnection() throws IOException
125{
126// enable enterField so client user can send messages
127enterField.setEnabled( true );
128
129// process messages sent from server
130do {
131
Fig. 17.5 Demonstrating the client portion of a stream-socket connection between a client and a server (part 3 of 5).
Chapter 17 |
Networking |
1001 |
|
|
|
|
|
132 |
|
// read message and display it |
|
133 |
|
try { |
|
134 |
|
message = ( String ) input.readObject(); |
|
135 |
|
displayArea.append( "\n" + message ); |
|
136 |
|
displayArea.setCaretPosition( |
|
137 |
|
displayArea.getText().length() ); |
|
138 |
|
} |
|
139 |
|
|
|
140 |
|
// catch problems reading from server |
|
141 |
|
catch ( ClassNotFoundException classNotFoundException ) { |
|
142 |
|
displayArea.append( "\nUnknown object type received" ); |
|
143 |
|
} |
|
144 |
|
|
|
145 |
} while ( !message.equals( "SERVER>>> TERMINATE" ) ); |
|
|
146 |
|
|
|
147 |
} // end method process connection |
|
|
148 |
|
|
|
149// close streams and socket
150private void closeConnection() throws IOException
151{
152displayArea.append( "\nClosing connection" );
153output.close();
154input.close();
155client.close();
156}
157
158// send message to server
159private void sendData( String message )
160{
161// send object to server
162try {
163 |
output.writeObject( "CLIENT>>> " + message ); |
164 |
output.flush(); |
165displayArea.append( "\nCLIENT>>>" + message );
166}
167
168// process problems sending object
169catch ( IOException ioException ) {
170displayArea.append( "\nError writing object" );
171}
172}
173
174// execute application
175public static void main( String args[] )
176{
177Client application;
178
179 if ( args.length == 0 )
180application = new Client( "127.0.0.1" );
181else
182 application = new Client( args[ 0 ] );
183
Fig. 17.5 Demonstrating the client portion of a stream-socket connection between a client and a server (part 4 of 5).
1002 |
Networking |
Chapter 17 |
|
|
|
|
|
184 |
|
application.setDefaultCloseOperation( |
|
185 |
|
JFrame.EXIT_ON_CLOSE ); |
|
186 |
|
|
|
187 |
|
application.runClient(); |
|
188 |
|
} |
|
189 |
|
|
|
190 |
} |
// end class Client |
|
The Server and Client windows after the Client connects to the Server
The Server and Client windows after the Client sends a message to the Server
The Server and Client windows after the Server sends a message to the Client
The Server and Client windows after the Client terminates the connection
Fig. 17.5 Demonstrating the client portion of a stream-socket connection between a client and a server (part 5 of 5).
Client method runClient (lines 63–90) performs the work necessary to connect to the Server, to receive data from the Server and to send data to the Server. Line 69 calls method connectToServer (lines 110–121) to perform the connection. After connecting, line 72 calls method getStreams (lines 93–107) to obtain references to the Socket’s InputStream and OutputStream objects. Then line 75 calls method processConnection (124–147) to handle messages sent from the server. When the connection terminates, line 78 calls closeConnection to close the streams and the
Socket.
Method connectToServer (lines 110–121) creates a Socket called client (lines 115–116) to establish a connection. The method passes two arguments to the
Chapter 17 |
Networking |
1003 |
Socket constructor—the Internet address of the server computer and the port number (5000) where that computer is awaiting client connections. The call to InetAddress static method getByName in the first argument returns an InetAddress object containing the Internet address specified as a command-line argument to the application (or 127.0.0.1 if no command-line arguments are specified). Method getByName can receive a String containing either the actual Internet address or the host name of the server. The first argument also could have been written other ways. For the localhost address 127.0.0.1, the first argument could be
InetAddress.getByName( "localhost" )
or
InetAddress.getLocalHost()
Also, there are versions of the Socket constructor that receive a String for the Internet address or host name. The first argument could have been specified as "127.0.0.1" or "localhost". [Note: We chose to demonstrate the client/server relationship by connecting between programs executing on the same computer (localhost). Normally, this first argument would be the Internet address of another computer. The InetAddress object for another computer can be obtained by specifying the Internet address or host name of the other computer as the String argument to InetAddress.getByName.]
The Socket constructor’s second argument is the server port number. This number must match the port number at which the server is waiting for connections (called the handshake point). Once the connection is made, a message is displayed in the JTextArea (lines 119–120) indicating the name of the server computer to which the client connected.
The Client uses an ObjectOutputStream to send data to the server and an ObjectInputStream to receive data from the server. Method getStreams (lines 93–107) creates the ObjectOutputStream and ObjectInputStream objects that use the OutputStream and InputStream objects associated with client.
Method processConnection (lines 124–147) contains a do/while structure that loops until the client receives the message “SERVER>>> TERMINATE.” Line 134 uses
ObjectInputStream method readObject to read a String from the server. Line 135 displays the message in the JTextArea. Lines 136–137 use JTextComponent method setCaretPosition to position the input cursor in the JTextArea after the last character in the JTextArea.
When the transmission is complete, method closeConnection (lines 150–156) closes the streams and the Socket.
When the user of the client application enters a String in the JTextField and presses the Enter key, the program calls method actionPerformed (lines 42–45) to read the String from the JTextField and invoke utility method sendData (159– 172). Method sendData sends the String object to server client, flushes the output buffer and appends the same String to the JTextArea in the client window.
17.7 Connectionless Client/Server Interaction with Datagrams
We have been discussing connection-oriented, streams-based transmission. Now we consider connectionless transmission with datagrams.
1004 |
Networking |
Chapter 17 |
Connection-oriented transmission is like the telephone system in which you dial and are given a connection to the telephone of the person with whom you wish to communicate. The connection is maintained for the duration of your phone call, even when you are not talking.
Connectionless transmission with datagrams is more like the way mail is carried via the postal service. If a large message will not fit in one envelope, you break it into separate message pieces that you place in separate, sequentially numbered envelopes. Each of the letters is then mailed at the same time. The letters could arrive in order, out of order or not at all (although the last case is rare, it does happen). The person at the receiving end reassembles the message pieces into sequential order before attempting to make sense of the message. If your message is small enough to fit in one envelope, you do not have to worry about the “out-of-sequence” problem, but it is still possible that your message might not arrive. One difference between datagrams and postal mail is that duplicates of datagrams can arrive on the receiving computer.
The programs of Fig. 17.6 and Fig. 17.7 use datagrams to send packets of information between a client application and a server application. In the Client application (Fig. 17.7), the user types a message into a JTextField and presses Enter. The program converts the message into a byte array and places it in a datagram packet that is sent to the server. The Server (Fig. 17.6) receives the packet and displays the information in the packet, then echoes the packet back to the client. When the client receives the packet, the client displays the information in the packet. In this example, the Client and Server classes are implemented similarly.
Class Server (Fig. 17.6) defines two DatagramPackets that the server uses to send and receive information and one DatagramSocket that sends and receives these packets. The constructor for class Server (lines 20–41) creates the graphical user interface where the packets of information will be displayed. Next the constructor creates the DatagramSocket in a try block. Line 32 uses the DatagramSocket constructor that takes an integer port number argument (5000) to bind the server to a port where the server can receive packets from clients. Clients sending packets to this Server specify port 5000 in the packets they send. The DatagramSocket constructor throws a SocketException if it fails to bind the DatagramSocket to a port.
Common Programming Error 17.2
Specifying a port that is already in use or specifying an invalid port number when creating a DatagramSocket results in a BindException.
1// Fig. 17.6: Server.java
2 // Set up a Server that will receive packets from a 3 // client and send packets to a client.
4
5 // Java core packages
6 import java.io.*;
7 import java.net.*;
8import java.awt.*;
9 import java.awt.event.*;
10
Fig. 17.6 Demonstrating the server side of connectionless client/server computing with datagrams (part 1 of 4).
Chapter 17 |
Networking |
1005 |
11// Java extension packages
12import javax.swing.*;
13
14public class Server extends JFrame {
15private JTextArea displayArea;
16private DatagramPacket sendPacket, receivePacket;
17private DatagramSocket socket;
18
19// set up GUI and DatagramSocket
20public Server()
21{
22super( "Server" );
23
24displayArea = new JTextArea();
25getContentPane().add( new JScrollPane( displayArea ),
26BorderLayout.CENTER );
27setSize( 400, 300 );
28setVisible( true );
29
30// create DatagramSocket for sending and receiving packets
31try {
32socket = new DatagramSocket( 5000 );
33}
34
35// process problems creating DatagramSocket
36catch( SocketException socketException ) {
37 socketException.printStackTrace();
38System.exit( 1 );
39}
40
41 } // end Server constructor
42
43// wait for packets to arrive, then display data and echo
44// packet to client
45public void waitForPackets()
46{
47// loop forever
48while ( true ) {
49 |
|
50 |
// receive packet, display contents, echo to client |
51 |
try { |
52 |
|
53 |
// set up packet |
54 |
byte data[] = new byte[ 100 ]; |
55 |
receivePacket = |
56 |
new DatagramPacket( data, data.length ); |
57 |
|
58 |
// wait for packet |
59 |
socket.receive( receivePacket ); |
60 |
|
61 |
// process packet |
62 |
displayPacket(); |
Fig. 17.6 Demonstrating the server side of connectionless client/server computing with datagrams (part 2 of 4).
1006 |
Networking |
Chapter 17 |
|
|
|
63 |
|
|
64 |
// echo information from packet back to client |
|
65 |
sendPacketToClient(); |
|
66 |
} |
|
67 |
|
|
68 |
// process problems manipulating packet |
|
69 |
catch( IOException ioException ) { |
|
70 |
displayArea.append( ioException.toString() + "\n" ); |
|
71 |
ioException.printStackTrace(); |
|
72 |
} |
|
73 |
|
|
74 |
} // end while |
|
75 |
|
|
76 |
} // end method waitForPackets |
|
77 |
|
|
78// display packet contents
79private void displayPacket()
80{
81displayArea.append( "\nPacket received:" +
82 |
"\nFrom host: " + receivePacket.getAddress() + |
83 |
"\nHost port: " + receivePacket.getPort() + |
84 |
"\nLength: " + receivePacket.getLength() + |
85 |
"\nContaining:\n\t" + |
86 |
new String( receivePacket.getData(), 0, |
87 |
receivePacket.getLength() ) ); |
88 |
} |
89 |
|
90// echo packet to client
91private void sendPacketToClient() throws IOException
92{
93displayArea.append( "\n\nEcho data to client..." );
95// create packet to send
96sendPacket = new DatagramPacket( receivePacket.getData(),
97 |
receivePacket.getLength(), |
receivePacket.getAddress(), |
98 |
receivePacket.getPort() ); |
|
99 |
|
|
100// send packet
101socket.send( sendPacket );
103displayArea.append( "Packet sent\n" );
104displayArea.setCaretPosition(
105displayArea.getText().length() );
106}
107
108// execute application
109public static void main( String args[] )
110{
111Server application = new Server();
113 |
application.setDefaultCloseOperation( |
114 |
JFrame.EXIT_ON_CLOSE ); |
Fig. 17.6 Demonstrating the server side of connectionless client/server computing with datagrams (part 3 of 4).
Chapter 17 |
Networking |
1007 |
115
116application.waitForPackets();
117}
118
119 } // end class Server
The Server window after the client sends a packet of data
Fig. 17.6 Demonstrating the server side of connectionless client/server computing with datagrams (part 4 of 4).
Server method waitForPackets (lines 45–76) uses an infinite loop to wait for packets to arrive at the Server. Lines 54–56 create a DatagramPacket in which a received packet of information can be stored. The DatagramPacket constructor for this purpose receives two arguments—a byte array containing the data and the length of the byte array. Line 59 waits for a packet to arrive at the Server. Method receive blocks until a packet arrives, then stores the packet in its DatagramPacket argument. Method receive throws an IOException if an error occurs receiving a packet.
When a packet arrives, the program calls method displayPacket (lines 79–88) to append the packet’s contents to displayArea. DatagramPacket method getAddress (line 82) returns an InetAddress object containing the host name of the computer from which the packet was sent. Method getPort (line 83) returns an integer specifying the port number through which the host computer sent the packet. Method getLength (line 84) returns an integer representing the number of bytes of data that were sent. Method getData (line 86) returns a byte array containing the data that was sent. The program uses the byte array to initialize a String object so the data can be output to the JTextArea.
After displaying a packet, the program calls method sendPacketToClient (line 65) to create a new packet and send it to the client. Lines 96–98 create sendPacket and pass four arguments to the DatagramPacket constructor. The first argument specifies the byte array to send. The second argument specifies the number of bytes to send. The third argument specifies the client computer’s Internet address, to which the packet will be sent. The fourth argument specifies the port where the client is waiting to receive packets. Line 101 sends the packet over the network. Method send throws an IOException if an error occurs sending a packet.
Class Client (Fig. 17.7) works similarly to class Server, except that the Client sends packets only when the user types a message in a JTextField and presses the Enter
1008 |
Networking |
Chapter 17 |
key. When this occurs, the program calls method actionPerformed (lines 34–67), which converts the String the user entered in the JTextField into a byte array (line 45). Lines 48–50 create a DatagramPacket and initialize it with the byte array, the length of the String that was entered by the user, the Internet address to which the packet is to be sent (InetAddress.getLocalHost() in this example) and the port number at which the Server is waiting for packets. Line 53 sends the packet. Note that the client in this example must know that the server is receiving packets at port 5000; otherwise, the server will not receive the packets.
1// Fig. 17.7: Client.java
2 // Set up a Client that will send packets to a 3 // server and receive packets from a server.
4
5 // Java core packages
6 import java.io.*;
7 import java.net.*;
8import java.awt.*;
9 import java.awt.event.*;
10
11// Java extension packages
12import javax.swing.*;
13
14public class Client extends JFrame {
15private JTextField enterField;
16private JTextArea displayArea;
17private DatagramPacket sendPacket, receivePacket;
18private DatagramSocket socket;
19
20// set up GUI and DatagramSocket
21public Client()
22{
23super( "Client" );
24 |
|
25 |
Container container = getContentPane(); |
26 |
|
27 |
enterField = new JTextField( "Type message here" ); |
28 |
|
29 |
enterField.addActionListener( |
30 |
|
31 |
new ActionListener() { |
32 |
|
33 |
// create and send a packet |
34 |
public void actionPerformed( ActionEvent event ) |
35 |
{ |
36 |
// create and send packet |
37 |
try { |
38 |
displayArea.append( |
39 |
"\nSending packet containing: " + |
40 |
event.getActionCommand() + "\n" ); |
41 |
|
Fig. 17.7 Demonstrating the client side of connectionless client/server computing with datagrams (part 1 of 4).
Chapter 17 |
Networking |
1009 |
|
|
|
|
|
42 |
|
// get message from textfield and convert to |
|
43 |
|
// array of bytes |
|
44 |
|
String message = event.getActionCommand(); |
|
45 |
|
byte data[] = message.getBytes(); |
|
46 |
|
|
|
47 |
|
// create sendPacket |
|
48 |
|
sendPacket = new DatagramPacket( |
|
49 |
|
data, data.length, |
|
50 |
|
InetAddress.getLocalHost(), 5000 ); |
|
51 |
|
|
|
52 |
|
// send packet |
|
53 |
|
socket.send( sendPacket ); |
|
54 |
|
|
|
55 |
|
displayArea.append( "Packet sent\n" ); |
|
56 |
|
displayArea.setCaretPosition( |
|
57 |
|
displayArea.getText().length() ); |
|
58 |
|
} |
|
59 |
|
|
|
60 |
|
// process problems creating or sending packet |
|
61 |
|
catch ( IOException ioException ) { |
|
62 |
|
displayArea.append( |
|
63 |
|
ioException.toString() + "\n" ); |
|
64 |
|
ioException.printStackTrace(); |
|
65 |
|
} |
|
66 |
|
|
|
67 |
|
} // end actionPerformed |
|
68 |
|
|
|
69 |
} |
// end anonymous inner class |
|
70 |
|
|
|
71 |
); // end call to addActionListener |
|
|
72 |
|
|
|
73 |
container.add( enterField, BorderLayout.NORTH ); |
|
|
74 |
|
|
|
75displayArea = new JTextArea();
76container.add( new JScrollPane( displayArea ),
77 |
BorderLayout.CENTER ); |
78 |
|
79setSize( 400, 300 );
80setVisible( true );
82// create DatagramSocket for sending and receiving packets
83try {
84socket = new DatagramSocket();
85}
86
87// catch problems creating DatagramSocket
88catch( SocketException socketException ) {
89 socketException.printStackTrace();
90System.exit( 1 );
91}
92
93 } // end Client constructor
Fig. 17.7 Demonstrating the client side of connectionless client/server computing with datagrams (part 2 of 4).
1010 |
Networking |
Chapter 17 |
94
95// wait for packets to arrive from Server,
96// then display packet contents
97public void waitForPackets()
98{
99// loop forever
100 |
while ( true ) { |
101 |
|
102 |
// receive packet and display contents |
103 |
try { |
104 |
|
105 |
// set up packet |
106 |
byte data[] = new byte[ 100 ]; |
107 |
receivePacket = |
108 |
new DatagramPacket( data, data.length ); |
109 |
|
110 |
// wait for packet |
111 |
socket.receive( receivePacket ); |
112 |
|
113 |
// display packet contents |
114 |
displayPacket(); |
115 |
} |
116 |
|
117 |
// process problems receiving or displaying packet |
118 |
catch( IOException exception ) { |
119 |
displayArea.append( exception.toString() + "\n" ); |
120 |
exception.printStackTrace(); |
121 |
} |
122 |
|
123 |
} // end while |
124 |
|
125 |
} // end method waitForPackets |
126 |
|
127// display contents of receivePacket
128private void displayPacket()
129{
130displayArea.append( "\nPacket received:" +
131 |
"\nFrom host: " + receivePacket.getAddress() + |
132 |
"\nHost port: " + receivePacket.getPort() + |
133 |
"\nLength: " + receivePacket.getLength() + |
134 |
"\nContaining:\n\t" + |
135 |
new String( receivePacket.getData(), 0, |
136 |
receivePacket.getLength() ) ); |
137 |
|
138 |
displayArea.setCaretPosition( |
139displayArea.getText().length() );
140}
141
142// execute application
143public static void main( String args[] )
144{
145Client application = new Client();
Fig. 17.7 Demonstrating the client side of connectionless client/server computing with datagrams (part 3 of 4).
Chapter 17 |
Networking |
1011 |
|
|
|
|
|
146 |
|
|
|
147 |
application.setDefaultCloseOperation( |
|
|
148 |
JFrame.EXIT_ON_CLOSE ); |
|
|
149 |
|
|
|
150application.waitForPackets();
151}
152
153 } // end class Client
The Client window after sending a packet to the server and receiving the packet back from the server
Fig. 17.7 Demonstrating the client side of connectionless client/server computing with datagrams (part 4 of 4).
Notice that the DatagramSocket constructor call (line 84) in this application does not specify any arguments. This constructor allows the computer to select the next available port number for the DatagramSocket. The client does not need a specific port number, because the server receives the client’s port number as part of each DatagramPacket sent by the client. Thus, the server can send packets back to the same computer and port number from which the server receives a packet of information.
Client method waitForPackets (lines 97–125) uses an infinite loop to wait for packets from the server. Line 111 blocks until a packet arrives. Note that this does not prevent the user from sending a packet, because the GUI events are handled in the event dispatch thread. It only prevents the while loop from continuing until a packet arrives at the Client. When a packet arrives, line 111 stores the packet in receivePacket, and line 114 calls method displayPacket (128–140) to display the packet’s contents in the
JTextArea.
17.8 Client/Server Tic-Tac-Toe Using a Multithreaded Server
In this section, we present the popular game Tic-Tac-Toe implemented by using client/server techniques with stream sockets. The program consists of a TicTacToeServer application (Fig. 17.8) that allows two TicTacToeClient applets (Fig. 17.9) to connect to the server and play Tic-Tac-Toe (outputs shown in Fig. 17.10). As the server receives each client connection, it creates an instance of inner class Player (lines 158–279 of Fig. 17.8) to process the client in a separate thread. These threads enable the clients to play the game independently. The server assigns Xs to the first client to connect (X makes the first move) and assigns
1012 |
Networking |
Chapter 17 |
Os to the second client to connect. The server maintains the information about the board so it can determine whether a player’s move is a valid or invalid move. Each TicTacToeClient applet (Fig. 17.9) 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. Class Square (lines 212–270 of Fig. 17.9) implements each of the nine squares on the board. Class TicTacToeServer and class Player are implemented in file TicTacToeServer.java (Fig. 17.8). Class TicTacToeClient and class Square are implemented in file TicTacToeClient.java (Fig. 17.9).
1// Fig. 17.8: TicTacToeServer.java
2 // This class maintains a game of Tic-Tac-Toe for two 3 // client applets.
4
5 // Java core packages
6import java.awt.*;
7 import java.awt.event.*;
8 import java.net.*;
9 import java.io.*;
10
11// Java extension packages
12import javax.swing.*;
13
14public class TicTacToeServer extends JFrame {
15private byte board[];
16private JTextArea outputArea;
17private Player players[];
18private ServerSocket server;
19private int currentPlayer;
20
21// set up tic-tac-toe server and GUI that displays messages
22public TicTacToeServer()
23{
24super( "Tic-Tac-Toe Server" );
25
26board = new byte[ 9 ];
27players = new Player[ 2 ];
28currentPlayer = 0;
29
30// set up ServerSocket
31try {
32server = new ServerSocket( 5000, 2 );
33}
34
35// process problems creating ServerSocket
36catch( IOException ioException ) {
37 ioException.printStackTrace();
38System.exit( 1 );
39}
40
41// set up JTextArea to display messages during execution
42outputArea = new JTextArea();
Fig. 17.8 Server side of client/server Tic-Tac-Toe program (part 1 of 6).
Chapter 17 |
Networking |
1013 |
43getContentPane().add( outputArea, BorderLayout.CENTER );
44outputArea.setText( "Server awaiting connections\n" );
46setSize( 300, 300 );
47setVisible( true );
48}
49
50// wait for two connections so game can be played
51public void execute()
52{
53// wait for each client to connect
54for ( int i = 0; i < players.length; i++ ) {
56 |
// wait for connection, create Player, start thread |
57 |
try { |
58 |
players[ i ] = new Player( server.accept(), i ); |
59 |
players[ i ].start(); |
60 |
} |
61 |
|
62 |
// process problems receiving connection from client |
63 |
catch( IOException ioException ) { |
64 |
ioException.printStackTrace(); |
65 |
System.exit( 1 ); |
66}
67}
68
69// Player X is suspended until Player O connects.
70// Resume player X now.
71synchronized ( players[ 0 ] ) {
72 |
players[ 0 ].setSuspended( false ); |
73players[ 0 ].notify();
74}
75
76 } // end method execute
77
78// display a message in outputArea
79public void display( String message )
80{
81outputArea.append( message + "\n" );
82}
83
84// Determine if a move is valid.
85// This method is synchronized because only one move can be
86// made at a time.
87public synchronized boolean validMove(
88int location, int player )
89{
90boolean moveDone = false;
91
92// while not current player, must wait for turn
93while ( player != currentPlayer ) {
94
Fig. 17.8 Server side of client/server Tic-Tac-Toe program (part 2 of 6).
1014 |
Networking |
Chapter 17 |
95 |
// wait for turn |
96 |
try { |
97 |
wait(); |
98 |
} |
99 |
|
100 |
// catch wait interruptions |
101 |
catch( InterruptedException interruptedException ) { |
102 |
interruptedException.printStackTrace(); |
103}
104}
106// if location not occupied, make move
107if ( !isOccupied( location ) ) {
108 |
|
109 |
// set move in board array |
110 |
board[ location ] = |
111 |
( byte ) ( currentPlayer == 0 ? 'X' : 'O' ); |
112 |
|
113 |
// change current player |
114 |
currentPlayer = ( currentPlayer + 1 ) % 2; |
115 |
|
116 |
// let new current player know that move occurred |
117 |
players[ currentPlayer ].otherPlayerMoved( location ); |
118 |
|
119 |
// tell waiting player to continue |
120 |
notify(); |
121 |
|
122 |
// tell player that made move that the move was valid |
123return true;
124}
125
126// tell player that made move that the move was not valid
127else
128return false;
129}
130
131// determine whether location is occupied
132public boolean isOccupied( int location )
133{
134if ( board[ location ] == 'X' || board [ location ] == 'O' )
135return true;
136else
137return false;
138}
139
140// place code in this method to determine whether game over
141public boolean gameOver()
142{
143return false;
144}
145
Fig. 17.8 Server side of client/server Tic-Tac-Toe program (part 3 of 6).
Chapter 17 |
Networking |
1015 |
146// execute application
147public static void main( String args[] )
148{
149TicTacToeServer application = new TicTacToeServer();
151 |
application.setDefaultCloseOperation( |
152 |
JFrame.EXIT_ON_CLOSE ); |
153 |
|
154application.execute();
155}
156
157// private inner class Player manages each Player as a thread
158private class Player extends Thread {
159private Socket connection;
160private DataInputStream input;
161private DataOutputStream output;
162private int playerNumber;
163private char mark;
164protected boolean suspended = true;
165
166// set up Player thread
167public Player( Socket socket, int number )
168{
169 |
playerNumber = number; |
170 |
|
171 |
// specify player's mark |
172 |
mark = ( playerNumber == 0 ? 'X' : 'O' ); |
173 |
|
174 |
connection = socket; |
175 |
|
176 |
// obtain streams from Socket |
177 |
try { |
178 |
input = new DataInputStream( |
179 |
connection.getInputStream() ); |
180 |
output = new DataOutputStream( |
181 |
connection.getOutputStream() ); |
182 |
} |
183 |
|
184 |
// process problems getting streams |
185 |
catch( IOException ioException ) { |
186 |
ioException.printStackTrace(); |
187 |
System.exit( 1 ); |
188}
189}
191// send message that other player moved; message contains
192// a String followed by an int
193public void otherPlayerMoved( int location )
194{
195 |
// send message indicating move |
196 |
try { |
197 |
output.writeUTF( "Opponent moved" ); |
|
|
Fig. 17.8 Server side of client/server Tic-Tac-Toe program (part 4 of 6).
1016 |
Networking |
Chapter 17 |
|
|
|
198 |
output.writeInt( location ); |
|
199 |
} |
|
200 |
|
|
201 |
// process problems sending message |
|
202 |
catch ( IOException ioException ) { |
|
203 |
ioException.printStackTrace(); |
|
204}
205}
207// control thread's execution
208public void run()
209{
210 |
// send client message indicating its mark (X or O), |
211 |
// process messages from client |
212 |
try { |
213 |
display( "Player " + ( playerNumber == 0 ? |
214 |
'X' : 'O' ) + " connected" ); |
215 |
|
216 |
// send player's mark |
217 |
output.writeChar( mark ); |
218 |
|
219 |
// send message indicating connection |
220 |
output.writeUTF( "Player " + |
221 |
( playerNumber == 0 ? "X connected\n" : |
222 |
"O connected, please wait\n" ) ); |
223 |
|
224 |
// if player X, wait for another player to arrive |
225 |
if ( mark == 'X' ) { |
226 |
output.writeUTF( "Waiting for another player" ); |
227 |
|
228 |
// wait for player O |
229 |
try { |
230 |
synchronized( this ) { |
231 |
while ( suspended ) |
232 |
wait(); |
233 |
} |
234 |
} |
235 |
|
236 |
// process interruptions while waiting |
237 |
catch ( InterruptedException exception ) { |
238 |
exception.printStackTrace(); |
239 |
} |
240 |
|
241 |
// send message that other player connected and |
242 |
// player X can make a move |
243 |
output.writeUTF( |
244 |
"Other player connected. Your move." ); |
245 |
} |
246 |
|
247 |
// while game not over |
248 |
while ( ! gameOver() ) { |
249 |
|
|
|
Fig. 17.8 Server side of client/server Tic-Tac-Toe program (part 5 of 6).
Chapter 17 |
Networking |
1017 |
|
|
|
250 |
// get move location from client |
|
251 |
int location = input.readInt(); |
|
252 |
|
|
253 |
// check for valid move |
|
254 |
if ( validMove( location, playerNumber ) ) { |
|
255 |
display( "loc: " + location ); |
|
256 |
output.writeUTF( "Valid move." ); |
|
257 |
} |
|
258 |
else |
|
259 |
output.writeUTF( "Invalid move, try again" ); |
|
260 |
} |
|
261 |
|
|
262 |
// close connection to client |
|
263 |
connection.close(); |
|
264 |
} |
|
265 |
|
|
266 |
// process problems communicating with client |
|
267 |
catch( IOException ioException ) { |
|
268 |
ioException.printStackTrace(); |
|
269 |
System.exit( 1 ); |
|
270}
271}
273// set whether or not thread is suspended
274public void setSuspended( boolean status )
275{
276suspended = status;
277}
278
279 } // end class Player
280
281 } // end class TicTacToeServer
Fig. 17.8 Server side of client/server Tic-Tac-Toe program (part 6 of 6).
We begin with a discussion of the server side of the Tic-Tac-Toe game. When the TicTacToeServer application executes, the main method (lines 147–155) creates a TicTacToeServer object called application. The constructor (lines 22–48) attempts to set up a ServerSocket. If successful, the program displays the server
1018 |
Networking |
Chapter 17 |
window, and main invokes the TicTacToeServer method execute (lines 51–76). Method execute loops twice, blocking at line 58 each time while waiting for a client connection. When a client connects, line 58 creates a new Player object to manage the connection as a separate thread, and line 59 calls that object’s start method to begin executing the thread.
When the TicTacToeServer creates a Player, the Player constructor (lines 167–189) receives the Socket object representing the connection to the client and gets the associated input and output streams. The Player’s run method (lines 208–271) controls the information that is sent to the client and the information that is received from the client. First, it tells the client that the client’s connection has been made (lines 213–214), then it passes to the client the character that the client will place on the board when a move is made (line 217). Lines 230–233 suspend each Player thread as it starts executing, because neither player is allowed to make a move when it first connects. Player X can move only when player O connects, and player O can make a move only after player X.
At this point, the game can be played, and the run method begins executing its while structure (lines 248–260). Each iteration of this while structure reads an integer (line 253) representing the location where the client wants to place a mark, and line 254 invokes the TicTacToeServer method validMove (lines 87–129) to check the move. Lines 254– 259 send a message to the client indicating whether the move was valid. 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).
Method validMove (lines 87–129 in class TicTacToeServer) is a synchronized method that allows only one player at a time to move. Synchronizing validMove prevents both players 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), the Player is placed in a wait state until it is that Player’s 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 (lines 110–111), notifies the other Player object (line 117) that a move has been made (so the client can be sent a message), invokes method notify (line 120) so the waiting Player (if there is one) can validate a move and returns true (line 123) to indicate that the move is valid.
When a TicTacToeClient (Fig. 17.9) applet 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 applet’s start method (lines 80–104) opens a connection to the server and gets the associated input and output streams from the Socket object. 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 102 creates Thread object outputThread and initializes it with the Runnable applet, then line 103 calls the thread’s start method. The applet’s run method (lines 108–137) controls the separate thread of execution. The method first reads the mark character (X or O) from the server (line 112), then loops continually (lines 123–135) and reads messages from the server (line 127). Each message is passed to the applet’s processMessage method (lines 140–211) for processing.
Chapter 17 |
Networking |
1019 |
||
|
|
|
|
|
1 |
// Fig. 17.9: |
TicTacToeClient.java |
|
|
2 |
// |
Client for |
the TicTacToe program |
|
3 |
|
|
|
|
4 |
// |
Java core packages |
|
5import java.awt.*;
6 import java.awt.event.*;
7 import java.net.*;
8 import java.io.*;
9
10// Java extension packages
11import javax.swing.*;
12
13// Client class to let a user play Tic-Tac-Toe with
14// another user across a network.
15public class TicTacToeClient extends JApplet
16implements Runnable {
17
18private JTextField idField;
19private JTextArea displayArea;
20private JPanel boardPanel, panel2;
21private Square board[][], currentSquare;
22private Socket connection;
23private DataInputStream input;
24private DataOutputStream output;
25private Thread outputThread;
26private char myMark;
27private boolean myTurn;
28
29// Set up user-interface and board
30public void init()
31{
32Container container = getContentPane();
34// set up JTextArea to display messages to user
35displayArea = new JTextArea( 4, 30 );
36displayArea.setEditable( false );
37container.add( new JScrollPane( displayArea ),
38 |
BorderLayout.SOUTH ); |
39 |
|
40// set up panel for squares in board
41boardPanel = new JPanel();
42boardPanel.setLayout( new GridLayout( 3, 3, 0, 0 ) );
44// create board
45board = new Square[ 3 ][ 3 ];
47// When creating a Square, the location argument to the
48// constructor is a value from 0 to 8 indicating the
49// position of the Square on the board. Values 0, 1,
50// and 2 are the first row, values 3, 4, and 5 are the
51// second row. Values 6, 7, and 8 are the third row.
52for ( int row = 0; row < board.length; row++ ) {
53
Fig. 17.9 Client side of client/server Tic-Tac-Toe program (part 1 of 6).
1020 |
Networking |
Chapter 17 |
|
|
|
54 |
for ( int column = 0; |
|
55 |
column < board[ row ].length; column++ ) { |
|
56 |
|
|
57 |
// create Square |
|
58 |
board[ row ][ column ] = |
|
59 |
new Square( ' ', row * 3 + column ); |
|
60 |
|
|
61 |
boardPanel.add( board[ row ][ column ] ); |
|
62 |
} |
|
63 |
|
|
64 |
} |
|
65 |
|
|
66// textfield to display player's mark
67idField = new JTextField();
68idField.setEditable( false );
69container.add( idField, BorderLayout.NORTH );
71// set up panel to contain boardPanel (for layout purposes)
72panel2 = new JPanel();
73panel2.add( boardPanel, BorderLayout.CENTER );
74container.add( panel2, BorderLayout.CENTER );
75}
76
77// Make connection to server and get associated streams.
78// Start separate thread to allow this applet to
79// continually update its output in text area display.
80public void start()
81{
82// connect to server, get streams and start outputThread
83try {
84 |
|
85 |
// make connection |
86 |
connection = new Socket( |
87 |
InetAddress.getByName( "127.0.0.1" ), 5000 ); |
88 |
|
89 |
// get streams |
90 |
input = new DataInputStream( |
91 |
connection.getInputStream() ); |
92 |
output = new DataOutputStream( |
93 |
connection.getOutputStream() ); |
94 |
} |
95 |
|
96// catch problems setting up connection and streams
97catch ( IOException ioException ) {
98ioException.printStackTrace();
99}
100
101// create and start output thread
102outputThread = new Thread( this );
103outputThread.start();
104}
105
Fig. 17.9 Client side of client/server Tic-Tac-Toe program (part 2 of 6).
Chapter 17 |
Networking |
1021 |
106// control thread that allows continuous update of the
107// text area displayArea
108public void run()
109{
110// get player's mark (X or O)
111try {
112 myMark = input.readChar();
113 idField.setText( "You are player \"" + myMark + "\"" );
114myTurn = ( myMark == 'X' ? true : false );
115}
116
117// process problems communicating with server
118catch ( IOException ioException ) {
119ioException.printStackTrace();
120}
121
122// receive messages sent to client and output them
123while ( true ) {
124 |
|
125 |
// read message from server and process message |
126 |
try { |
127 |
String message = input.readUTF(); |
128 |
processMessage( message ); |
129 |
} |
130 |
|
131 |
// process problems communicating with server |
132 |
catch ( IOException ioException ) { |
133 |
ioException.printStackTrace(); |
134}
135}
137 } // end method run
138
139// process messages received by client
140public void processMessage( String message )
141{
142// valid move occurred
143if ( message.equals( "Valid move." ) ) {
144 |
displayArea.append( "Valid move, please wait.\n" ); |
145 |
|
146 |
// set mark in square from event-dispatch thread |
147 |
SwingUtilities.invokeLater( |
148 |
|
149 |
new Runnable() { |
150 |
|
151 |
public void run() |
152 |
{ |
153 |
currentSquare.setMark( myMark ); |
154 |
} |
155 |
|
156 |
} |
157 |
|
|
|
Fig. 17.9 Client side of client/server Tic-Tac-Toe program (part 3 of 6).
1022 |
Networking |
Chapter 17 |
158); // end call to invokeLater
159}
160
161// invalid move occurred
162else if ( message.equals( "Invalid move, try again" ) ) {
163displayArea.append( message + "\n" );
164myTurn = true;
165 |
} |
166 |
|
167// opponent moved
168else if ( message.equals( "Opponent moved" ) ) {
170 |
// get move location and update board |
171 |
try { |
172 |
final int location = input.readInt(); |
173 |
|
174 |
// set mark in square from event-dispatch thread |
175 |
SwingUtilities.invokeLater( |
176 |
|
177 |
new Runnable() { |
178 |
|
179 |
public void run() |
180 |
{ |
181 |
int row = location / 3; |
182 |
int column = location % 3; |
183 |
|
184 |
board[ row ][ column ].setMark( |
185 |
( myMark == 'X' ? 'O' : 'X' ) ); |
186 |
displayArea.append( |
187 |
"Opponent moved. Your turn.\n" ); |
188 |
} |
189 |
|
190 |
} |
191 |
|
192 |
); // end call to invokeLater |
193 |
|
194 |
myTurn = true; |
195 |
} |
196 |
|
197 |
// process problems communicating with server |
198 |
catch ( IOException ioException ) { |
199 |
ioException.printStackTrace(); |
200 |
} |
201 |
|
202 |
} |
203 |
|
204// simply display message
205else
206 displayArea.append( message + "\n" );
207
208 displayArea.setCaretPosition(
209 displayArea.getText().length() );
Fig. 17.9 Client side of client/server Tic-Tac-Toe program (part 4 of 6).
Chapter 17 |
Networking |
1023 |
210
211 } // end method processMessage
212
213// send message to server indicating clicked square
214public void sendClickedSquare( int location )
215{
216if ( myTurn ) {
217 |
|
218 |
// send location to server |
219 |
try { |
220 |
output.writeInt( location ); |
221 |
myTurn = false; |
222 |
} |
223 |
|
224 |
// process problems communicating with server |
225 |
catch ( IOException ioException ) { |
226 |
ioException.printStackTrace(); |
227}
228}
229}
230
231// set current Square
232public void setCurrentSquare( Square square )
233{
234currentSquare = square;
235}
236
237// private class for the sqaures on the board
238private class Square extends JPanel {
239private char mark;
240private int location;
241
242public Square( char squareMark, int squareLocation )
243{
244 |
mark = squareMark; |
245 |
location = squareLocation; |
246 |
|
247 |
addMouseListener( |
248 |
|
249 |
new MouseAdapter() { |
250 |
|
251 |
public void mouseReleased( MouseEvent e ) |
252 |
{ |
253 |
setCurrentSquare( Square.this ); |
254 |
sendClickedSquare( getSquareLocation() ); |
255 |
} |
256 |
|
257 |
} // end anonymous inner class |
258 |
|
259 |
); // end call to addMouseListener |
260 |
|
261 |
} // end Square constructor |
|
|
Fig. 17.9 Client side of client/server Tic-Tac-Toe program (part 5 of 6).
1024 |
Networking |
Chapter 17 |
262
263// return preferred size of Square
264public Dimension getPreferredSize()
265{
266return new Dimension( 30, 30 );
267}
268
269// return minimum size of Square
270public Dimension getMinimumSize()
271{
272return getPreferredSize();
273}
274
275// set mark for Square
276public void setMark( char newMark )
277{
278 mark = newMark;
279repaint();
280}
281
282// return Square location
283public int getSquareLocation()
284{
285return location;
286}
287
288// draw Square
289public void paintComponent( Graphics g )
290{
291 super.paintComponent( g );
292
293 g.drawRect( 0, 0, 29, 29 );
294g.drawString( String.valueOf( mark ), 11, 20 );
295}
296
297 } // end class Square
298
299 } // end class TicTacToeClient
Fig. 17.9 Client side of client/server Tic-Tac-Toe program (part 6 of 6).
If the message received is Valid move., lines 143–159 display the message Valid move, please wait. and call class Square’s setMark method 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., lines 162–165 display the message so the user can click a different square. If the message received is Opponent moved., lines 168–195 read an integer from the server indicating where the opponent moved and 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 206 simply displays the message.
Chapter 17 |
Networking |
1025 |
|
|
|
|
|
|
Fig. 17.10 Sample outputs from the client/server Tic-Tac-Toe program (part 1 of 2).