【问题标题】:How do I use one Java program, to monitor another Java program's output?如何使用一个 Java 程序来监控另一个 Java 程序的输出?
【发布时间】:2015-11-17 16:49:51
【问题描述】:

下图显示了我正在尝试做的事情:它只是 2 个程序。一个是简单的Child 程序,它每 2 秒逐行写出整数。

另一个是Parent 程序,它监视日志文件(只是一个非常基本的文本文件)。如果日志文件在 5 秒内没有被修改,那么它应该重新启动 Child 程序(通过批处理文件);然后正常继续。

我的子类代码在这里:

package fileiotestapplication;
import java.io.*;
import java.io.IOException;
import java.util.*;


public class WriterClass {

    @SuppressWarnings("oracle.jdeveloper.java.insufficient-catch-block")
    public WriterClass() {
        super();


            int[] content = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,};
            String[] friends = {"bob",};

                File file = new File("/C:/Java_Scratch/someFile.txt");
                // if file does not exists, then create it

            try {

                if (!file.exists()) {
                    file.createNewFile();
                }


                for (int i = 0 ; i < content.length; i++) 
                {                   

                        PrintStream bw = new PrintStream( new FileOutputStream(file, true) );     

                        System.out.println("testing " + i);
                        bw.println( String.valueOf(content[i]) );
                        bw.close();


                        Thread.sleep(2500);
                }


                System.out.println("Done");
            } catch (IOException ioe) {
                // TODO: Add catch code
                ioe.printStackTrace();
            }
            catch (InterruptedException ioe) {
                            // TODO: Add catch code
                            ioe.printStackTrace();
                }

        //someIS.println(i);
        System.out.println("This is OK");



    }

    public static void main(String[] args) {
        WriterClass writerClass = new WriterClass();


    }
}

The source code

还有I linked here my current code for the Parent class

我现在要做的是添加一些逻辑,当子类停止写入输出时会捕获。我想做的是计算日志文件中的所有行;然后每 5 秒比较一次,这是一个好方法吗(另一种方法是 - 继续检查文件是否被修改过)?

编辑:下面使用waitFor() 的建议确实有帮助,尽管我仍在研究细节:它通常是这样的:

  try {
   /* StackOverflow code */  

   for (  ;  ; )  {
   ProcessBuilder pb = new ProcessBuilder("TheBatchFile.bat");
   pb.directory(new File("C://Java_Scratch_//Autonomic_Using_Batch//"));
   Process p = pb.start();
   p.waitFor();
}
  /* end - StackOverflow code */  

  }
  catch (IOException i) {
    i.printStackTrace();
  }


  catch (InterruptedException i) {
    i.printStackTrace();
  }

【问题讨论】:

  • 文件不能监控任何东西。你的意思是'如何使用一个Java 程序来监控另一个Java 程序的输出'?
  • 父子进程是分开运行的吗?或者孩子是一个“孩子”线程?
  • @EJP - 正确 - 我们需要监控输出
  • 喜欢那个图!
  • @Coffee 如果您只是想监视子程序,那么写入文件比使用 ProcessBuilder 或类似的东西要慢得多。不过,这已经在另一个答案中了。

标签: java multithreading io processbuilder


【解决方案1】:

随着文件大小的不断增长,这将变得非常缓慢。一种更简单的方法是简单地检查文件的最后修改时间。假设子程序可能停止写入文件的原因是程序终止(而不是例如挂在无限循环中),最好直接监视子 进程 本身而不是依赖关于观察过程的效果。如果父进程可以负责启动程序,这将特别方便。

这可以通过 Java 8 中的 ProcessBuilderProcess 类来完成。从文档中复制,您可以像这样启动该过程(如果您只想监视它是否正在运行):

ProcessBuilder pb = new ProcessBuilder("TheBatchFile.bat", "Argument1", "Argument2");
pb.directory(new File("/path/to/working/dir"));
Process p = pb.start();

然后,您可以简单地调用p.waitFor(); 来等待进程终止。循环执行此操作,您将获得自动重启子行为。

