0

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>
Peppino
  • 31
  • 8
  • 3
    Please [edit] the post to make it [mre] as explained in the previous similar question you asked. This is far from copy-paste-run, and I suspect it is not **M**. It is not clear how `Server` and `ServerThread` are used. `FXMLMailClientController implements Initializable` but does not have `initialize` method. None of the fields in `FXMLMailClientController` (formEmail, choiceAccount, connectClient, socket and more) is used. – c0der May 31 '20 at 17:03
  • 2
    Before dealing with 3 clients, start with one. Show a code that runs and successfully connects a client to the server. If you need to pass a reference (like socket) to the controller see [this](https://stackoverflow.com/a/61521157/3992939) – c0der May 31 '20 at 17:45
  • I modified the previous code. Now it works. There are no problems with one client, but when I try to run MailClient again it says: Stop and Rerun. How can I start the second/third client without this error? – Peppino May 31 '20 at 17:59
  • 1. How do I use the posted code to see one connected client ?. I don't know much about client-servers. If you get one working I might be able to help you get more working. When I run the client I see "Client connect" but the server never accepts. "Server connect" is not printed 2."_when I try to run MailClient again_" you mean you run `MailClient` main again ? – c0der Jun 01 '20 at 03:44
  • 1) you need to start main in Server.java. 2) you need to start main in MailClient.java 3) it will open a new window , you must choose an account in ChoiceBox and then click Connect. 4) now in the server appeare "Server Connect" , while in the client appeare "Client Connect". My aim is open 3 clients(one for each account) concurrently. @c0der – Peppino Jun 01 '20 at 09:33
  • I'll post [mre] of a server supporting multiple clients – c0der Jun 01 '20 at 09:48
  • I tried your example and it seems to work: all three clients behave the same, show the window and after selecting from the dropbox, they connect and display "Client connect" message in the console, while the server displays "Server connect" and "Connected" three times. However, I had to change `fx:controller="sample.FXMLMailClientController"` to `fx:controller="FXMLMailClientController"` since your FXMLMailClientController class is not in the sample package. But without that change, the window would not show even once. – Palo Jun 06 '20 at 20:18
  • thank you @Palo , I discovered that I don't put the tick on a setting of the IDE , so it doesn't show more than one client. – Peppino Jun 07 '20 at 08:42

1 Answers1

1

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.

c0der
  • 18,467
  • 6
  • 33
  • 65