在小型客户端 - 服务器应用程序中更改 JavaFX 标签文本

问题描述 投票:0回答:1

我创建了一个小型的客户端-服务器程序,其工作原理如下:客户端在五秒内向服务器发送一个随机数,然后服务器发送一个(或多或少著名的)报价给客户端。该随机数表示某些引号所在的 ServerClass 中 ArrayList 的索引。连接运行良好,客户收到报价,我可以轻松地使用 sysout 将其打印出来。 我现在的问题是,我想在一个小的 JavaFX / FXML 文件中将收到的报价显示为标签,并且当从服务器传入新报价时,文本应该每五秒更改一次。 QuotesClient 类(即客户端)也是相应 FXML 文件的控制器,但问题是因为我的 Client 类中负责处理传入 UDP 数据包的方法是静态的,当然我无法访问非静态在此方法中标记。

我也尝试将标签设为静态,但随后我收到一个 NullPointerException:

java.lang.NullPointerException
    at Aufgabe1.QuotesClient.main(QuotesClient.java:48)
    at Aufgabe1.QuotesApp.lambda$1(QuotesApp.java:32)
    at java.base/java.lang.Thread.run(Thread.java:844)

这是我的客户文件:

public class QuotesClient {
    private static final int BUFSIZE = 508;
    
    @FXML
    private static Label zitatLabel; //Thats the quoteLabel i wanna change

