Client/Server Interaction with Stream Socket Connections
Client Server Interaction with Stream Socket Connections
Figure 24.5 and Fig. 24.7 use stream sockets to demonstrate a simple client/server chat application. The server waits for a client connection attempt. When a client connects to the server, the server application sends the client a String object (recall that Strings are Serializable objects) indicating that the connection was successful. Then the client displays the message. The client and server applications each provide textfields that 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 terminates. Then the server waits for the next client to connect. The declaration of class Server appears in Fig. 24.5. The declaration of class Client appears in Fig. 24.7. The screen captures showing the execution between the client and the server are shown as part of Fig. 24.7.
Figure 24.5. Server portion of a client/server stream-socket connection.
(This item is displayed on pages 1120 - 1123 in the print version)
1 // Fig. 24.5: Server.java 2 // Set up a Server that will receive a connection from a client, send 3 // a string to the client, and close the connection. 4 import java.io.EOFException; 5 import java.io.IOException; 6 import java.io.ObjectInputStream; 7 import java.io.ObjectOutputStream; 8 import java.net.ServerSocket; 9 import java.net.Socket; 10 import java.awt.BorderLayout; 11 import java.awt.event.ActionEvent; 12 import java.awt.event.ActionListener; 13 import javax.swing.JFrame; 14 import javax.swing.JScrollPane; 15 import javax.swing.JTextArea; 16 import javax.swing.JTextField; 17 import javax.swing.SwingUtilities; 18 19 public class Server extends JFrame 20 { 21 private JTextField enterField; // inputs message from user 22 private JTextArea displayArea; // display information to user 23 private ObjectOutputStream output; // output stream to client 24 private ObjectInputStream input; // input stream from client 25 private ServerSocket server; // server socket 26 private Socket connection; // connection to client 27 private int counter = 1; // counter of number of connections 28 29 // set up GUI 30 public Server() 31 { 32 super ( "Server" ); 33 34 enterField = new JTextField(); // create enterField 35 enterField.setEditable( false ); 36 enterField.addActionListener( 37 new ActionListener() 38 { 39 // send message to client 40 public void actionPerformed( ActionEvent event ) 41 { 42 sendData( event.getActionCommand() ); 43 enterField.setText( "" ); 44 } // end method actionPerformed 45 } // end anonymous inner class 46 ); // end call to addActionListener 47 48 add( enterField, BorderLayout.NORTH ); 49 50 displayArea = new JTextArea(); // create displayArea 51 add( new JScrollPane( displayArea ), BorderLayout.CENTER ); 52 53 setSize( 300, 150 ); // set size of window 54 setVisible( true ); // show window 55 } // end Server constructor 56 57 // set up and run server 58 public void runServer() 59 { 60 try // set up server to receive connections; process connections 61 { 62 server = new ServerSocket( 12345, 100 ); // create ServerSocket 63 64 while ( true ) 65 { 66 try 67 { 68 waitForConnection(); // wait for a connection 69 getStreams(); // get input & output streams 70 processConnection(); // process connection 71 } // end try 72 catch ( EOFException eofException ) 73 { 74 displayMessage( " Server terminated connection" ); 75 } // end catch 76 finally 77 { 78 closeConnection(); // close connection 79 counter++; 80 } // end finally 81 } // end while 82 } // end try 83 catch ( IOException ioException ) 84 { 85 ioException.printStackTrace(); 86 } // end catch 87 } // end method runServer 88 89 // wait for connection to arrive, then display connection info 90 private void waitForConnection() throws IOException 91 { 92 displayMessage( "Waiting for connection " ); 93 connection = server.accept(); // allow server to accept connection 94 displayMessage( "Connection " + counter + " received from: " + 95 connection.getInetAddress().getHostName() ); 96 } // end method waitForConnection 97 98 // get streams to send and receive data 99 private void getStreams() throws IOException 100 { 101 // set up output stream for objects 102 output = new ObjectOutputStream( connection.getOutputStream() ); 103 output.flush(); // flush output buffer to send header information 104 105 // set up input stream for objects 106 input = new ObjectInputStream( connection.getInputStream() ); 107 108 displayMessage( " Got I/O streams " ); 109 } // end method getStreams 110 111 // process connection with client 112 private void processConnection() throws IOException 113 { 114 String message = "Connection successful"; 115 sendData( message ); // send connection successful message 116 117 // enable enterField so server user can send messages 118 setTextFieldEditable( true ); 119 120 do // process messages sent from client 121 { 122 try // read message and display it 123 { 124 message = ( String ) input.readObject(); // read new message 125 displayMessage( " " + message ); // display message 126 } // end try 127 catch ( ClassNotFoundException classNotFoundException ) 128 { 129 displayMessage( " Unknown object type received" ); 130 } // end catch 131 132 } while ( !message.equals( "CLIENT>>> TERMINATE" ) ); 133 } // end method processConnection 134 135 // close streams and socket 136 private void closeConnection() 137 { 138 displayMessage( " Terminating connection " ); 139 setTextFieldEditable( false ); // disable enterField 140 141 try 142 { 143 output.close(); // close output stream 144 input.close(); // close input stream 145 connection.close(); // close socket 146 } // end try 147 catch ( IOException ioException ) 148 { 149 ioException.printStackTrace(); 150 } // end catch 151 } // end method closeConnection 152 153 // send message to client 154 private void sendData( String message ) 155 { 156 try // send object to client 157 { 158 output.writeObject( "SERVER>>> " + message ); 159 output.flush(); // flush output to client 160 displayMessage( " SERVER>>> " + message ); 161 } // end try 162 catch ( IOException ioException ) 163 { 164 displayArea.append( " Error writing object" ); 165 } // end catch 166 } // end method sendData 167 168 // manipulates displayArea in the event-dispatch thread 169 private void displayMessage( final String messageToDisplay ) 170 { 171 SwingUtilities.invokeLater( 172 new Runnable() 173 { 174 public void run() // updates displayArea 175 { 176 displayArea.append( messageToDisplay ); // append message 177 } // end method run 178 } // end anonymous inner class 179 ); // end call to SwingUtilities.invokeLater 180 } // end method displayMessage 181 182 // manipulates enterField in the event-dispatch thread 183 private void setTextFieldEditable( final boolean editable ) 184 { 185 SwingUtilities.invokeLater( 186 new Runnable() 187 { 188 public void run() // sets enterField's editability 189 { 190 enterField.setEditable( editable ); 191 } // end method run 192 } // end inner class 193 ); // end call to SwingUtilities.invokeLater 194 } // end method setTextFieldEditable 195 } // end class Server |
Server Class
Server's constructor (lines 3055) creates the server's GUI, which contains a JTextField and a JTextArea. Server displays its output in the JTextArea. When the main method (lines 712 of Fig. 24.6) executes, it creates a Server object, specifies the window's default close operation and calls method runServer (declared at lines 5887).
Figure 24.6. Test class for Server.
(This item is displayed on page 1124 in the print version)
1 // Fig. 24.6: ServerTest.java 2 // Test the Server application. 3 import javax.swing.JFrame; 4 5 public class ServerTest 6 { 7 public static void main( String args[] ) 8 { 9 Server application = new Server(); // create server 10 application.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); 11 application.runServer(); // run server application 12 } // end main 13 } // end class ServerTest |
Method runServer sets up the server to receive a connection and processes one connection at a time. Line 62 creates a ServerSocket called server to wait for connections. The ServerSocket listens for a connection from a client at port 12345. 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.
Common Programming Error 24.1
Specifying a port that is already in use or specifying an invalid port number when creating a ServerSocket results in a BindException. |
Line 68 calls method waitForConnection (declared at lines 9096) to wait for a client connection. After the connection is established, line 69 calls method getStreams (declared at lines 99109) to obtain references to the streams for the connection. Line 70 calls method processConnection (declared at lines 112133) to send the initial connection message to the client and to process all messages received from the client. The finally block (lines 7680) terminates the client connection by calling method closeConnection (lines 136151) even if an exception occurs. Method displayMessage (lines 169180) is called from these methods to use the event-dispatch thread to display messages in the application's JTextArea.
In method waitForConnection (lines 9096), line 93 uses ServerSocket method accept to wait for a connection from a client. When a connection occurs, the resulting Socket is assigned to connection. Method accept blocks until a connection is received (i.e., the thread in which accept is called stops executing until a client connects). Lines 9495 output the host name of the computer that made the connection. Socket method getInetAddress returns an InetAddress (package java.net) containing information about the client computer. InetAddress method getHostName returns the host name of the client computer. For example, there is a special IP address (127.0.0.1) and host name (localhost) that is useful for testing networking applications on your local computer (this is also known as the loopback address). If getHostName is called on an InetAddress containing 127.0.0.1, the corresponding host name returned by the method would be localhost.
Method getStreams (lines 99109) obtains references to the Socket's streams and uses them to initialize an ObjectOutputStream (line 102) and an ObjectInputStream (line 106), respectively. Note the call to ObjectOutputStream method flush at line 103. This statement causes the ObjectOutputStream on the server to send a stream header to the corresponding client's ObjectInputStream. The stream header contains such information as the version of object serialization being used to send objects. This information is required by the ObjectInputStream so that it can prepare to receive those objects correctly.
Software Engineering Observation 24.5
When using an ObjectOutputStream and ObjectInputStream to send and receive data over a network connection, always create the ObjectOutputStream first and flush the stream so that the client's ObjectInputStream can prepare to receive the data. This is required only for networking applications that communicate using ObjectOutputStream and ObjectInputStream. |
Performance Tip 24.3
A computer's input and output components are typically much slower than its memory. Output buffers typically are used to increase the efficiency of an application by sending larger amounts of data fewer times, thus reducing the number of times an application accesses the computer's input and output components. |
Line 114 of method processConnection (lines 112133) calls method sendData to send "SERVER>>> Connection successful" as a string to the client. The loop at lines 120132 executes until the server receives the message "CLIENT>>> TERMINATE". Line 124 uses ObjectInputStream method readObject to read a String from the client. Line 125 invokes method displayMessage to append the message to the JTextArea.
When the transmission is complete, method processConnection returns, and the program calls method closeConnection (lines 136151) to close the streams associated with the Socket and close the Socket. Then the server waits for the next connection attempt from a client by continuing with line 68 at the beginning of the while loop.
When the user of the server application enters a string in the textfield and presses the Enter key, the program calls method actionPerformed (lines 4044), which reads the string from the textfield and calls utility method sendData (lines 154166) to send the string to the client. Method sendData writes the object, flushes the output buffer and appends the same string to the textarea in the server window. It is not necessary to invoke displayMessage to modify the textarea here, because method sendData is called from an event handlerthus, sendData executes as part of the event-dispatch thread.
Note that Server receives a connection, processes it, closes it and waits for the next connection. A more likely scenario would be a Server that receives a connection, sets it up to be processed as a separate thread of execution, then immediately waits for new connections. The separate threads that process existing connections can continue to execute while the Server concentrates on new connection requests. This makes the server more efficient, because multiple client requests can be processed concurrently. We demonstrate a multithreaded server in Section 24.8.
Client Class
Like class Server, class Client's (Fig. 24.7) constructor (lines 2956) creates the GUI of the application (a JTextField and a JTextArea). Client displays its output in the textarea. When method main (lines 719 of Fig. 24.8) executes, it creates an instance of class Client, specifies the window's default close operation and calls method runClient (declared at lines 5979). In this example, you can execute the client from any computer on the Internet and specify the IP address or host name of the server computer as a commandline argument to the program. For example, the command
java Client 192.168.1.15
attempts to connect to the Server on the computer with IP address 192.168.1.15.
Figure 24.7. Client portion of a stream-socket connection between client and server.
(This item is displayed on pages 1126 - 1129 in the print version)
1 // Fig. 24.7: Client.java 2 // Client that reads and displays information sent from a Server. 3 import java.io.EOFException; 4 import java.io.IOException; 5 import java.io.ObjectInputStream; 6 import java.io.ObjectOutputStream; 7 import java.net.InetAddress; 8 import java.net.Socket; 9 import java.awt.BorderLayout; 10 import java.awt.event.ActionEvent; 11 import java.awt.event.ActionListener; 12 import javax.swing.JFrame; 13 import javax.swing.JScrollPane; 14 import javax.swing.JTextArea; 15 import javax.swing.JTextField; 16 import javax.swing.SwingUtilities; 17 18 public class Client extends JFrame 19 { 20 private JTextField enterField; // enters information from user 21 private JTextArea displayArea; // display information to user 22 private ObjectOutputStream output; // output stream to server 23 private ObjectInputStream input; // input stream from server 24 private String message = ""; // message from server 25 private String chatServer; // host server for this application 26 private Socket client; // socket to communicate with server 27 28 // initialize chatServer and set up GUI 29 public Client( String host ) 30 { 31 super( "Client" ); 32 33 chatServer = host; // set server to which this client connects 34 35 enterField = new JTextField(); // create enterField 36 enterField.setEditable( false ); 37 enterField.addActionListener( 38 new ActionListener() 39 { 40 // send message to server 41 public void actionPerformed( ActionEvent event ) 42 { 43 sendData( event.getActionCommand() ); 44 enterField.setText( "" ); 45 } // end method actionPerformed 46 } // end anonymous inner class 47 ); // end call to addActionListener 48 49 add( enterField, BorderLayout.NORTH ); 50 51 displayArea = new JTextArea(); // create displayArea 52 add( new JScrollPane( displayArea ), BorderLayout.CENTER ); 53 54 setSize( 300, 150 ); // set size of window 55 setVisible( true ); // show window 56 } // end Client constructor 57 58 // connect to server and process messages from server 59 public void runClient() 60 { 61 try // connect to server, get streams, process connection 62 { 63 connectToServer(); // create a Socket to make connection 64 getStreams(); // get the input and output streams 65 processConnection(); // process connection 66 } // end try 67 catch ( EOFException eofException ) 68 { 69 displayMessage( " Client terminated connection" ); 70 } // end catch 71 catch ( IOException ioException ) 72 { 73 ioException.printStackTrace(); 74 } // end catch 75 finally 76 { 77 closeConnection(); // close connection 78 } // end finally 79 } // end method runClient 80 81 // connect to server 82 private void connectToServer() throws IOException 83 { 84 displayMessage( "Attempting connection " ); 85 86 // create Socket to make connection to server 87 client = new Socket( InetAddress.getByName( chatServer ), 12345 ); 88 89 // display connection information 90 displayMessage( "Connected to: " + 91 client.getInetAddress().getHostName() ); 92 } // end method connectToServer 93 94 // get streams to send and receive data 95 private void getStreams() throws IOException 96 { 97 // set up output stream for objects 98 output = new ObjectOutputStream( client.getOutputStream() ); 99 output.flush(); // flush output buffer to send header information 100 101 // set up input stream for objects 102 input = new ObjectInputStream( client.getInputStream() ); 103 104 displayMessage( " Got I/O streams " ); 105 } // end method getStreams 106 107 // process connection with server 108 private void processConnection() throws IOException 109 { 110 // enable enterField so client user can send messages 111 setTextFieldEditable( true ); 112 113 do // process messages sent from server 114 { 115 try // read message and display it 116 { 117 message = ( String ) input.readObject(); // read new message 118 displayMessage( " " + message ); // display message 119 } // end try 120 catch ( ClassNotFoundException classNotFoundException ) 121 { 122 displayMessage( " Unknown object type received" ); 123 } // end catch 124 125 } while ( !message.equals( "SERVER>>> TERMINATE" ) ); 126 } // end method processConnection 127 128 // close streams and socket 129 private void closeConnection() 130 { 131 displayMessage( " Closing connection" ); 132 setTextFieldEditable( false ); // disable enterField 133 134 try 135 { 136 output.close(); // close output stream 137 input.close(); // close input stream 1 138 client.close(); // close socket 139 } // end try 140 catch ( IOException ioException ) 141 { 142 ioException.printStackTrace(); 143 } // end catch 144 } // end method closeConnection 145 146 // send message to server 147 private void sendData( String message ) 148 { 149 try // send object to server 150 { 151 output.writeObject( "CLIENT>>> " + message ); 152 output.flush(); // flush data to output 153 displayMessage( " CLIENT>>> " + message ); 154 } // end try 155 catch ( IOException ioException ) 156 { 157 displayArea.append( " Error writing object" ); 158 } // end catch 159 } // end method sendData 160 161 // manipulates displayArea in the event-dispatch thread 162 private void displayMessage( final String messageToDisplay ) 163 { 164 SwingUtilities.invokeLater( 165 new Runnable() 166 { 167 public void run() // updates displayArea 168 { 169 displayArea.append( messageToDisplay ); 170 } // end method run 171 } // end anonymous inner class 172 ); // end call to SwingUtilities.invokeLater 173 } // end method displayMessage 174 175 // manipulates enterField in the event-dispatch thread 176 private void setTextFieldEditable( final boolean editable ) 177 { 178 SwingUtilities.invokeLater( 179 new Runnable() 180 { 181 public void run() // sets enterField's editability 182 { 183 enterField.setEditable( editable ); 184 } // end method run 185 } // end anonymous inner class 186 ); // end call to SwingUtilities.invokeLater 187 } // end method setTextFieldEditable 188 } // end class Client |
Figure 24.8. Class that tests the Client.
(This item is displayed on pages 1130 - 1131 in the print version)
1 // Fig. 24.8: ClientTest.java 2 // Test the Client class. 3 import javax.swing.JFrame; 4 5 public class ClientTest 6 { 7 public static void main( String args[] ) 8 { 9 Client application; // declare client application 10 11 // if no command line args 12 if ( args.length == 0 ) 13 application = new Client( "127.0.0.1" ); // connect to localhost 14 else 15 application = new Client( args[ 0 ] ); // use args to connect 16 17 application.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); 18 application.runClient(); // run client application 19 } // end main 20 } // end class ClientTest
|
Client method runClient (lines 5979) sets up the connection to the server, processes messages received from the server and closes the connection when communication is complete. Line 63 calls method connectToServer (declared at lines 8292) to perform the connection. After connecting, line 64 calls method getStreams (declared at lines 95105) to obtain references to the Socket's stream objects. Then line 65 calls method processConnection (declared at lines 108126) to receive and display messages sent from the server. The finally block (lines 7578) calls closeConnection (lines 129144) to close the streams and the Socket even if an exception has occurred. Method displayMessage (lines 162173) is called from these methods to use the event-dispatch thread to display messages in the application's textarea.
Method connectToServer (lines 8292) creates a Socket called client (line 87) to establish a connection. The method passes two arguments to the Socket constructorthe IP address of the server computer and the port number (12345) where the server application is awaiting client connections. In the first argument, InetAddress static method getByName returns an InetAddress object containing the IP 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 IP 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 IP address or host name. The first argument could have been specified as "127.0.0.1" or "localhost". We chose to demonstrate the client/server relationship by connecting between applications executing on the same computer (localhost). Normally, this first argument would be the IP address of another computer. The InetAddress object for another computer can be obtained by specifying the computer's IP address or host name as the argument to InetAddress method getByName. The Socket constructor's second argument is the server port number. This must match the port number at which the server is waiting for connections (called the handshake point). Once the connection is made, lines 9091 display a message in the text area indicating the name of the server computer to which the client has connected.
The Client uses an ObjectOutputStream to send data to the server and an ObjectInputStream to receive data from the server. Method getStreams (lines 95105) creates the ObjectOutputStream and ObjectInputStream objects that use the streams associated with the client socket.
Method processConnection (lines 108126) contains a loop that executes until the client receives the message "SERVER>>> TERMINATE". Line 117 reads a String object from the server. Line 118 invokes displayMessage to append the message to the textarea.
When the transmission is complete, method closeConnection (lines 129144) closes the streams and the Socket.
When the user of the client application enters a string in the textfield and presses the Enter key, the program calls method actionPerformed (lines 4145) to read the string and invoke utility method sendData (147159) to send the string to the server. Method sendData writes the object, flushes the output buffer and appends the same string to the JTextArea in the client window. Once again, it is not necessary to invoke utility method displayMessage to modify the textarea here, because method sendData is called from an event handler.