如何将对象从inputStream客户端返回到JavaFX控制器?

2022-07-23 00:00:00 multithreading server client java javafx

所以基本上我有一个MainConstroll类,它有每个按钮的方法。我还有一个服务器多客户端应用程序。在客户端,我有一个sendMessage方法,它将一个字符串和一个对象作为参数发送到outputStreams到服务器。

在相同的方法中,我有两个来自服务器的消息的inputStream和一个对象。问题是此方法运行在实现Run方法的Thread上,而我无法返回该对象。

我尝试创建一个静态类来保存这些参数,但在控制器类中调用时,getter为空。

实现此目标的最佳方法是什么?

public void onSaveButton(javafx.event.ActionEvent actionEvent) throws Exception {


    Parent root = null;
    Boolean type = false;
    String message = null;


    if (adminCheckbox.isSelected()) {
        root = FXMLLoader.load(getClass().getResource("/fxml/admin.fxml"));
        type = true;
        message = "Admin";
    }

    if (competitorCheckbox.isSelected()) {
        root = FXMLLoader.load(getClass().getResource("/fxml/competitor.fxml"));
        message = "Competitor";
    }

    PersonEntity personEntity = new PersonEntity();
    personEntity.setIdTeam(Integer.parseInt(teamField.getText()));
    personEntity.setType(type);
    personEntity.setUsername(usernameField.getText());

    client.sendMessageToServer(message, personEntity);

    System.out.println(Utils.getMessage());}