【讨论】:

  • 您还可以看到程序正在输出什么。只需将子程序输出到System.out 而不是文件,然后在父程序中调用p.getOutputStream() 并从中读取。 (也可以使用p.getInputStream写到子程序的System.in)。
  • @kittycat3141 - 我想我跟着。由于父母和孩子都在同一个JVM中运行对吗?我猜System.in 可以被运行在 JVM 中的任何 java 程序访问?
  • @Coffee:没必要——事实上,每个运行的Java程序都有自己的JVM实例;您不能在一个 JVM 中拥有多个进程。标准输入和输出流旨在用于 进程之间的通信。 (您可以在同一个 JVM 中拥有多个 线程,但您也可以与共享内存进行通信 - 同一进程中的所有线程都可以访问相同的变量。)
  • @AasmundEldhuset - 我想我还是跟着我,虽然这对我来说还是有点困惑。但是我们可以在同一个 JVM 中运行多个程序吗? I was reading up here just now though where they say
  • @Coffee:太好了!我周末出去了,与此同时,看起来你已经对你的另一个问题有了一个广泛的答案。祝你好运!
【解决方案2】:

您可以使用目录监视服务:

https://docs.oracle.com/javase/tutorial/essential/io/notification.html

您可以配置路径或文件并注册watcher

每次文件更改时,观察者都会收到通知。您可以存储通知的此时间戳以供以后使用。

有关详细信息,请参阅我上面的链接。

然后您可以使用计时器或线程来检查上次修改。

【讨论】:

  • 谢谢!你碰巧有使用“目录监视服务”编码的经验吗?我很好奇它是如何工作的
  • 提供的链接显示了一个广泛的示例。如果您在实施时遇到问题,请考虑发布问题。
  • 非常感谢,我终于启动并运行了这段代码。我现在实际上正在尝试一个新的挑战,if you don't mind checking it out I'd greatly appreciate it - 谢谢!
【解决方案3】:

虽然您创建文本文件的方法和使用批处理脚本是可行的,但还有更好的方法来处理它。这是多任务处理的标准问题,通过创建几个线程,它一点也不难。

与使用批处理文件和多个程序从外部“绕过”系统相比,使用线程有几个优点。首先,这些可能包括:

  1. 将所有内容放在一起使项目更加整洁, 并且更容易分发。

  2. 更容易实现。如果您从未使用过线程,它们可能看起来令人困惑,但在我看来,它们的危害较小,然后是绕过它们所涉及的所有步骤。正如我希望在下面展示的那样,用线程实现这个问题并不难。

  3. 提高了性能,因为避免了文件 IO 的非常昂贵的操作和生成批处理文件。在大多数情况下,线程也比进程的性能有所提高,因为它们更容易产生,而且多线程通过减少对多个内核的依赖,在比多处理更广泛的处理器上看到了性能改进。

  4. 一个程序正在读取文件,而另一个程序同时写入文件之间没有粗略的重叠。尽可能避免此类情况。

  5. 保持 Java 令人印象深刻的跨平台能力,因为您没有使用不是跨平台的批处理。对于这个项目,这对您来说可能并不重要,但您将来可能会遇到类似问题的东西,而这更重要,因此您将练习实施它。

  6. 以“正确的方式”使用线程而不是 通过使用更老套的方法来养成坏习惯。如果这是一个 学习项目,你不妨学对了。

我继续编写了最有可能用于解决问题的方法。我的代码有一个每两秒计数一次的子线程,以及一个监控子线程的父线程,如果子线程五秒钟没有计数,则重新启动它。让我们检查一下我的程序,让您了解它的工作原理。

首先,这里是父类:

public class Parent {

    private Child child;

    public Parent(){
        child = new Child(this);
        child.start();
    }

    public void report(int count){ //Starts a new watchdog timer
        Watchdog restartTimer = new Watchdog(this, count);
        restartTimer.start();
    }

    public void restartChild(int currentCount){
        if (currentCount == child.getCount()){ //Check if the count has not changed
            //If it hasn't
            child.kill();
            child.start();
        }

    }


    public static void main(String[] args){
        //Start up the parent function, it spawns the child
        new Parent();
    }

}

