Search code examples
multithreadingsocketsjavafxserverclient

Java Multipleclients - server javafx


I have to create an electronic mail with JavaFX using socket, FXML. I need to create one server and 3 clients that are my three accounts and they must start in parallel. Every client must have an associated thread but my problem is: when I start the first client it works, so the FXML file opens. But when I try to open the second client Intellij shows a pop-up that says to me: Stop And Rerun. In my FXML I have a connect button in which I must choose one of my accounts and then my server says "connect". How can I fix this problem? Opening more than one client? If you don't understand I'll try to be more specific.

MailClient.java

import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class MailClient extends Application {

    @Override
    public void start(Stage stage) {
        try{
            Parent root = FXMLLoader.load(getClass().getResource("FXMLMailClient.fxml"));
            Scene scene = new Scene(root);
            stage.setScene(scene);
            stage.show();

        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        Socket s = new Socket("127.0.0.1", 4445);
        PrintWriter out = new PrintWriter(s.getOutputStream(), true);
        launch(args);
    }
}
Server.java

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Server {
    private int port = 4445;
    private ServerSocket s = null;

    private static ArrayList<ServerThread> clients = new ArrayList<>();
    private static ExecutorService pool = Executors.newFixedThreadPool(3);

    public void activate() throws IOException {
        try {
            s = new ServerSocket(port);
            while (true) {
                Socket s1 = s.accept();
                System.out.println("Server connect");
                ServerThread st1 = new ServerThread(s1);
                clients.add(st1);

                pool.execute(st1);
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }finally{
            s.close();
        }

    }

    public static void main(String[] args) throws IOException {
        Server s = new Server();
        s.activate();
    }
}
ServerThread.java

import java.io.IOException;
import java.net.Socket;

class ServerThread implements Runnable {

    private Socket socket = null;

    public ServerThread(Socket socket)  throws IOException {
        this.socket = socket;
    }
    @Override
    public void run() {
        System.out.println("Connected");
        // during the run, the following cases will be handled:
        // write an email, receive an email, delete an email.
    }
}
FXMLMailClientController.java

import java.io.IOException;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;

public class FXMLMailClientController {
    private boolean isConnected = false;

    @FXML
    private void handleConnectAction(ActionEvent event) throws IOException {
        while (!isConnected) {
            System.out.println("Client connect");
            isConnected = true;
        }
    }
}

FXMLMailClient.fxml

<?import java.lang.String?>
<?import javafx.collections.FXCollections?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ChoiceBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane id="AnchorPane" prefHeight="583.0" prefWidth="994.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.FXMLMailClientController">
    <children>
        <Button fx:id="connectClient" layoutX="70.0" layoutY="116.0" mnemonicParsing="false" onAction="#handleConnectAction" prefWidth="85.0" text="Connetti" />
        <Label fx:id="account" layoutX="383.0" layoutY="14.0" prefWidth="344.0" text="" />
        <ChoiceBox fx:id="choiceAccount" layoutY="83.0" prefHeight="25.0" prefWidth="225.0">
            <items>
                <FXCollections fx:factory="observableArrayList">
                    <String fx:value="email1" />
                    <String fx:value="email2" />
                    <String fx:value="email3" />
                </FXCollections>
            </items>
        </ChoiceBox>
    </children>
</AnchorPane>

Solution

  • The following MRE demonstrates a sever that supports multiple clients.
    JavaFx Application is typically the entry point. It should be used to start this example:

    import java.io.IOException;
    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Parent;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    public class MailClient extends Application {
    
        //todo add support to start / stop the server and al clents 
        private final static int PORT_NUMBER = 4445;
    
        @Override
        public void start(Stage stage) {
            try{
                Parent root = FXMLLoader.load(getClass().getResource("FXMLMailClient.fxml"));
                //todo path port number to FXMLMailClientController
                Scene scene = new Scene(root);
                stage.setScene(scene);
                stage.show();
    
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) throws IOException {
            new Server(PORT_NUMBER).activate(); //todo start server ftom gui 
            launch(args);
        }
    }
    

    The Server continuously accepts connections. Every client connected is handled by a separate thread (ServerThread).
    The Server simply prints out any message received from a client:

    import java.io.DataInputStream;
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class Server {
    
        private final ExecutorService pool;
        private final List<ServerThread> clients;
        private final int portNumber;
        private  boolean stop;
    
        Server(int portNumber) {
            this.portNumber = portNumber;
            pool = Executors.newFixedThreadPool(3);
            clients = new ArrayList<>();
        }
    
        private void runServer(){
    
            System.out.println("SERVER: Waiting for client");
            try{
                ServerSocket serverSocket = new ServerSocket(portNumber);
                stop = false;
    
                while(! stop){//do in loop to support multiple clients
                    Socket clientSocket = serverSocket.accept();
                    System.out.println("SERVER: client connected");
                    ServerThread st1 = new ServerThread(clientSocket);
                    pool.execute(st1);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public void stop(){
            for( ServerThread st : clients) {
                st.stopServerTread();
            }
            stop = true;
            pool.shutdown();
        }
    
        public void activate(){
            new Thread(()->runServer()).start();
        }
    }
    
    class ServerThread extends Thread {
    
        private Socket socket = null;
        private  boolean stop;
    
        public ServerThread(Socket socket) {
            this.socket = socket;
        }
    
        @Override
        public void run() {
    
            try{
                stop = false;
                DataInputStream in = new DataInputStream( socket.getInputStream() );
                String fromClient;
                while(!stop){
                    if((fromClient = in.readUTF()) != null) {
                        System.out.println("SERVER: recieved message - " + fromClient);
                    }
                }
    
            } catch (IOException e) {
                e.printStackTrace();;
            }
        }
    
        void stopServerTread(){
            stop = true;
        }
    }
    

    FXMLMailClient.fxml

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import java.lang.String?>
    <?import javafx.collections.FXCollections?>
    <?import javafx.scene.control.Button?>
    <?import javafx.scene.control.ChoiceBox?>
    <?import javafx.scene.control.Label?>
    <?import javafx.scene.layout.AnchorPane?>
    <?import javafx.scene.text.Font?>
    
    <AnchorPane id="AnchorPane" prefHeight="300.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/10.0.1" 
    xmlns:fx="http://javafx.com/fxml/1" fx:controller="tests.FXMLMailClientController"> 
      <children>
        <Button fx:id="connectClient" layoutX="70.0" layoutY="120.0" mnemonicParsing="false" 
                                        onAction="#handleConnectAction" prefWidth="85.0" text="Connetti" />  
        <ChoiceBox fx:id="choiceAccount" layoutY="85.0" prefHeight="25.0" prefWidth="225.0">
          <items>
            <FXCollections fx:factory="observableArrayList">
              <String fx:value="email1" />
              <String fx:value="email2" />
              <String fx:value="email3" />
            </FXCollections>
          </items>
        </ChoiceBox>
      </children>
    </AnchorPane>
    

    The controller constructs a new Client when a button is pressed:

    import java.io.IOException;
    import javafx.event.ActionEvent;
    import javafx.fxml.FXML;
    import javafx.scene.control.ChoiceBox;
    
    public class FXMLMailClientController {
    
        private final String hostName = "localhost";
        private final int portNumber = 4445;
    
        @FXML
        ChoiceBox<String> choiceAccount;
    
        @FXML
        private void handleConnectAction(ActionEvent event) throws IOException {
    
            if(choiceAccount.getSelectionModel().getSelectedItem() == null) return;
            new Client(choiceAccount.getSelectionModel().getSelectedItem(), hostName, portNumber).activate();
        }
    }
    

    The Client keeps sending time-stamped messages to the Server:

    import java.io.DataInputStream;
    import java.io.DataOutputStream;
    import java.io.IOException;
    import java.net.Socket;
    import java.net.UnknownHostException;
    import java.time.LocalTime;
    import java.time.format.DateTimeFormatter;
    import java.util.concurrent.TimeUnit;
    
    public class Client{
    
        private final String account, hostName;
        private final  int portNumber;
        private  boolean stop;
    
        Client(String account, String hostName, int portNumber )  {
            this.account = account;
            this.hostName = hostName;
            this.portNumber = portNumber;
        }
    
        private void runClient(){
            try {
                stop = false;
                Socket socket = new Socket(hostName, portNumber);
                DataInputStream in = new DataInputStream( socket.getInputStream() );
                DataOutputStream out = new DataOutputStream( socket.getOutputStream() );
                out.writeUTF(getClientMessage());
    
                while (! stop) {
                    TimeUnit.SECONDS.sleep(3);
                    out.writeUTF(getClientMessage());
                }
    
            } catch (UnknownHostException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
                System.exit(1);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    
        public void activate(){
            new Thread(()->runClient()).start();
        }
    
        public void stop(){
            stop = true;
        }
    
        private String getClientMessage(){
            LocalTime time = LocalTime.now();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
            return "from client # "+ account + " at "+ time.format(formatter);
        }
    }
    

    Note that in this simple demo the number of clients is not limited to 3.
    Also connecting two clients having the same account is possible.