和客户端方法:

 public void sendMessageToServer(String message, Object object) throws Exception {

    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("Say something and the message will be sent to the server: ");

            //For receiving and sending data
            boolean isClose = false;

            while (!isClose) {
                try {

                    ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
                    ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());

                    if (message.equals("Bye")) {
                        isClose = true;
                    }

                    outputStream.writeObject(message);
                    outputStream.writeObject(object);

                    String messageFromServer = (String) inputStream.readObject();
                    //System.out.println(messageFromServer);

                    int index = messageFromServer.indexOf(' ');
                    String word = messageFromServer.substring(0, index);

                    if (messageFromServer.equals("Bye")) {
                        isClose = true;
                    }

                    if (!word.equals("LIST")) {
                        Object obj = (Object) inputStream.readObject();
                        Utils.setMessage(messageFromServer);
                        return;
                        //Utils.setObject(obj);
                        //System.out.println("IN FOR " + Utils.getMessage());

                    } else {
                        List<Object> list = (List<Object>) inputStream.readObject();
                        Utils.setMessage(messageFromServer);
                        Utils.setObjects(list);
                        return;

                    }

                } catch (IOException | ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

解决方案

我不知道您的服务器是什么,也不知道您的应用程序的其余部分。我知道您正在使用原始的TCP套接字使用自定义协议进行通信。

我所做的是从Java教程中获取类似示例的代码。该代码是来自Writing the Server Side of a Socket的Knock客户端服务器代码。基本的服务器代码没有改变,我只是用一个JavaFX用户界面替换了基于控制台的客户端。

未更改的服务器代码:

  • https://docs.oracle.com/javase/tutorial/networking/sockets/examples/KnockKnockProtocol.java

  • https://docs.oracle.com/javase/tutorial/networking/sockets/examples/KKMultiServer.java

  • https://docs.oracle.com/javase/tutorial/networking/sockets/examples/KKMultiServerThread.java

已更换控制台客户端:

  • https://docs.oracle.com/javase/tutorial/networking/sockets/examples/KnockKnockClient.java

我提供了两种不同的客户端实现;一种是在JavaFX应用程序线程上进行同步客户端调用,另一种是使用JavaFX任务进行异步客户端调用。

当前的基于任务的异步解决方案对于一个完整规模的生产系统来说不够健壮,因为它在技术上有可能丢失消息,并且它不能可靠地匹配请求和响应消息。但对于这样的演示来说,这是可以的。

为了简单执行,UI应用程序在运行时都会在预定义的端口上启动本地服务器,但您可以删除代码以从UI应用程序启动服务器,并在需要时从命令行运行服务器。

Knock Knock SyncClient.java

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

public class KnockKnockSyncClient {
    private Socket socket;
    private PrintWriter out;
    private BufferedReader in;

    public String connect(String host, int port) {
        try {
            socket = new Socket(host, port);

            out = new PrintWriter(
                    socket.getOutputStream(),
                    true
            );

            in = new BufferedReader(
                    new InputStreamReader(
                            socket.getInputStream()
                    )
            );

            return in.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    public String sendMessage(String request) {
        String response = null;

        try {
            out.println(request);
            response = in.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return response;
    }

    public void disconnect() {
        try {
            if (socket != null) {
                socket.close();
                socket = null;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Knock Knock SyncApp

import javafx.animation.FadeTransition;
import javafx.application.*;
import javafx.geometry.Insets;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class KnockKnockSyncApp extends Application {
    private static final String HOST = "localhost";
    private static final int PORT = 8809;

    public static final String QUIT_RESPONSE = "Bye.";

    private ExecutorService serverExecutor;
    private KnockKnockSyncClient client;

    private static final String CSS = """
            data:text/css,
            .root {
                -fx-font-size: 20;
            }
            .list-cell {
                -fx-alignment: baseline-right;
                -fx-text-fill: purple;
            }
            .list-cell:odd {
                -fx-alignment: baseline-left;
                -fx-text-fill: darkgreen;
            }
            """;

    @Override
    public void init() {
        serverExecutor = Executors.newSingleThreadExecutor(r -> new Thread(r, "KKServer"));
        serverExecutor.submit(
                () -> {
                    try {
                        KKMultiServer.main(new String[]{ PORT + "" });
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
        );

        client = new KnockKnockSyncClient();
    }

    @Override
    public void start(Stage stage) {
        ListView<String> messageView = new ListView<>();

        TextField inputField = new TextField();
        inputField.setPromptText("Enter a message for the server.");

        inputField.setOnAction(event -> {
            String request = inputField.getText();
            messageView.getItems().add(request);

            String response = client.sendMessage(request);
            messageView.getItems().add(response);

            messageView.scrollTo(Math.max(0, messageView.getItems().size() - 1));

            inputField.clear();

            if (QUIT_RESPONSE.equals(response)) {
                closeApp(inputField.getScene());
            }
        });

        VBox layout = new VBox(10,
                messageView,
                inputField
        );
        layout.setPadding(new Insets(10));
        layout.setPrefWidth(600);

        Scene scene = new Scene(layout);
        scene.getStylesheets().add(CSS);

        stage.setScene(scene);
        stage.show();

        inputField.requestFocus();

        String connectResponse = client.connect(HOST, PORT);
        if (connectResponse != null) {
            messageView.getItems().add(connectResponse);
        }
    }

    private void closeApp(Scene scene) {
        Parent root = scene.getRoot();
        root.setDisable(true);
        FadeTransition fade = new FadeTransition(Duration.seconds(1), root);
        fade.setToValue(0);
        fade.setOnFinished(e -> Platform.exit());
        fade.play();
    }

    @Override
    public void stop() {
        client.disconnect();
        serverExecutor.shutdown();
    }

    public static void main(String[] args) {
        launch();
    }
}

Knock Knock AsyncClient.java

import javafx.concurrent.Task;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;

public class KnockKnockAsyncClient extends Task<Void> {
    private final String host;
    private final int port;

    private final BlockingQueue<String> messageQueue = new LinkedBlockingDeque<>();

    public KnockKnockAsyncClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    @Override
    protected Void call() {
        try (
                Socket kkSocket = new Socket(host, port);
                PrintWriter out = new PrintWriter(kkSocket.getOutputStream(), true);
                BufferedReader in = new BufferedReader(
                        new InputStreamReader(kkSocket.getInputStream()));
        ) {
            String fromServer;
            String fromUser;

            while ((fromServer = in.readLine()) != null) {
                // this is not a completely robust implementation because updateMessage
                // can coalesce responses and there is no matching in send message calls
                // to responses.  A more robust implementation may allow matching of
                // requests and responses, perhaps via a message id.  It could also
                // replace the updateMessage call with storage of results in a collection
                // (e.g. an observable list) with thread safe notifications of response
                // arrivals, e.g. through CompleteableFutures and/or Platform.runLater calls.
                updateMessage(fromServer);
                System.out.println("Server: " + fromServer);
                if (fromServer.equals("Bye."))
                    break;

                fromUser = messageQueue.take();
                System.out.println("Client: " + fromUser);
                out.println(fromUser);
            }
        } catch (InterruptedException e) {
            if (isCancelled()) {
                updateMessage("Cancelled");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    public void sendMessage(String request) {
        messageQueue.add(request);
    }
}

Knock Knock AsyncApp.java

import javafx.animation.FadeTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

public class KnockKnockAsyncApp extends Application {
    private static final String HOST = "localhost";
    private static final int PORT = 8809;

    public static final String QUIT_RESPONSE = "Bye.";

    private ExecutorService serverExecutor;
    private ExecutorService clientExecutor;

    private static final String CSS = """
            data:text/css,
            .root {
                -fx-font-size: 20;
            }
            .list-cell {
                -fx-alignment: baseline-right;
                -fx-text-fill: purple;
            }
            .list-cell:odd {
                -fx-alignment: baseline-left;
                -fx-text-fill: darkgreen;
            }
            """;

    @Override
    public void init() {
        serverExecutor = Executors.newSingleThreadExecutor(r -> new Thread(r, "KKServer"));
        serverExecutor.submit(
                () -> {
                    try {
                        KKMultiServer.main(new String[]{ PORT + "" });
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
        );

        clientExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
            final AtomicInteger threadNum = new AtomicInteger(0);

            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r,"KKClient-" + threadNum.getAndAdd(1));
            }
        });
    }

    @Override
    public void start(Stage stage) {
        ListView<String> messageView = new ListView<>();

        KnockKnockAsyncClient client = new KnockKnockAsyncClient(HOST, PORT);
        // monitor and action responses from the server.
        client.messageProperty().addListener((observable, oldValue, response) -> {
            if (response != null) {
                logMessage(messageView, response);
            }

            if (QUIT_RESPONSE.equals(response)) {
                closeApp(messageView.getScene());
            }
        });

        TextField inputField = new TextField();
        inputField.setPromptText("Enter a message for the server.");

        inputField.setOnAction(event -> {
            String request = inputField.getText();
            logMessage(messageView, request);

            client.sendMessage(request);

            inputField.clear();
        });

        VBox layout = new VBox(10,
                messageView,
                inputField
        );
        layout.setPadding(new Insets(10));
        layout.setPrefWidth(600);

        Scene scene = new Scene(layout);
        scene.getStylesheets().add(CSS);

        stage.setScene(scene);
        stage.show();

        inputField.requestFocus();

        clientExecutor.submit(client);
    }

    private void logMessage(ListView<String> messageView, String request) {
        messageView.getItems().add(request);
        messageView.scrollTo(Math.max(0, messageView.getItems().size() - 1));
    }

    private void closeApp(Scene scene) {
        Parent root = scene.getRoot();
        root.setDisable(true);
        FadeTransition fade = new FadeTransition(Duration.seconds(1), root);
        fade.setToValue(0);
        fade.setOnFinished(e -> Platform.exit());
        fade.play();
    }

    @Override
    public void stop() {
        clientExecutor.shutdown();
        serverExecutor.shutdown();
    }

    public static void main(String[] args) {
        launch();
    }
}

相关文章