如果您愿意,可以将其中的 main 函数放在其他地方,但是要启动所有内容,只需实例化一个父函数。父类有一个子类的实例,它启动子线程。子进程将使用 report 方法向父进程报告它的计数,该方法会生成一个看门狗计时器(稍后会详细介绍),该计时器将在五秒后使用当前计数调用 restartChild。 RestartChild,如果计数仍然与提供的相同,则重新启动子线程。

这是看门狗定时器的类:

class Watchdog implements Runnable { //A timer that will run after five seconds
       private Thread t;
       private Parent parent;
       private int initialCount;

       public Watchdog(Parent parent, int count){ //make a  timer with a count, and access to the parent
           initialCount = count;
           this.parent = parent;
       }

       public void run() { //Timers logic
           try {
               Thread.sleep(5000); // If you want to change the time requirement, modify it here
               parent.restartChild(initialCount);

           } catch (InterruptedException e) {
                System.out.println("Error in watchdog thread");
            }

       }

       public void start () // start the timer
       {
          if (t == null)
          {
             t = new Thread (this);
             t.start ();
          }
       }

    }

这个看门狗定时器是一个线程,父进程将使用 start 方法运行。 parent 将自己作为参数发送,以便我们可以调用 parent 的 restartChild 函数。它存储计数,因为当它在五秒后运行时,restartChild 会检查计数是否发生变化。

最后,这是子类

public class Child implements Runnable{

    private Thread t;
    public int counter = 0;
    private boolean running;

    private Parent parent; // Record the parent function

    public Child(Parent parent){
        this.parent = parent;
    }

    private void initializeAll(){
        counter = 0;
        running = true;
    }

    public int getCount(){
        return counter;
    }

    @Override
    public void run() {
        while((counter <= 100)&&(running)){ 
            //The main logic for child
            counter +=1;
            System.out.println(counter);
            parent.report(counter); // Report a new count every two seconds

            try {
                Thread.sleep(2000); // Wait two seconds
            } catch (InterruptedException e) {
                System.out.println("Thread Failed");
            }
        }

    }

    public void start(){ //Start the thread

        initializeAll();
        t = new Thread(this);
        t.start();

    }

    public void kill(){ //Kill the thread
        running = false;
    }


}

这也是一个线程,因此它实现了可运行的,并且在这方面的行为很像看门狗。 Run() 是子线程的主要方法,这是您启动它时调用的逻辑所在。使用 start() 启动子程序会将所有变量设置为其默认值,然后开始 run() 逻辑。 run 中的逻辑包含在 if(running) 中,因为这让我们可以通过将 running 设置为 false 来在内部终止线程。

目前,孩子现在所做的只是增加它的计数器,将其输出到控制台,然后每两秒向父母报告 100 次活动。您可能希望在计数超过 100 后删除停止它的条件,但我将其包括在内,以便父母最终有理由重新启动孩子。要更改行为,请查看子项的 run 方法,这是所有主要操作所在的位置。

【讨论】:

  • 快速问题 - 我可以从命令行运行它,但到目前为止我还没有看到如何启动 Watchdog 类。还是在 Parent 课程开始后就开始了?谢谢
  • 对不起,我不相信我完全理解你的问题。 “开始看门狗课程”是什么意思?
  • 定时器在孩子报告它的计数时启动,当孩子报告另一个数字时它们不会被杀死,而是因为计数发生变化,它们没有效果。
  • 不用担心,希望我的最后一条评论回答了这个问题,但为了澄清,Parent 将报告函数定义为public void report(int count){Watchdog restartTimer = new Watchdog(this, count);restartTimer.start();} 这将实例化一个新的看门狗,并使用 start() 函数启动它的计时器。每当孩子报告新动作时都会调用它。我在这里启动它,而不是在最后一个计时器结束时等到最后一个动作后的五秒,而不是像时钟一样每五秒检查一次,这样它就保持跟上,而不是有一个小的容差。
  • 现在开始澄清。我会继续努力的,非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-19
  • 1970-01-01
  • 1970-01-01
  • 2013-10-18
  • 2010-11-05
  • 1970-01-01
相关资源
最近更新 更多