我如何使用一个 Java 程序来监视另一个 Java 程序的输出?
How do I use one Java program, to monitor another Java program's output?
下图显示了我正在尝试做的事情:它只是 2 个程序。一个是简单的 Child
程序,每 2 秒写出整数,line-by-line .
另一个是 Parent
监控日志文件(只是一个非常基本的文本文件)的程序。如果日志文件在 5 秒内没有被修改,那么它应该重新启动 Child
程序(通过批处理文件);然后继续正常。
我的 child class 代码在这里:
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();
}
}
和I linked here my current code for the Parent class。
我现在要做的是添加一些逻辑,当 child class 停止写入输出时捕获。我想做的是计算日志文件中的所有行;然后每 5 秒比较一次,这是一个好方法吗(替代方案是 - 继续检查文件是否被修改)?
编辑:下面使用 waitFor()
的建议确实有帮助,尽管我仍在研究细节:它通常是这样的:
try {
/* Whosebug 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 - Whosebug code */
}
catch (IOException i) {
i.printStackTrace();
}
catch (InterruptedException i) {
i.printStackTrace();
}
随着文件不断变大,这会变得非常慢。一种更简单的方法是简单地检查文件的最后修改时间。假设子程序可能停止写入文件的原因是程序终止(而不是例如挂在无限循环中),直接监视子程序 process 本身可能更好而不是依赖于观察过程的影响。如果父进程可以首先负责启动程序,这将特别方便。
这个可以用Java中的ProcessBuilder and Process 类来完成 8.从文档中复制,你可以这样开始这个过程(如果你只想监视它是否是运行与否):
ProcessBuilder pb = new ProcessBuilder("TheBatchFile.bat", "Argument1", "Argument2");
pb.directory(new File("/path/to/working/dir"));
Process p = pb.start();
然后,您可以简单地调用p.waitFor();
等待进程终止。在循环中执行此操作,您就会有自动重启子行为。
您可以使用目录监视服务:
https://docs.oracle.com/javase/tutorial/essential/io/notification.html
您可以配置路径或文件并注册watcher
。
每次更改文件时,观察者都会收到通知。您可以存储此通知时间戳以备后用。
有关详细信息,请参阅我上面的 link。
然后您可以使用计时器或线程来检查上次修改。
虽然您创建文本文件和使用批处理脚本的方法可行,但还有更好的方法来实现它。这是多任务处理的标准问题,通过创建几个线程,一点也不难。
与使用批处理文件和多个程序的系统相比,使用线程有几个优点"around"。对于初学者,这些可能包括:
将所有内容放在一起会使项目更整洁、更干净、
并且稍微更容易分发。
更容易实现。如果您从未使用过某些线程,它们可能看起来很混乱,但在我看来,它们是次要的,然后是绕过它们的所有步骤。正如我希望在下面展示的那样,用线程实现这个问题并不难。
提高了性能,因为避免了非常昂贵的文件 IO 操作和生成批处理文件。在大多数情况下,与进程相比,线程的性能也有所提高,因为它们更容易生成,而且多线程通过减少对多个内核的依赖,在比多处理更广泛的处理器上实现了性能改进。
一个程序读取文件与另一个程序同时写入文件之间没有粗略重叠。尽可能避免这种情况。
保持 Java 令人印象深刻的跨平台能力,因为您没有使用不跨平台的批处理。对于这个项目,这对你来说可能并不重要,但你以后可能会遇到类似的问题,而这更重要,所以你将练习实施它。
使用线程 "right way" 而不是
通过使用更 hacky 的方法养成坏习惯。如果这是一个
学习项目,你不妨学学吧
我继续编写了我最有可能用来解决问题的方法。我的代码有一个 child 线程,每两秒计数一次,还有一个 parent 线程监视 child,并在 child 持续五秒而不计数时重新启动它。让我们检查一下我的程序,让您了解它是如何工作的。
首先,这里是 class 对应 parent:
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();
}
}
如果你愿意,可以把里面的主要功能放在别的地方,但是要启动一切,只需实例化一个parent。 parent class 有一个 child class 的实例,它启动了 child 线程。 child 将使用 report 方法向 parent 报告它正在计数,该方法会生成一个看门狗计时器(稍后会详细介绍),该计时器将在五秒后根据当前计数调用 restartChild。 RestartChild,如果计数仍然与提供的计数相同,则重新启动 child 线程。
这里是看门狗定时器的class:
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 ();
}
}
}
这个看门狗定时器是parent会运行用start方法的一个线程。 parent 将自己作为参数发送,这样我们就可以调用 parent.It 的 restartChild 函数来存储计数,因为当它在 运行 秒后五秒后,restartChild 将检查计数是否有改变了。
最后,这里是 child class
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;
}
}
这也是一个线程,因此它实现了 运行nable,在这方面,它的行为很像看门狗。 运行() 是 child 线程的主要方法,这是您的逻辑在启动时调用的地方。使用 start() 启动 child 会将所有变量设置为其默认值,然后开始 运行() 逻辑。 运行 中的逻辑包含在 if(运行ning) 中,因为这让我们可以通过设置 运行 false 在内部终止线程。
目前,child 现在所做的只是增加它的计数器,将其输出到控制台,然后将 activity 报告给 parent,100 次,每两秒.您可能希望删除在计数超过 100 后停止它的条件,但我包含了它,以便 parent 最终有理由重新启动 child。要更改行为,请查看 child 的 运行 方法,这是所有主要操作所在的地方。
下图显示了我正在尝试做的事情:它只是 2 个程序。一个是简单的 Child
程序,每 2 秒写出整数,line-by-line .
另一个是 Parent
监控日志文件(只是一个非常基本的文本文件)的程序。如果日志文件在 5 秒内没有被修改,那么它应该重新启动 Child
程序(通过批处理文件);然后继续正常。
我的 child class 代码在这里:
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();
}
}
和I linked here my current code for the Parent class。
我现在要做的是添加一些逻辑,当 child class 停止写入输出时捕获。我想做的是计算日志文件中的所有行;然后每 5 秒比较一次,这是一个好方法吗(替代方案是 - 继续检查文件是否被修改)?
编辑:下面使用 waitFor()
的建议确实有帮助,尽管我仍在研究细节:它通常是这样的:
try {
/* Whosebug 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 - Whosebug code */
}
catch (IOException i) {
i.printStackTrace();
}
catch (InterruptedException i) {
i.printStackTrace();
}
随着文件不断变大,这会变得非常慢。一种更简单的方法是简单地检查文件的最后修改时间。假设子程序可能停止写入文件的原因是程序终止(而不是例如挂在无限循环中),直接监视子程序 process 本身可能更好而不是依赖于观察过程的影响。如果父进程可以首先负责启动程序,这将特别方便。
这个可以用Java中的ProcessBuilder and Process 类来完成 8.从文档中复制,你可以这样开始这个过程(如果你只想监视它是否是运行与否):
ProcessBuilder pb = new ProcessBuilder("TheBatchFile.bat", "Argument1", "Argument2");
pb.directory(new File("/path/to/working/dir"));
Process p = pb.start();
然后,您可以简单地调用p.waitFor();
等待进程终止。在循环中执行此操作,您就会有自动重启子行为。
您可以使用目录监视服务:
https://docs.oracle.com/javase/tutorial/essential/io/notification.html
您可以配置路径或文件并注册watcher
。
每次更改文件时,观察者都会收到通知。您可以存储此通知时间戳以备后用。
有关详细信息,请参阅我上面的 link。
然后您可以使用计时器或线程来检查上次修改。
虽然您创建文本文件和使用批处理脚本的方法可行,但还有更好的方法来实现它。这是多任务处理的标准问题,通过创建几个线程,一点也不难。
与使用批处理文件和多个程序的系统相比,使用线程有几个优点"around"。对于初学者,这些可能包括:
将所有内容放在一起会使项目更整洁、更干净、 并且稍微更容易分发。
更容易实现。如果您从未使用过某些线程,它们可能看起来很混乱,但在我看来,它们是次要的,然后是绕过它们的所有步骤。正如我希望在下面展示的那样,用线程实现这个问题并不难。
提高了性能,因为避免了非常昂贵的文件 IO 操作和生成批处理文件。在大多数情况下,与进程相比,线程的性能也有所提高,因为它们更容易生成,而且多线程通过减少对多个内核的依赖,在比多处理更广泛的处理器上实现了性能改进。
一个程序读取文件与另一个程序同时写入文件之间没有粗略重叠。尽可能避免这种情况。
保持 Java 令人印象深刻的跨平台能力,因为您没有使用不跨平台的批处理。对于这个项目,这对你来说可能并不重要,但你以后可能会遇到类似的问题,而这更重要,所以你将练习实施它。
使用线程 "right way" 而不是 通过使用更 hacky 的方法养成坏习惯。如果这是一个 学习项目,你不妨学学吧
我继续编写了我最有可能用来解决问题的方法。我的代码有一个 child 线程,每两秒计数一次,还有一个 parent 线程监视 child,并在 child 持续五秒而不计数时重新启动它。让我们检查一下我的程序,让您了解它是如何工作的。
首先,这里是 class 对应 parent:
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();
}
}
如果你愿意,可以把里面的主要功能放在别的地方,但是要启动一切,只需实例化一个parent。 parent class 有一个 child class 的实例,它启动了 child 线程。 child 将使用 report 方法向 parent 报告它正在计数,该方法会生成一个看门狗计时器(稍后会详细介绍),该计时器将在五秒后根据当前计数调用 restartChild。 RestartChild,如果计数仍然与提供的计数相同,则重新启动 child 线程。
这里是看门狗定时器的class:
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 ();
}
}
}
这个看门狗定时器是parent会运行用start方法的一个线程。 parent 将自己作为参数发送,这样我们就可以调用 parent.It 的 restartChild 函数来存储计数,因为当它在 运行 秒后五秒后,restartChild 将检查计数是否有改变了。
最后,这里是 child class
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;
}
}
这也是一个线程,因此它实现了 运行nable,在这方面,它的行为很像看门狗。 运行() 是 child 线程的主要方法,这是您的逻辑在启动时调用的地方。使用 start() 启动 child 会将所有变量设置为其默认值,然后开始 运行() 逻辑。 运行 中的逻辑包含在 if(运行ning) 中,因为这让我们可以通过设置 运行 false 在内部终止线程。
目前,child 现在所做的只是增加它的计数器,将其输出到控制台,然后将 activity 报告给 parent,100 次,每两秒.您可能希望删除在计数超过 100 后停止它的条件,但我包含了它,以便 parent 最终有理由重新启动 child。要更改行为,请查看 child 的 运行 方法,这是所有主要操作所在的地方。