Проблема с множественным вводом команды с использованием Apache Commons Exec и извлечением вывода - PullRequest
8 голосов
/ 18 августа 2011

Я пишу Java-приложение, которое должно использовать внешнее приложение командной строки с использованием библиотеки Apache Commons Exec.Приложение, которое мне нужно запустить, имеет довольно длительное время загрузки, поэтому было бы предпочтительным сохранить один экземпляр в активном состоянии, а не каждый раз создавать новый процесс.Способ работы приложения очень прост.После запуска он ожидает некоторого нового ввода и генерирует некоторые данные в качестве вывода, оба из которых используют стандартный ввод-вывод приложения.

Поэтому идея состоит в том, чтобы выполнить CommandLine, а затем использовать PumpStreamHandlerс тремя отдельными потоками (вывод, ошибка и ввод) и использовать эти потоки для взаимодействия с приложением.До сих пор у меня была эта работа в основных сценариях, где у меня есть один вход, один выход, и приложение затем закрывается.Но как только я пытаюсь получить вторую транзакцию, что-то идет не так.

После создания CommandLine я создаю свой Executor и запускаю его так:

this.executor = new DefaultExecutor();

PipedOutputStream stdout = new PipedOutputStream();
PipedOutputStream stderr = new PipedOutputStream();
PipedInputStream stdin = new PipedInputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(stdout, stderr, stdin);

this.executor.setStreamHandler(streamHandler);

this.processOutput = new BufferedInputStream(new PipedInputStream(stdout));
this.processError = new BufferedInputStream(new PipedInputStream(stderr));
this.processInput = new BufferedOutputStream(new PipedOutputStream(stdin));

this.resultHandler = new DefaultExecuteResultHandler();
this.executor.execute(cmdLine, resultHandler);

Iзатем перейдите к запуску трех разных потоков, каждый из которых обрабатывает отдельный поток.У меня также есть три SynchronousQueues, которые обрабатывают ввод и вывод (один используется в качестве ввода для входного потока, один для информирования outputQueue о запуске новой команды и один для вывода).Например, поток входного потока выглядит следующим образом:

while (!killThreads) {
    String input = inputQueue.take();

    processInput.write(input.getBytes());
    processInput.flush();

    IOQueue.put(input);
}

Если я удаляю цикл while и просто выполняю его один раз, кажется, что все работает отлично.Очевидно, что если я попытаюсь выполнить его снова, PumpStreamHandler выдает исключение, потому что к нему обращались два разных потока.

Проблема здесь в том, что кажется, что processInput действительно не очищается, пока поток не завершится.При отладке приложение командной строки действительно получает свои входные данные только после завершения потока, но никогда не получает их, если цикл while сохраняется.Я пробовал много разных вещей, чтобы заставить ProcessInput сбрасываться, но, похоже, ничего не работает.

Кто-нибудь пробовал что-то подобное раньше?Есть ли что-то, что я пропускаю?Любая помощь будет принята с благодарностью!

Ответы [ 3 ]

9 голосов
/ 23 сентября 2011

В итоге я придумал, как сделать эту работу.Просматривая код библиотеки Commons Exec, я заметил, что StreamPumpers, используемые PumpStreamHandler, не сбрасываются каждый раз, когда поступают новые данные.Вот почему код работал, когда я выполнял его только один раз, поскольку он автоматически сбрасывал и закрывал поток.Поэтому я создал классы, которые я назвал AutoFlushingStreamPumper и AutoFlushingPumpStreamHandler.Последний аналогичен обычному PumpStreamHandler, но использует AutoFlushingStreamPumpers вместо обычных.AutoFlushingStreamPumper делает то же самое, что и стандартный StreamPumper, но сбрасывает свой выходной поток каждый раз, когда что-то записывает в него.

Я довольно тщательно его протестировал и, похоже, он работает хорошо.Спасибо всем, кто пытался это выяснить!

1 голос
/ 20 августа 2012

Для моих целей оказывается, что мне нужно только переопределить «ExecuteStreamHandler».Вот мое решение, которое захватывает stderr в StringBuilder и позволяет вам передавать вещи в stdin и получать вещи из stdout:

class SendReceiveStreamHandler implements ExecuteStreamHandler

Весь класс представлен в GitHub здесь.

0 голосов
/ 20 мая 2014

Чтобы иметь возможность написать более одной команды в STDIN процесса, я создал новый

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Map;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.lang3.CharEncoding;

public class ProcessExecutor extends DefaultExecutor {

    private BufferedWriter processStdinput;

    @Override
    protected Process launch(CommandLine command, Map env, File dir) throws IOException {
        Process process = super.launch(command, env, dir);
        processStdinput = new BufferedWriter(new OutputStreamWriter(process.getOutputStream(), CharEncoding.UTF_8));
        return process;
    }

    /**
     * Write a line in the stdin of the process.
     * 
     * @param line
     *            does not need to contain the carriage return character.
     * @throws IOException
     *             in case of error when writing.
     * @throws IllegalStateException
     *             if the process was not launched.
     */
    public void writeLine(String line) throws IOException {
        if (processStdinput != null) {
            processStdinput.write(line);
            processStdinput.newLine();
            processStdinput.flush();
        } else {
            throw new IllegalStateException();
        }
    }

}

Чтобы использовать этого нового исполнителя, я сохраняю поток по конвейеру в PumpStreamHandler, чтобы избежать этогоSTDIN должен быть закрыт PumpStreamHandler.

ProcessExecutor executor = new ProcessExecutor();
executor.setExitValue(0);
executor.setWorkingDirectory(workingDirectory);
executor.setWatchdog(new ExecuteWatchdog(ExecuteWatchdog.INFINITE_TIMEOUT));
executor.setStreamHandler(new PumpStreamHandler(outHanlder, outHanlder, new PipedInputStream(new PipedOutputStream())));
executor.execute(commandLine, this);

Вы можете использовать метод writeLine () исполнителя или создать свой собственный.

...