2015-12-30 40 views
1

我正在从编写Java Swing应用程序转换到JavaFX以编写基于Java的现代GUI应用程序。在GUI中与JavaFX网络服务交互

我想知道创建基于网络的可重用线程服务的最佳方法。我编写网络服务的方式是使用一个控制器类(通过Net-beans GUI从FXML生成)。我通过名为'transmitter'的私人服务成员将线程逻辑放在这里,并通过Start/Stop按钮的事件回调连接了启动/停止逻辑。
基于网络的线程被实现为javafx服务 - 我这样做是因为我想在目标地址更改时重新启动服务/线程。这似乎是替代独立任务的推荐方法。

网络服务现在非常简单,只需使用一些GUI小部件即可将数据包配置为每秒传输一次主机/端口。我只需要在主机/端口小部件发生变化时重新启动服务,但是如果网络服务正在运行,我想在不中断/重新启动DatagramSocket的情况下修改数据包。我有疑问并需要一些指导的地方是:

  1. 在基于FXML的应用程序中线程化网络线程的建议方法是什么?一个例子将不胜感激。
  2. 如何安全地将来自GUI小部件的更改(通过其执行回调的 动作)与正在运行的服务类进行通信?

下面显示的是我的控制器类的最相关的部分:

/** 
* 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

当然,您并不是说您的GUI应用程序代表网络上的客户端提供网络服务?你想在服务器上使用什么GUI? – scottb

+0

@scottb不,这是一个简单的gui客户端应用程序,它包含一个UDP发送线程,我只需要能够控制出去的数据包和这些数据包的内容。其中一个GUI控件“mDLMUHostSpec”包含一个可编辑的IP /端口,其他控件都是配置数据包的内容,如果IP /端口发生变化,则需要停止并重新启动现有服务。如果GUI中的其他参数发生变化(这会影响客户端服务传输的数据报(不会与服务器混淆,因为我认为您可能已经解释过)),但这些参数需要安全更新。 – johnco3

+0

我使用基于Web套接字的通信[javafx-websocket-test](https://github.com/jewelsea/javafx-websocket-test)做了一个JavaFX应用程序的概念验证。也许有些概念可以帮助你,但我记得看似简单的Java web socket API确实包含了一些陷阱。 – jewelsea

回答

2

背景

这个答案是基于对这一问题的意见的集合,它漫步了一下,并没有提供有针对性的在问题的代码的解决方案,并没有解决一些概念诸如基于UDP套接字的低级通信系统的问题 - 对此表示歉意。

样品溶液项目

我没有使用基于网络套接字通信一个JavaFX应用程序的概念验证:javafx-websocket-test。也许一些概念可以帮助你,尤其是使用它的client JavaFX Task and Service codesample client application and controller

该项目确实说明,在可执行的实施,一些在Adam Bien's article on JavaFX Integration Strategies概述的通信原则James_D地连接,例如:

  1. 在JavaFX服务中设置Web套接字端点。
  2. 在异步JavaFX任务中包装每个通信交互。
  3. 使用异步事件回调将成功和失败结果分流回UI。

此外,该示例显示了网络服务和JavaFX UI之间的交互,JavaFX UI向服务发出异步请求并处理异步响应。

我记得看似简单的Java web socket API确实包含一些问题。这只是一个概念验证,所以请谨慎使用它作为强大网络服务的基础。

评论和想法

这实际上是一个必然棘手的问题IMO回答,由于这些原因:

  1. 有许多形式的网络通信,其中一些适合不同应用。
  2. 目前没有将网络服务与JavaFX应用程序集成的标准或最佳实践。
  3. 通过UI状态监视和异常处理提供强大的网络连接通常不像看起来那样直截了当,而且很容易出错。

有许多细微之处进行处理,如:

  • 什么在通信故障的情况下怎么办?
  • 如果应用程序以比网络或服务器可以处理的速度更快的速度发出请求,该怎么办?
  • 如果用户在消息未完成时关闭应用程序,会发生什么情况?
  • 如何确保在长时间的通信过程中UI不会被冻结?
  • 如何提供UI反馈,表明冗长的网络处理正在进行中?
  • 正在使用什么底层通信技术?
  • 底层通信是有状态还是无状态?
  • 通信是否为非阻塞和事件驱动或阻塞?
  • 如何序列化和反序列化传输数据?

尽管适用于所有通信模式的单一尺寸将很困难,但可以调整适合多种需求的“标准”通信模型。例如类似于基于浏览器的网络模型中的http ajax callsNetConnections for flash。这些似乎足以满足各种各样的需求。尽管当然,它们并不是最佳的,否则不会创建诸如网络套接字或HTTP直播等替代系统。

理想情况下,对于JavaFX客户端=>服务器通信,将会有一个类似jQuery.ajax()的单个标准化API,但我还没有看到任何人创建了与该API类似的JavaFX。

与其他核心JavaFX API不同,此类用于网络通信的标准化高级接口目前不存在现成的形式。但是,有大量的库和函数可用作开发自己的服务的基本构建块;也许甚至太多以合理的过程。

请注意,大多数更高级别的网络协议库(例如Tyrus web socket implementationApache HTTP components)的底层JAX-RS provider都有自己的用于通信的内部线程池。像netty这样的系统基于nio,并且是事件驱动的而不是线程管理的。您的JavaFX的网络客户服务是什么这两件事情之一:

  • 对于non-blocking I/O它发出异步调用,钩住响应事件,并通过中继Platform.runLater他们带回的JavaFX。
  • 为了阻止I/O,它使用隐式或显式执行程序服务池产生一个任务或服务的线程来管理UI交互,但不管实际的网络通信。

一个关键和令人困惑的事情是,JavaFX应用程序代码应该总是以异步方式执行网络通信。对于非阻塞I/O,调用已经是异步的,所以不需要包装任务。为了阻止I/O,你不想阻塞UI线程,所以在它自己的线程中运行的Task包装可以防止发生这种情况。

有人会认为这会使非阻塞I/O调用变得更简单,但它并不是真的,因为JDK的非阻塞I/O API是相当低级的,并且编码起来相当棘手。它不适合高级应用程序代码。

通常,应用程序代码最好使用更高级别的库,如JAX-RS,Web套接字或akka(或者,最好是位于它们之上的层),它们在内部管理通信的细节,或非阻塞的方式,并提供用于发送和接收消息的事件驱动的API。单个消息事件可以封装在JavaFX Task中进行异步处理。因此,从JavaFX应用程序的角度来看,所有事情都是事件驱动的,没有任何障碍,同一个应用程序API工作,而不管底层通信协议和阻塞/非阻塞通信基础设施。

感谢您的概念证明应用程序,这将是非常有用的,但有一点是模糊的是,如何安全地将GUI更改安全地传达到正在运行的服务线程。看起来,HelloService使用'name'简单字符串属性在GUI启动之前将更改从GUI传递给服务。我想知道如何以线程安全的方式将UI更改传递给正在运行的后台服务。也许通过某种类型或消息API?

BlockingQueue具有固定最大大小,即拒绝额外请求时在队列满可以被用于通信从基于JavaFX的线程代码给消费者的服务。这是经典producer-consumer problem合理优雅的解决方案。

当然,您可以跳过阻塞队列并继续创建异步任务ad-nauseum,这适用于低容量通信,但可能导致大量通信的线程资源不足。处理该问题的一种标准方法是使用Executors中的ExecutorService,它管理线程池。执行程序服务的线程池可以定义为bounded to a max number of threads,并在内部使用一个无限的队列,在所有线程都忙的情况下消息堆积起来。这样您就不需要定义自己的阻塞队列,只需发出异步服务请求,并在线程中立即处理这些请求,或者在内部队列中请求堆积的情况下(如果不能)。

这实际上是一个JavaFX Service的工作方式:

该服务默认情况下使用一些未指定的默认或最大线程池大小线程池的Executor。这样做是为了让幼稚的代码不会通过创建数千个线程来完全淹没系统。

和:

如果该服务被指定执行人,那么它将被用于实际执行的服务。否则,守护进程线程将被创建并执行。如果您希望创建非守护线程,则指定一个自定义的执行程序(例如,您可以使用带有自定义ThreadFactory的ThreadPoolExecutor)。

更先进的解决方案,其中一个简单的BlockedQueue消息是不恰当会使用基于主题消息队列式的解决方案,例如,一个基于Java的客户端STOMP像这样kaazing example

获取到服务的消息的信息只是要求一部分,基本上是做一个异步消息发送。您还需要处理返回的响应。要做到这一点,有两种选择:

  1. 您将每个请求建模为单独的任务,并且onSuccess和onError处理程序处理任务响应。在服务中运行任务可确保它由具有由内部队列支持的固定线程池进行溢出的执行程序处理。
  2. 你写你自己的长时间运行的服务接口与它自己的API和封装一个阻塞队列的请求,使用Platform.runLater处理沟通结果返回给用户界面。

为了使响应处理逻辑动态和可调主叫方,您可以通过处理函数作为成功使用Platform.runLater原来的通话将被执行的lambda函数。

如果将调用包装在任务或服务中,并使用onSucceeded函数,则不需要runLater调用,因为实现将确保在任务完成后在JavaFX线程上调用onSucceeded处理程序。

请注意,通常网络请求和响应需要将数据编组和数据解串转换为可串行化数据流。一些较高级别的网络API(如JAX-RS或Web套接字提供程序)提供接口和实用程序来为您执行一些此类工作,通常使用特定库进行不同类型的转换,例如用于JSON的Jackson的XML序列化的JAXB序列化。

略相关信息,并进一步思考

接下来的这个可能是一个有点离题,但这是BlockingQueue and Task interaction一个例子,它不是一个网络服务,但它内部的演示队列的使用生产者/消费者情况,反应式用户界面和进度监控。

另一件看起来很有趣的事情(至少对我来说)是基于Akka的基于JavaFX客户端 - >服务器通信的解决方案。这似乎是传统的http/rest/soap/rmi调用或基于消息队列的处理的不错选择。 Akka本质上是一种基于事件的容错异步并发通信解决方案,因此它似乎是基于UI的框架(如JavaFX)的良好匹配,允许开发人员在适当的抽象层进行处理。但是我还没有看到依赖于Akka的基于JavaFX的消息客户端。

2

我想知道建立一个基于网络的可重复使用的 穿线服务的最佳方法。我使用一个控制器类(通过Net-beans GUI从FXML生成)编写网络服务的方式为 。我通过一个名为'transmitter'的私人服务成员 将线程逻辑放在这里,我通过 Start/Stop按钮的事件回调连接了启动/停止逻辑。

我虚心地建议你开发你的网络服务和GUI控制器作为单独的项目。

我将网络服务作为守护程序/后台线程在其自己的容器或虚拟机中运行。这个组织的优点是它使您的服务器远离JavaFX事件循环和应用程序线程的变幻莫测。您需要设计服务来识别管理命令和/或中断来自控制器的请求。您可以开发您的网络服务作为REST或任何您想要的,而不必考虑如何将其推送到JavaFX应用程序线程中。然后,我可以让GUI控制器作为单独的GUI应用程序在同一个进程中运行,或者,如果需要远程管理,可以在单独的JVM中(并使用IPC发送/接收管理消息)运行。

TL; DR:如果是我,我会拒绝将网络服务编程为JavaFX应用程序的诱惑。

+0

我同意这一点。这是一篇[非常好的文章](http:// www。oracle.com/technetwork/articles/java/javafxinteg-2062777.html),了解如何将服务集成到您的FX应用程序中。 (第二次我今天在SO上引用了这篇文章......)。 –