9

Windows では、コマンド ラインからプログラムを起動したときに、子プロセスの I/O を確実に操作できません。サーバーが I/O にコンソールを使用するのは標準であるため、イライラします。GUI は素晴らしいものですが、私はコマンド ラインに固執して物事をシンプルに保ちたいと思っています。Eclipse IDE からサーバーを実行しているときは、子プロセスの I/O はまったく問題ないことに気付きましたが、コマンド ラインから実行する場合はまったく別の話です。子プロセスの読み取りまたは書き込みはできませんが、プロセスは引き続き実行されます。この問題を示すテスト コードを以下にいくつか書きました。この問題が別のマシンで再現され、解決策が得られることを願っています。Eclipse から実行すると、継承された I/O が期待どおりに機能します。でも、Windows コマンド プロンプトから実行すると、子プロセスに対して何も読み書きできません。どちらの場合も、子プロセスの出力をファイルにリダイレクトすることは常に成功しますが、入力を子プロセスに渡すことはできません。この問題の解決策が既にある場合は、そのページにリンクしてください。

JRE/JDK の実装:

>java -version
java version "1.7.0_01"
Java(TM) SE Runtime Environment (build 1.7.0_01-b08)
Java HotSpot(TM) 64-Bit Server VM (build 21.1-b02, mixed mode)

次のコードを検討してください。

package com.comp8nerd4u2.io.test;

/*
 * These tests attempt to confirm what I'm experiencing under my build environment
 */

import java.io.File;
import java.io.IOException;

public final class PIOTest {

/** The command to run as a child process. The command itself isn't the test, but what you use to run this Java program is the test. */
private static final String[] COMMAND = {"cmd.exe", "/c", "echo This is a test. Feel free to change this."}; // Change this to just {"cmd.exe"} or some other program that accepts input and you'll see how frustrating this is
/** Controls how the test process is built */
private static final ProcessBuilder PB = new ProcessBuilder(COMMAND);
/** How long to allow the process to run before forcibly terminating it. */
private static final long PROCESS_TIMEOUT = 10000L;
private static final Runnable R = new TimedInterruptWorker(PROCESS_TIMEOUT);

private static int n = 0;

static {
    PB.redirectErrorStream(true);
}

private PIOTest() {}

public static void main(String[] args) {

    // ----- Begin Tests -----

    /*
     * Test #1: Let's test putting our command's output onto our standard I/O streams
     * Goal condition: Child process outputs expected output, and exits before the timeout. If child process expects input, it should accept entered input.
     * Known success factors: Parent process' standard I/O is piped to Eclipse. Tests would probably succeed with Netbeans as well
     * Known fail factors: Parent process' standard I/O is piped to Windows Command Prompt
     * Result under fail condition: Child process hangs if it fills up its output buffer or requests input, but exits on its own otherwise, unless it took longer than the timeout. 
     */
    PB.inheritIO();
    doTest();

    // Test #2: Let's test putting our command's output into a file
    PB.redirectOutput(new File("piotest.txt"));
    doTest();
}

/**
 * Performs the I/O test.
 */
private static void doTest() {
    n++;
    Process p = null;
    try {
        p = PB.start();
    } catch (IOException e) {
        e.printStackTrace();
        return;
    }
    try {
        Thread t = new Thread(R);
        t.setDaemon(true);
        t.start();
        System.out.format("[Test #%d] Child exited with status code %d\n", n, p.waitFor());
        t.interrupt();
    } catch (InterruptedException e) {
        p.destroy();
        System.out.format("[Test #%d] Child took longer than the timeout.\n", n);
    }
}

/**
 * Useful for sending interrupts after a certain amount of time has passed.
 * 
 * @author comp8nerd4u2
 */
private static final class TimedInterruptWorker implements Runnable {

    private long timeout = 0;
    private Thread target = null;

    public TimedInterruptWorker(long timeout) {
        this(timeout, Thread.currentThread());
    }

    public TimedInterruptWorker(long timeout, Thread target) {
        this.timeout = timeout;
        this.target = target;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(timeout);
        } catch (InterruptedException e) {
            return;
        }
        target.interrupt();
    }

}

}

更新: 実行時に任意のコマンドを受け入れるようにテストを変更し、Linux vps サーバーにアップロードしました。ssh セッションから実行したところ、すべての子プロセスの I/O を簡単に読み書きできました。一つ気になったことがありました。インタラクティブな bash シェルを子プロセスとして開き、その出力をファイルにリダイレクトすると、CentOS がプログラムを停止したと思います。それまたは私のプログラムがクラッシュしました。

[admin@comp8nerd4u2 piotest]$ java -jar piotest.jar
Enter command to run : bash
[admin@comp8nerd4u2 piotest]$ [Test #1] Child took longer than the timeout.

[1]+  Stopped                 java -jar piotest.jar
[admin@comp8nerd4u2 piotest]$

最初の行は、コマンドの入力です。2行目は生成されたbashシェルですが、何も入力しなかったため、タイムアウト後にプログラムが強制終了します。2 回目のテストの準備が整い、「piotest.txt」ファイルが作成された後、クラッシュするか、OS によって停止されます。実際のテスト自体は変更されていませんが、実行時に実行するコマンドを入力できるようになった点が異なります。これは Linux では正常に機能しますが、Windows では機能しません。Win32 API を知っている人が、Windows でこのテストが失敗する理由を何らかの方法で説明できることを願っています。

4

2 に答える 2

2

この記事を見たことがありますか?http://www.javaworld.com/jw-12-2000/jw-1229-traps.html?page=1

Windows で入出力ストリームを処理する必要があるように思えます。この記事は Runtime.exec に関するものですが、ProcessBuilder のネイティブ コードは非常によく似ており、Windows でも同じ種類の問題を抱えているに違いありません。

これが Windows 上の Eclipse で機能する理由についての私の推測では、コンソール ビューに表示するために、Eclipse がユーザーに代わってストリームを処理しているためです。

于 2012-06-24T17:31:15.953 に答える
1

私は答えが遅れていることを知っていますが、答えに出くわす前にこの質問に出くわし、同じ船に乗っている他の誰かを検索して救いたいと思っていました.

これは、実際には Windows の既知のバグです: https://bugs.openjdk.java.net/browse/JDK-8023130

ストリームを自分でリダイレクトすることで回避できます。

Process p = pb.start();
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = null;
while ((line = br.readLine()) != null) {
   System.out.println(line);
}

p.waitFor();

br.close();
于 2014-02-11T23:16:05.287 に答える