我正在从编写Java Swing应用程序转换到JavaFX,以编写基于Java的现代GUI应用程序。
我想知道创建基于网络的可重用线程服务的最佳方法。我编写网络服务的方式是使用控制器类(通过Net-beans GUI从FXML生成)。我通过一个名为' transmitter
'的私人服务成员将线程逻辑放在这里。我通过开始/停止按钮的事件回调来连接启动/停止逻辑
基于网络的线程实现为javafx服务 - 我这样做是因为我想在目标地址更改时重新启动服务/线程。这似乎是建议的方法,而不是独立的任务。
现在网络服务非常简单,它所做的就是使用一些GUI小部件来配置数据包,每秒一次传输到主机/端口。我只需要在主机/端口小部件更改时重新启动服务,但是如果网络服务正在运行,我想在不中断/重新启动DatagramSocket的情况下修改数据包。我有疑问并需要一些指导的地方是:
以下显示的是我的控制器类中最相关的部分:
/**
* FXML Controller class
*
* @author johnc
*/
public class OpMessageServerController implements Initializable {
@FXML
private Text mCurrentDateTimeText;
@FXML
private Label mApplicationStatus;
@FXML
private ComboBox<DiscreteStatus> mPofDS;
@FXML
private ComboBox<PhaseOfFlightFMS> mPofFMS;
@FXML
private ComboBox<DiscreteStatus> mTailNumberDS;
@FXML
private ComboBox<DiscreteStatus> mConfigTableDS;
@FXML
private ComboBox<DiscreteStatus> mDateTimeDS;
@FXML
private TextField mEpicPN;
@FXML
private TextField mConfigTablePNHash;
@FXML
private TextField mTailNumber;
@FXML
private ComboBox<DiscreteStatus> mTopLevelPNDS;
@FXML
private Button mStartStopButton;
@FXML
private ComboBox<String> mDLMUHostSpec;
@FXML
private CheckBox connectionStatusC1;
@FXML
private CheckBox wsuConnectionStatus;
@FXML
private CheckBox connectionStatusC4;
@FXML
private CheckBox connectionStatusC3;
@FXML
private CheckBox connectionStatusC2;
@FXML
private CheckBox dlmuwConnectionStatus;
private Service<Void> transmitter;
/**
* Initializes the controller class.
* @param url
* @param rb
*/
@Override
public void initialize(URL url, ResourceBundle rb) {
mPofDS.setItems(FXCollections.observableArrayList(DiscreteStatus.values()));
mPofDS.getSelectionModel().selectFirst();
mPofFMS.setItems(FXCollections.observableArrayList(PhaseOfFlightFMS.values()));
mPofFMS.getSelectionModel().selectFirst();
mTailNumberDS.setItems(FXCollections.observableArrayList(DiscreteStatus.values()));
mTailNumberDS.getSelectionModel().selectFirst();
mConfigTableDS.setItems(FXCollections.observableArrayList(DiscreteStatus.values()));
mConfigTableDS.getSelectionModel().selectFirst();
mDateTimeDS.setItems(FXCollections.observableArrayList(DiscreteStatus.values()));
mDateTimeDS.getSelectionModel().selectFirst();
mTopLevelPNDS.setItems(FXCollections.observableArrayList(DiscreteStatus.values()));
mTopLevelPNDS.getSelectionModel().selectFirst();
// mDLMUHostSpec.setItems(FXCollections.observableArrayList(
// FXCollections.observableArrayList("localhost:1234", "192.168.200.2:1234")));
// add event handler here to update the current date/time label
// this should also update the transmit datastructure
final Timeline timeline = new Timeline(new KeyFrame(
Duration.seconds(1), (ActionEvent event) -> {
LocalDateTime currentDateTime = LocalDateTime.now();
mCurrentDateTimeText.setText(currentDateTime.format(
DateTimeFormatter.ofPattern("kk:mm:ss uuuu")));
}));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
// create a service.
transmitter = new Service() {
@Override
protected Task createTask() {
return new Task<Void>() {
@Override
protected Void call() throws InterruptedException {
updateMessage("Running...");
updateProgress(0, 10);
DatagramSocket sock = null;
while (!isCancelled()) {
try {
if (sock == null) {
DatagramSocket sock = new DatagramSocket();
}
} catch (SocketException ex) {
Logger.getLogger(OpMessageServerController.class.getName()).log(Level.SEVERE, null, ex);
}
//Block the thread for a short time, but be sure
//to check the InterruptedException for cancellation
OpSupportMessage opSupportMessage = new OpSupportMessage(
DiscreteStatus.NormalOperation,
PhaseOfFlightFMS.Cruise,
DiscreteStatus.NormalOperation,
"TAILNUM",
DiscreteStatus.NormalOperation);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
String[] specParts = mDLMUHostSpec.getValue().split(":");
if (specParts.length == 2) {
try {
opSupportMessage.write(bos);
byte[] buff = bos.toByteArray();
DatagramPacket packet = new DatagramPacket(
buff, buff.length, InetAddress.getByName(
specParts[0]), Integer.parseInt(specParts[1]));
mSocket.send(packet);
Thread.sleep(1000);
} catch (IOException ex) {
} catch (InterruptedException interrupted) {
if (isCancelled()) {
updateMessage("Cancelled");
break;
}
}
}
}
updateMessage("Cancelled");
return null;
}
@Override
protected void succeeded() {
System.out.println("Scanning completed.");
}
@Override
protected void failed() {
System.out.println("Scanning failed.");
}
@Override
protected void running() {
System.out.println("Scanning started.");
}
@Override
protected void cancelled() {
System.out.println("Scanning cancelled.");
}
private void DatagramSocket() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
};
}
};
mApplicationStatus.textProperty().bind(transmitter.messageProperty());
};
@FXML
private void startStopButtonAction(ActionEvent event) {
if (!transmitter.isRunning()) {
transmitter.reset();
transmitter.start();
}
}
…
}
答案 0 :(得分:3)
背景
这个答案是基于对问题的一系列评论,它有点摇摆,没有提供针对问题中代码的解决方案,也没有解决问题中的一些概念,如低级UDP基于套接字的通信系统 - 为此道歉。
示例解决方案项目
我使用基于Web套接字的通信做了一个JavaFX应用程序概念证明:javafx-websocket-test。也许那里的一些概念可能会对您有所帮助,特别是使用它的client JavaFX Task and Service code和sample client application and controller。
该项目确实在可执行的实现中演示了James_D链接的Adam Bien's article on JavaFX Integration Strategies中概述的一些通信原则,例如:
此外,该示例显示了网络服务和JavaFX UI之间的交互,JavaFX UI向服务发出异步请求并处理来自它的异步响应。
我确实记得看似简单的Java Web套接字API确实包含了一些问题。它只是一个概念证明,因此请小心使用它作为强大的网络服务的基础。
评论和想法
由于以下原因,这实际上是回答IMO的难题:
有许多细微之处需要处理,例如:
即使单一尺寸适合所有通信模式也很困难,但标准是&#34;可以调整适合许多需求的通信模型。例如,类似于基于浏览器的网络模型中的http ajax calls或NetConnections for flash。这些似乎足以满足各种各样的需求。虽然它们当然不是最佳选择,否则就不会创建诸如网络套接字或http直播之类的替代系统。
理想情况下,对于JavaFX client =&gt;,会有一个标准化的API,例如jQuery.ajax()。服务器通信,但我还没有看到任何人创建JavaFX相当于那种API。
与其他核心JavaFX API不同,这种用于网络通信的标准化高级接口目前尚未以现成的形式存在。但是,有许多库和函数可用作开发自己服务的基本构建块;也许甚至太多而不能合理地处理。
请注意,大多数更高级别的网络协议库(例如Tyrus web socket implementation或Apache HTTP components基础JAX-RS provider都有自己的内部线程池进行通信。像netty这样的系统基于nio并且是事件驱动而不是线程管理。您的JavaFX网络客户端服务是以下两种方式之一:
一个关键且令人困惑的事情是JavaFX应用程序代码应始终以异步方式执行网络通信。对于非阻塞I / O,调用已经是异步,因此不一定需要包装器任务。对于阻止I / O,您不希望阻止UI线程,因此在其自己的线程中运行的Task包装器可以防止发生这种情况。
有人会认为这会使非阻塞I / O调用变得更简单,但事实并非如此,因为JDK的非阻塞I / O API水平相当低且相当棘手编码到。它不适合高级应用程序代码。
通常,应用程序代码最好使用更高级别的库,例如JAX-RS,Web套接字或akka(或者,最好是它们之上的一个层),它们在内部管理阻塞或通信的细节。非阻塞方式并提供用于发送和接收消息的事件驱动API。各个消息事件可以包装在JavaFX Task中以进行异步处理。因此,从JavaFX应用程序的角度来看,一切都是事件驱动的,没有任何阻塞,并且无论底层通信协议和阻塞/非阻塞通信基础设施如何,相同的应用程序API都能正常工作。
感谢概念验证应用,这将非常有用,但有一点有点模糊,就是如何安全地将GUI更改安全地传递给正在运行的服务线程。 HelloService似乎使用了&#39;名称&#39;简单的字符串属性,用于在启动之前将更改从GUI传递到服务。我想知道如何以线程安全的方式将UI更改传递给正在运行的后台服务。通过某种类型或消息api或许?
具有固定最大大小的BlockingQueue在队列已满时拒绝其他请求,可用于从基于JavaFX线程的代码到消费者服务的通信。它是经典producer-consumer problem的一个相当优雅的解决方案。
当然,你可以跳过阻塞队列并继续创建异常任务ad-nauseum,这对于低容量通信来说很好,但是可能导致有限的线程资源缺乏以进行高容量通信。处理这种情况的一种标准方法是使用来自管理线程池的ExecutorService的Executors。执行程序服务的线程池可以定义为bounded to a max number of threads,并在内部使用无限制队列,如果所有线程都忙,则消息堆积起来。这样您就不需要定义自己的阻塞队列,只需发出异步服务请求,如果可以,则立即在线程中处理它们,如果不能,请求会在内部队列中堆积。
这实际上是JavaFX Service的工作方式:
默认情况下,服务使用具有一些未指定的默认或最大线程池大小的线程池Executor。这样做是为了使天真的代码不会通过创建数千个线程来完全淹没系统。
和:
如果在服务上指定了Executor,那么它将用于实际执行服务。否则,将创建并执行守护程序线程。如果您希望创建非守护程序线程,请指定自定义执行程序(例如,您可以使用带有自定义ThreadFactory的ThreadPoolExecutor)。
简单的BlockedQueue消息传递不适合的更复杂的解决方案将使用基于主题的消息队列样式解决方案,例如基于Java的STOMP客户端,例如此kaazing example。
将消息信息提供给服务只是要求的一部分,本质上是执行异步消息发送。您还需要处理返回的响应。要做到这一点,有两种选择:
要使响应处理程序逻辑动态化并可由调用者调整,您可以将处理程序函数作为lambda函数传递,以便在使用Platform.runLater进行原始调用时成功执行。
如果将调用包装在Task或Service中,并使用onSucceeded函数,则不需要runLater调用,因为实现将确保在任务完成后在JavaFX线程上调用onSucceeded处理程序
请注意,网络请求和响应通常需要对可序列化流中的数据编组和解组进行一些转换。一些更高级别的网络API(如JAX-RS或Web套接字提供程序)提供了接口和实用程序,可以为您执行某些工作,通常使用特定库进行不同类型的转换,例如JAXB用于XML序列化Jackson用于JSON序列化。
略有关联的信息和进一步的想法
接下来可能有点偏离主题,但这是BlockingQueue and Task interaction的一个示例,它不是网络服务,但它确实演示了在生产者/消费者情况下使用队列,具有被动UI和进展监测。
另一件令人感兴趣的事情(至少对我而言)是基于Akka的JavaFX客户端与服务器通信解决方案。这似乎是传统的http / rest / soap / rmi调用或基于消息队列的处理的一个很好的替代方案。 Akka本质上是一个基于事件的容错异步并发通信解决方案,因此它似乎是基于UI的框架(如JavaFX)的良好匹配,允许开发人员在适当的抽象层进行处理。但我还没有看到一个依赖于Akka的基于JavaFX的消息传递客户端。
答案 1 :(得分:2)
我想知道创建基于网络的最佳方法 可重复使用的线程服务。我编写网络服务的方式是 使用控制器类(通过Net-bean从FXML生成) GUI)。我通过私人服务成员将线程逻辑放在这里 命名的发射器&#39;我通过连线启动/停止逻辑 开始/停止按钮的事件回调。
我谦卑地建议您将网络服务和GUI控制器作为单独的项目进行开发。
我希望网络服务在自己的容器或虚拟机中作为守护程序/后台线程运行。该组织的优势在于它使您的服务器远离JavaFX事件循环和应用程序线程的变幻莫测。您将要设计服务以识别管理命令和/或中断来自控制器的请求。您可以将您的网络服务开发为REST或任何您想要的内容,而无需知道如何将其转换为JavaFX应用程序线程。
然后,我将GUI控制器作为单独的GUI应用程序在同一进程中运行,或者,如果需要远程管理,则在单独的JVM中运行(并使用IPC发送/接收管理消息)。
TL; DR:如果是我,我会抵制将网络服务编程为JavaFX应用程序的诱惑。