/* SocketBallsMultiserverApplication.java Copyright (c) Kari Laitinen http://www.naturalprogramming.com/ 2010-02-08 File created. 2010-02-08 Last modification. This program is an improved version of SocketBallsApplication.java. The Balls Server of this program is able to simultaneously serve several Ball Clients. This is achieved by using a separate thread that serves each Ball Client. See more comments at the beginning of SocketBallsApplication.java USING INSTRUCTIONS: Remember to increment the port number when you run multiple clients. WARNINGS: This program is not very widely tested in multicomputer environments. TO DO: Catching of the exceptions may need a new re-evaluation. Stopping the server is not yet fully designed. We might need to close all sockets that are managed by the threads that serve clients. Is there a method to close the ServerSocket when the server is shut down? Now the execution of all threads is controlled with variable server_thread_should_be_run. Is there an alternative mechanism to run the threads 'elegantly'? */ import java.io.* ; import java.net.* ; import java.awt.* ; import java.awt.event.* ; import javax.swing.* ; import java.awt.geom.* ; import java.util.ArrayList ; import java.util.Random ; // The Ball class in this program is almost the same as the Ball class // in file Ball.java. The only difference between these two classes // is that the class below is Serializable. class Ball implements Serializable { int ball_center_point_x = 0 ; int ball_center_point_y = 0 ; Color ball_color = Color.red ; int ball_diameter = 100 ; boolean this_ball_is_activated = false ; public Ball( int given_center_point_x, int given_center_point_y, Color given_color ) { ball_center_point_x = given_center_point_x ; ball_center_point_y = given_center_point_y ; ball_color = given_color ; } public void activate_ball() { this_ball_is_activated = true ; } public void deactivate_ball() { this_ball_is_activated = false ; } public int get_ball_center_point_x() { return ball_center_point_x ; } public int get_ball_center_point_y() { return ball_center_point_y ; } public int get_ball_diameter() { return ball_diameter ; } public void move_right() { ball_center_point_x += 3 ; } public void move_left() { ball_center_point_x -= 3 ; } public void move_up() { ball_center_point_y -= 3 ; } public void move_down() { ball_center_point_y += 3 ; } public void move_this_ball( int movement_in_direction_x, int movement_in_direction_y ) { ball_center_point_x = ball_center_point_x + movement_in_direction_x ; ball_center_point_y = ball_center_point_y + movement_in_direction_y ; } public void move_to_position( int new_center_point_x, int new_center_point_y ) { ball_center_point_x = new_center_point_x ; ball_center_point_y = new_center_point_y ; } public void shrink() { // The if-construct ensures that the ball does not become // too small. if ( ball_diameter > 10 ) { ball_diameter -= 6 ; } } public void enlarge() { ball_diameter += 6 ; } public void set_diameter( int new_diameter ) { if ( new_diameter > 5 ) { ball_diameter = new_diameter ; } } public void set_color( Color new_color ) { ball_color = new_color ; } public boolean contains_point( Point given_point ) { int ball_radius = ball_diameter / 2 ; // Here we use the Pythagorean theorem to calculate the distance // from the given point to the center point of the ball. // See the note at the end of this file. int distance_from_given_point_to_ball_center = (int) Math.sqrt( Math.pow( ball_center_point_x - given_point.x, 2 ) + Math.pow( ball_center_point_y - given_point.y, 2 ) ) ; return ( distance_from_given_point_to_ball_center <= ball_radius ) ; } public boolean intersects_rectangle( Rectangle2D given_rectangle ) { Area this_ball_area = new Area( new Ellipse2D.Float( ball_center_point_x - ball_diameter / 2, ball_center_point_y - ball_diameter / 2, ball_diameter, ball_diameter ) ) ; return this_ball_area.intersects( given_rectangle ) ; } public void draw( Graphics graphics ) { graphics.setColor( ball_color ) ; graphics.fillOval( ball_center_point_x - ball_diameter / 2, ball_center_point_y - ball_diameter / 2, ball_diameter, ball_diameter ) ; graphics.setColor( Color.BLACK ) ; graphics.drawOval( ball_center_point_x - ball_diameter / 2, ball_center_point_y - ball_diameter / 2, ball_diameter, ball_diameter ) ; // If this ball is activated, it will have a thick black edge if ( this_ball_is_activated == true ) { graphics.drawOval( ball_center_point_x - ball_diameter / 2 + 1, ball_center_point_y - ball_diameter / 2 + 1, ball_diameter - 2, ball_diameter - 2 ) ; graphics.drawOval( ball_center_point_x - ball_diameter / 2 + 2, ball_center_point_y - ball_diameter / 2 + 2, ball_diameter - 4, ball_diameter - 4 ) ; } } } class BallsPanel extends JPanel { ArrayList balls_to_be_shown = new ArrayList() ; String message_below_the_balls = "No activities." ; Font font_for_text = new Font( "Serif", Font.PLAIN, 14 ) ; protected void set_message_text( String new_message_text ) { message_below_the_balls = new_message_text ; System.out.print( "\n Message " + message_below_the_balls ) ; repaint() ; } protected void add_to_message_text( String continuation_to_message_text ) { message_below_the_balls = message_below_the_balls + " " + continuation_to_message_text ; System.out.print( "\n Message " + message_below_the_balls ) ; repaint() ; } public void paintComponent( Graphics graphics ) { super.paintComponent( graphics ) ; graphics.setColor( Color.BLACK ) ; graphics.fillRect( 0, 0, getWidth(), getHeight() ) ; for ( int ball_index = 0 ; ball_index < balls_to_be_shown.size() ; ball_index ++ ) { ( balls_to_be_shown.get( ball_index ) ).draw( graphics ) ; } graphics.setColor( Color.WHITE ) ; graphics.setFont( font_for_text ) ; graphics.drawString( message_below_the_balls, 10, getHeight() - 40 ) ; } } class BallsServerPanel extends BallsPanel implements Runnable { private Thread thread_to_run_balls_server ; private boolean server_thread_should_be_run = true ; private int server_port_number = 5040 ; // The following dynamic array will contain the thread // objects that serve the Balls Clients ArrayList threads_serving_clients = new ArrayList(); // The following is an inner class whose objects will serve // the clients that contact the server. public class ClientServingThread extends Thread { private Socket socket_to_client ; private ObjectOutputStream output_stream ; private ObjectInputStream input_stream ; public ClientServingThread( Socket given_socket_to_client ) { socket_to_client = given_socket_to_client; } public void run() { try { // Get output stream. output_stream = new ObjectOutputStream( socket_to_client.getOutputStream() ) ; // flush output buffer to send header information output_stream.flush(); // set up input stream for objects input_stream = new ObjectInputStream( socket_to_client.getInputStream() ); add_to_message_text( " Streams OK" ); while ( server_thread_should_be_run == true ) { // The following statement waits until the client // sends an object, or the socket is closed by the // stop_balls_server() method. In the latter case // it seems that a SocketException is thrown. String received_string = (String) input_stream.readObject() ; set_message_text( "FROM CLIENT: " + received_string ) ; Random random_number_generator = new Random() ; int random_ball_index = random_number_generator.nextInt( balls_to_be_shown.size() ) ; Ball ball_to_be_sent = balls_to_be_shown.get( random_ball_index ) ; balls_to_be_shown.remove( random_ball_index ) ; output_stream.writeObject( ball_to_be_sent ) ; output_stream.flush() ; if ( balls_to_be_shown.size() == 0 ) { // All balls have been sent. Let's create new balls. create_new_ball_objects() ; set_message_text( "New balls created." ) ; } Thread.sleep( 500 ) ; } } catch ( ClassNotFoundException classNotFoundException ) { System.out.print( "\n Unknown object type received" ); } catch ( SocketException caught_socket_exception ) { System.out.print( "\n SocketException caught" ); } catch ( InterruptedException caught_interrupted_exception ) { } catch ( IOException ioException ) { ioException.printStackTrace(); } try { output_stream.close() ; input_stream.close() ; socket_to_client.close() ; } catch ( IOException ioException ) { ioException.printStackTrace(); } } // end of the run() method of the inner class } // end of inner class ClientServingThread private void create_new_ball_objects() { balls_to_be_shown.clear() ; // Let's create balls that have a random color. Random random_number_generator = new Random() ; // The following loops create 9 balls. for ( int ball_center_point_y = 120 ; ball_center_point_y < 600 ; ball_center_point_y += 200 ) { for ( int ball_center_point_x = 124 ; ball_center_point_x < 600 ; ball_center_point_x += 200 ) { Ball new_ball = new Ball( ball_center_point_x, ball_center_point_y, new Color( random_number_generator.nextInt( 0xFFFFFF ) ) ) ; new_ball.set_diameter( 180 ) ; balls_to_be_shown.add( new_ball ) ; } } } public BallsServerPanel() { create_new_ball_objects() ; thread_to_run_balls_server = new Thread( this ) ; thread_to_run_balls_server.start() ; } public void stop_balls_server() { if ( thread_to_run_balls_server != null ) { thread_to_run_balls_server.interrupt() ; } // We might need to close() all socets in all threads! /* if ( socket_to_client != null ) { try { socket_to_client.close() ; } catch( IOException caught_exception ) { System.out.print( "\n Socket closing problem ??? " ) ; } } */ server_thread_should_be_run = false ; System.out.print( "\n Server termination attempted." ) ; } public void run() { set_message_text( "Starting server..." ) ; try { while ( server_thread_should_be_run == true ) { // Step 1: Create a ServerSocket. ServerSocket server_socket = new ServerSocket( server_port_number, 100 ); // Step 2: Wait for a connection. set_message_text( "Waiting client..." ) ; // Method accept() waits until a client makes a connection and // returns a Socket object that will be used in the actual // communications operations. Socket new_socket_to_client = server_socket.accept() ; add_to_message_text( "Connection " + new_socket_to_client.getInetAddress().getHostName() ) ; // A client has made a connection. Let's create and start // a thread for that client. ClientServingThread thread_for_new_client = new ClientServingThread( new_socket_to_client ) ; threads_serving_clients.add( thread_for_new_client ) ; thread_for_new_client.start() ; // After a thread for the newest client has been activated, // this thread resumes waiting another client. // We will increment the port number for the next client. server_port_number ++ ; // System.out.print( " S" ) ; } // end of while } // process EOFException when client closes connection catch ( EOFException eofException ) { System.out.println( "Client terminated connection" ) ; } catch ( IOException ioException ) { ioException.printStackTrace(); } set_message_text( "Main server thread terminated." ) ; } // end of run() } class BallsClientPanel extends BallsPanel implements Runnable, ActionListener { private Thread thread_to_run_balls_client = null ; private boolean client_thread_should_be_run = true ; private boolean ball_requesting_enabled = false ; private ObjectOutputStream output_stream; private ObjectInputStream input_stream; private String server_address = "127.0.0.1" ; private int server_port_number = 5040 ; JButton get_ball_button = new JButton( "Get Ball" ) ; public BallsClientPanel( String given_server_address, int given_server_port_number ) { server_address = given_server_address ; server_port_number = given_server_port_number ; JPanel operations_panel = new JPanel() ; get_ball_button.addActionListener( this ) ; operations_panel.add( get_ball_button ) ; operations_panel.setBackground( Color.BLACK ) ; setLayout( new BorderLayout() ) ; add( "South", operations_panel ) ; //add( "South", get_ball_button ) ; thread_to_run_balls_client = new Thread( this ) ; thread_to_run_balls_client.start(); } public void enable_ball_requesting() { ball_requesting_enabled = true ; } public void run() { try { // Step 1: Create a Socket to make connection set_message_text( "Connecting..." ); Socket socket = new Socket( InetAddress.getByName( server_address ), server_port_number ) ; // display connection information add_to_message_text( "Connected to: " + socket.getInetAddress().getHostName() ) ; // Step 2: Get the input and output streams for objects output_stream = new ObjectOutputStream( socket.getOutputStream() ); output_stream.flush(); // flush output to send header information input_stream = new ObjectInputStream( socket.getInputStream() ); add_to_message_text( " Streams OK" ); while ( client_thread_should_be_run == true ) { if ( ball_requesting_enabled == true ) { output_stream.writeObject( "Client wants a ball." ) ; output_stream.flush() ; try { Ball new_ball_from_server = (Ball) input_stream.readObject(); if ( balls_to_be_shown.size() >= 9 ) { // We'll delete the old balls. balls_to_be_shown.clear() ; } balls_to_be_shown.add( new_ball_from_server ) ; set_message_text( "FROM SERVER: " + new_ball_from_server ) ; ball_requesting_enabled = false ; } catch ( ClassNotFoundException classNotFoundException ) { System.out.print( "\nUnknown object type received" ); } } else { // No ball requesting command has been given // Let's wait a little bit try { Thread.sleep( 500 ) ; } catch ( InterruptedException caught_interrupted_exception ) { } } } // Step 4: Close connection add_to_message_text( "Closing..." ); output_stream.close(); input_stream.close(); socket.close(); } catch ( EOFException eofException ) { System.out.print( "\nServer terminated connection" ); } catch ( IOException ioException ) { ioException.printStackTrace(); } add_to_message_text( "Thread terminated." ) ; } // end of run() method public void stop_balls_client() { if ( thread_to_run_balls_client != null ) { thread_to_run_balls_client.interrupt() ; } client_thread_should_be_run = false ; } public void actionPerformed( ActionEvent event ) { if ( event.getSource() instanceof JButton ) { if ( event.getSource() == get_ball_button ) { enable_ball_requesting() ; } } } } class SocketBallsFrame extends JFrame implements ActionListener, WindowListener { public static final int FRAME_WIDTH = 660 ; public static final int FRAME_HEIGHT = 720 ; JButton server_button = new JButton( "RUN AS BALLS SERVER" ) ; JButton client_button = new JButton( "RUN AS BALLS CLIENT" ) ; JTextField server_address_field = new JTextField( "127.0.0.1", 30 ) ; JTextField server_port_number_field = new JTextField( "5040", 4 ) ; JPanel operations_panel ; BallsServerPanel balls_server_panel ; BallsClientPanel balls_client_panel ; public SocketBallsFrame() { setTitle( "SELECT APPLICATION OPERATION MODE" ) ; setSize( 500, 150 ) ; operations_panel = new JPanel( new GridLayout( 3, 2, 10, 10 ) ) ; //operations_panel.setBounds( 100, 50, 300, 150 ) ; server_button.addActionListener( this ) ; client_button.addActionListener( this ) ; operations_panel.add( server_button ) ; operations_panel.add( client_button ) ; operations_panel.add( new Label( "Server address (URL):" ) ) ; operations_panel.add( server_address_field ) ; operations_panel.add( new Label( "Server port number: " ) ) ; operations_panel.add( server_port_number_field ) ; getContentPane().add( operations_panel ) ; addWindowListener( this ) ; } public void actionPerformed( ActionEvent event ) { if ( event.getSource() instanceof JButton ) { if ( event.getSource() == server_button ) { balls_server_panel = new BallsServerPanel() ; setTitle( "SOCKET BALLS SERVER" ) ; setSize( FRAME_WIDTH, FRAME_HEIGHT ) ; getContentPane().remove( operations_panel ) ; getContentPane().add( balls_server_panel ) ; // The validate() method is used to cause a container // to lay out its subcomponents again. It should be // invoked when a container's subcomponents are modified // (added to or removed from the container, // or layout-related information changed) after the // container has been displayed. validate() ; } else if ( event.getSource() == client_button ) { balls_client_panel = new BallsClientPanel( server_address_field.getText(), Integer.parseInt( server_port_number_field.getText() ) ) ; setTitle( "SOCKET BALLS CLIENT" ) ; setSize( FRAME_WIDTH, FRAME_HEIGHT ) ; getContentPane().remove( operations_panel ) ; getContentPane().add( balls_client_panel ) ; validate() ; } } } public void windowClosing( WindowEvent event ) { if ( balls_server_panel != null ) { balls_server_panel.stop_balls_server() ; } if ( balls_client_panel != null ) { balls_client_panel.stop_balls_client() ; } setVisible( false ) ; System.exit( 0 ) ; } // Other methods required by the WindowListener interface: public void windowActivated( WindowEvent event ) {} public void windowClosed( WindowEvent event ) {} public void windowDeactivated( WindowEvent event ) {} public void windowDeiconified( WindowEvent event ) {} public void windowIconified( WindowEvent event ) {} public void windowOpened( WindowEvent event ) {} } public class SocketBallsMultiserverApplication { public static void main( String[] not_in_use ) { SocketBallsFrame socket_balls_frame = new SocketBallsFrame(); // socket_balls_frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); socket_balls_frame.setVisible( true ) ; } }