    public static void main(String[] args) throws UnknownHostException {
        var host = "localhost";
        var localPort = 40000;
        var serverPort = 50000; 
        try (var socket = new DatagramSocket(localPort)) {
            while(true) {
                Random random = new Random();
                int randomNumber = random.nextInt(3);
                System.out.println("Die Zufallszahl ist: " + randomNumber);
                var data = serialize(randomNumber).getBytes();
                
                //Send packages (random number for the server)
                var addr = InetAddress.getByName(host);
                var packetOut = new DatagramPacket(data, data.length, addr, serverPort);
                socket.send(packetOut);
                
                //Receive packeges (Quote-Objekts)
                var packetIn = new DatagramPacket(new byte[BUFSIZE], BUFSIZE);
                socket.receive(packetIn);
                
                var json = new String(packetIn.getData());
                Quote quote = deserialize(json);
                System.out.println("Quote: " + quote.getText());
                zitatLabel.setText(quote.getText()); //Throws the NullPointerException
               
                Thread.sleep(5000);
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }
    
    private static String serialize(int number) {
        var jsonb = JsonbBuilder.create();
        return jsonb.toJson(number);
    }
    
    private static Quote deserialize(String data) {
        var jsonb = JsonbBuilder.create();
        return jsonb.fromJson(data, Quote.class);
    }
    
}

我通过 QuotesApp.java 类启动我的整个程序。我还创建了两个单独的线程,一个用于服务器,一个用于客户端:

public class QuotesApp extends Application {
    
    @Override
    public void start(Stage primaryStage) throws Exception {
        FXMLLoader loader = new FXMLLoader(QuotesApp.class.getResource("/Aufgabe1/GUIQuotes.fxml"));
        Parent root = loader.load();
        Scene scene = new Scene(root);
        primaryStage.setTitle("UserLogin");
        primaryStage.setResizable(false);
        primaryStage.setScene(scene);
        primaryStage.show();
        
        
        // Starte den Server und den Client in separaten Threads
        Thread serverThread = new Thread(() -> {
            QuotesServer.main(new String[0]);
        });

        Thread clientThread = new Thread(() -> {
            try {
            QuotesClient.main(new String[0]);
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
        });

        serverThread.start();
        clientThread.start();        
    }

    public static void main(String[] args) {
        launch(args);
        
    }
}
java javafx network-programming udp client-server
1个回答
0
投票

@FXML

 加载 FXML 文件时,
FXMLLoader 注释的字段将在控制器
中初始化。控制器是由 
FXMLLoader
 创建的特定对象。只有尝试从该特定对象内访问这些字段才有意义。

在您的情况下,您的服务器和客户端甚至不由对象表示(因为它们仅使用静态方法)。您首先需要以面向对象的方式编写此代码,其中服务器和客户端由实际对象表示。然后,您需要设计应用程序的 UI 部分,以便可以通过客户端值的更改来更新 UI;类似 MVC 的方法通常是实现此目的的最佳方法。

这是一个简单的例子。我使用 Jackson 进行 JSON 反序列化、普通套接字和服务器套接字,因为我更了解这些技术。相同的结构可以使用不同的底层技术。

首先,这是

Quote

 类,简单地实现为 
record
:

Quote.java

package org.jamesd.examples.quoteserver; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; public record Quote(String text, String author) { @JsonCreator public Quote(@JsonProperty("text") String text, @JsonProperty("author") String author) { this.text = text; this.author = author; } }
这是一个

QuoteServer

。它有一个 
serveQuotes()
 方法,它无限期地阻塞(至少直到服务器关闭为止)并将处理请求交给执行器。请求只需发送一个 
int
 代表他们想要哪个报价(在真实的应用程序中,您将处理无效的报价 ID 等):

QuoteServer.java


package org.jamesd.examples.quoteserver; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.BufferedWriter; import java.io.DataInputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class QuoteServer implements AutoCloseable{ public static final int PORT = 5555; private final ServerSocket serverSocket; private final ObjectMapper objectMapper = new ObjectMapper(); private final ExecutorService exec = Executors.newCachedThreadPool(); private final List<Quote> quotes = List.of( new Quote( """ There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle. """, "Albert Einstein"), new Quote( """ Good judgment comes from experience, and experience comes from bad judgment. """, "Rita Mae Brown" ), new Quote( """ Imagination was given to man to compensate him for what he is not, and a sense of humor was provided to console him for what he is. """, "Oscar Wilde" ) ); public QuoteServer() throws IOException { serverSocket = new ServerSocket(PORT); } public void serveQuotes() { try { while (!serverSocket.isClosed()) { Socket socket = serverSocket.accept(); exec.execute(() -> handleRequest(socket)); } } catch (SocketException sockExc) { if (serverSocket.isClosed()) { System.out.println("Socket closed"); } else { sockExc.printStackTrace(); } } catch (IOException exc) { exc.printStackTrace(); } } private void handleRequest(Socket socket) { try { DataInputStream input = new DataInputStream(socket.getInputStream()); int index = input.readInt(); Quote quote = quotes.get(index); String json = serialize(quote); BufferedWriter output = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); output.write(json); output.flush(); socket.close(); } catch (IOException e) { throw new RuntimeException(e); } } private String serialize(Quote quote) throws JsonProcessingException { return objectMapper.writeValueAsString(quote); } @Override public void close() throws Exception { serverSocket.close(); } }
这是一个

QuoteClient

课程。这只有一种一次性方法,获取 
int
 并返回从服务器发送的 
Quote
。同样,在现实生活中,您可能会添加更多功能。

QuoteClient.java


package org.jamesd.examples.quoteserver; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; public class QuoteClient { private final ObjectMapper objectMapper = new ObjectMapper(); public Quote getQuote(int index) { try { Socket socket = new Socket("localhost", QuoteServer.PORT); DataOutputStream output = new DataOutputStream(socket.getOutputStream()); output.writeInt(index); Quote quote = objectMapper.readValue(socket.getInputStream(), Quote.class); socket.close(); return quote; } catch (IOException e) { throw new RuntimeException(e); } } }
对于 UI 部分,使用某种模型-视图-控制器 (MVC) 或类似的方法。其中最重要的方面是将应用程序中显示的数据分离出来放入模型类中,并让视图通过观察模型来更新。 (MVC 的不同风格因视图和控制器之间的划分方式而异。)

在这种情况下,模型从服务器获取数据,因此需要客户端。要定期更新(例如每五秒),您可以使用

ScheduledService

 创建通过客户端请求数据的 
Task
。该服务的 
lastValueProperty()
 保存从任务返回的最后一个值,即从服务器检索的最后一个值。所以模型可以简单地看起来像:

Model.java


package org.jamesd.examples.quoteserver; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.concurrent.ScheduledService; import javafx.concurrent.Task; import javafx.util.Duration; import java.util.Random; public class Model { private final QuoteClient quoteClient = new QuoteClient(); private final ScheduledService<Quote> service; public Model() { service = new ScheduledService<>() { private final Random rng = new Random(); @Override protected Task<Quote> createTask() { final int quoteIndex = rng.nextInt(3); return new Task<>() { @Override protected Quote call() { return quoteClient.getQuote(quoteIndex); } }; } }; service.setPeriod(Duration.seconds(5)); service.start(); } public ReadOnlyObjectProperty<Quote> currentQuoteProperty() { return service.lastValueProperty(); } }
这是一个简单的 FXML 文件。它只有一些标签(包括一个用于引用文本的标签和一个用于其作者的标签):

Quotes.fxml


<?xml version="1.0" encoding="UTF-8"?> <?import javafx.geometry.Insets?> <?import javafx.scene.control.Label?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.control.Button?> <?import javafx.scene.layout.HBox?> <VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml" fx:controller="org.jamesd.examples.quoteserver.QuoteController"> <padding> <Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/> </padding> <Label fx:id="quoteLabel"/> <HBox spacing="5"><Label text=" - "/><Label fx:id="authorLabel" /></HBox> </VBox>
这是对应的控制器。它需要访问模型,因此它有一个 

setModel(Model model)

 方法。当它收到新模型时,它只是将标签的文本绑定到模型中的值:

QuoteController.java


package org.jamesd.examples.quoteserver; import javafx.fxml.FXML; import javafx.scene.control.Label; public class QuoteController { @FXML private Label quoteLabel ; @FXML private Label authorLabel; public void setModel(Model model) { quoteLabel.textProperty().bind(model.currentQuoteProperty().map(Quote::text)); authorLabel.textProperty().bind(model.currentQuoteProperty().map(Quote::author)); } }
最后是应用程序类。这会在 

init()

 方法中启动一个服务器(在现实生活中您可能不会这样做;服务器不会位于同一个 JVM 中)并在后台线程中运行它。它还创建了一个
Model
。加载 FXML 后,模型将传递到控制器。

QuoteApplication.java


package org.jamesd.examples.quoteserver; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.stage.Stage; import java.io.IOException; public class QuoteApplication extends Application { private Model model; private QuoteServer server ; @Override public void init() throws IOException { server = new QuoteServer(); Thread serverThread = new Thread(server::serveQuotes); serverThread.setDaemon(true); serverThread.start(); model = new Model(); } @Override public void start(Stage stage) throws IOException { FXMLLoader fxmlLoader = new FXMLLoader(QuoteApplication.class.getResource("Quotes.fxml")); Scene scene = new Scene(fxmlLoader.load(), 800, 500); QuoteController controller = fxmlLoader.getController(); controller.setModel(model); stage.setScene(scene); stage.show(); } @Override public void stop() throws Exception { server.close(); } public static void main(String[] args) { launch(); } }
    
© www.soinside.com 2019 - 2024. All rights reserved.