【问题标题】:Java Multipleclients - server javafxJava 多客户端 - 服务器 javafx
【发布时间】:2020-09-18 21:46:15
【问题描述】:

我必须使用套接字 FXML 使用 JavaFX 创建电子邮件。我需要创建一台服务器和 3 个客户端,它们是我的三个帐户,它们必须并行启动。每个客户端都必须有一个关联的线程,但我的问题是:当我启动第一个客户端时它工作,所以 FXML 文件打开。但是当我尝试打开第二个客户端时,Intellij 显示一个弹出窗口,告诉我:停止并重新运行。在我的 FXML 中,我有一个连接按钮,我必须在其中选择一个帐户,然后我的服务器会显示“连接”。我该如何解决这个问题?开设多个客户?如果你不明白,我会尝试更具体。

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>

【问题讨论】:

  • 请edit 将该帖子设为minimal reproducible example,如您之前提出的类似问题中所述。这远非复制粘贴运行,我怀疑它不是 M。目前尚不清楚Server 和ServerThread 是如何使用的。 FXMLMailClientController implements Initializable 但没有 initialize 方法。 FXMLMailClientController 中的所有字段(formEmail、choiceAccount、connectClient、socket 等)均未使用。
  • 在与3个客户打交道之前,先从一个开始。显示运行并成功将客户端连接到服务器的代码。如果您需要将引用(如套接字)传递给控制器​​,请参阅this
  • 我修改了之前的代码。现在它起作用了。一个客户端没有问题,但是当我再次尝试运行 MailClient 时,它说:停止并重新运行。如何在没有此错误的情况下启动第二个/第三个客户端?
  • 1.如何使用发布的代码查看一个连接的客户端?我对客户端服务器了解不多。如果你得到一份工作,我也许可以帮助你得到更多的工作。当我运行客户端时,我看到“客户端连接”,但服务器从不接受。 “服务器连接”未打印 2.“当我再次尝试运行 MailClient 时”你的意思是你再次运行 MailClient main ?
  • 1) 你需要在 Server.java 中启动 main。 2)您需要在 MailClient.java 中启动 main 3)它将打开一个新窗口,您必须在 ChoiceBox 中选择一个帐户,然后单击连接。 4) 现在在服务器中出现“Server Connect”,而在客户端中出现“Client Connect”。我的目标是同时打开 3 个客户(每个帐户一个)。 @c0der

标签: multithreading sockets javafx server client


【解决方案1】:

以下 MRE 演示了支持多个客户端的服务器。
JavaFx Application 通常是入口点。应该用它来启动这个例子:

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);
    }
}

Server 不断接受连接。每个连接的客户端都由一个单独的线程 (ServerThread) 处理。
Server 简单地打印出从客户端收到的任何消息:

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>

控制器在按下按钮时构造一个新的Client:

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();
    }
}

Client 不断向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);
    }
}

请注意,在这个简单的演示中,客户端的数量不限于 3 个。
也可以连接具有相同帐户的两个客户端。

【讨论】:

  • 谢谢你,伙计,对不起我的英语!
  • 没问题。我很高兴它有帮助。
猜你喜欢
  • 1970-01-01
  • 2016-04-09
  • 1970-01-01
  • 2017-07-06
  • 2015-02-19
  • 2015-01-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多