Две программы со связанными StdIn и StdOut

12

Предположим, у меня есть две программы под названием ProgramAи ProgramB. Я хочу запустить их обоих одновременно в интерпретаторе Windows cmd. Но я хочу, чтобы StdOutиз ProgramAзацепили к StdInо ProgramBи StdOutо ProgramBзацепили к StdInо ProgramA.

Что-то вроде этого

 ________________ ________________
| | | |
| StdIn (== ← === ← == (StdOut |
| Программа А | | Программа Б |
| | | |
| StdOut) == → === → ==) StdIn |
| ________________ | | ________________ |

Есть ли команда, чтобы сделать это - каким-то образом добиться этой функциональности из cmd?

DarthRubik
источник
4
В Unix я бы использовал именованные каналы, для этого в Windows есть нечто, называемое именованные каналы, которое совершенно другое и, возможно, неприменимо.
Ясен
@Jasen в соответствии с комментарием здесь linuxjournal.com/article/2156 именованные каналы могут работать на cygwin ... если так, то, возможно, вы могли бы разработать и опубликовать в качестве ответа, хотя сначала стоит проверить на cygwin
barlop
Я полагаю, что программы также должны были бы быть написаны так, чтобы они не зацикливались. Так что, если stdout - пустая строка, не передавайте в stdin, а если ничего не является в stdin, то завершайте работу. Таким образом, избегая бесконечного цикла? Но из интереса, какое приложение?
Барлоп
2
говорите, хотите, чтобы у двух экземпляров "Элизы" был разговор?
Jasen
Если программы не разработаны тщательно, чтобы справиться с этим, они могут зайти в тупик - оба ждут ввода от другого и ничего не происходит (или обе они пытаются записать в полные буферы и никогда не читают что-либо, чтобы очистить их) )
Random832

Ответы:

5

Это можно сделать не только с помощью командного файла! :-)

Проблема может быть решена путем использования временного файла в качестве «канала». Для двунаправленной связи требуются два «конвейерных» файла.

Процесс A читает стандартный ввод из "pipe1" и записывает стандартный вывод в pipe2
Процесс B считывает стандартный ввод из pipe2 и записывает стандартный вывод в pipe1

Важно, чтобы оба файла существовали до запуска любого процесса. Файлы должны быть пустыми в начале.

Если пакетный файл пытается прочитать из файла, который находится на текущем конце, он просто ничего не возвращает, и файл остается открытым. Так что моя подпрограмма readLine постоянно читает, пока не получит непустое значение.

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

Мой процесс A контролирует поток. Он инициирует вещи, записывая 1 (сообщение в B), а затем входит в цикл с 10 итерациями, где читает значение (сообщение из B), добавляет 1 и затем записывает результат (сообщение в B). Наконец, он ожидает последнего сообщения от B, а затем записывает сообщение «выход» в B и завершает работу.

Мой процесс B находится в условно бесконечном цикле, который читает значение (сообщение от A), добавляет 10, а затем записывает результат (сообщение в A). Если B когда-либо прочитает сообщение «выйти», оно немедленно завершится.

Я хотел продемонстрировать, что связь полностью синхронна, поэтому я ввел задержку в обоих циклах процесса A и B.

Обратите внимание, что процедура readLine находится в узком цикле, который непрерывно использует как процессор, так и файловую систему, ожидая ввода. Задержка PING может быть добавлена ​​к циклу, но тогда процессы не будут такими отзывчивыми.

Я использую настоящий канал для удобства запуска процессов A и B. Но канал не работает, так как через него не проходит никакой связи. Все общение происходит через мои временные "трубочные" файлы.

С тем же успехом я мог бы использовать START / B для запуска процессов, но затем я должен определить, когда они оба завершаются, чтобы я знал, когда удалять временные файлы «канала». Гораздо проще использовать трубу.

Я решил поместить весь код в один файл - главный скрипт, который запускает A и B, а также код для A и B. Я мог бы использовать отдельный файл скрипта для каждого процесса.

test.bat

@echo off

if "%~1" equ "" (

    copy nul pipe1.txt >nul
    copy nul pipe2.txt >nul

    "%~f0" A <pipe1.txt >>pipe2.txt | "%~f0" B <pipe2.txt >>pipe1.txt

    del pipe1.txt pipe2.txt

    exit /b

)


setlocal enableDelayedExpansion
set "prog=%~1"
goto !prog!


:A
call :writeLine 1
for /l %%N in (1 1 5) do (
  call :readLine
    set /a ln+=1
  call :delay 1
    call :writeLine !ln!
)
call :readLine
call :delay 1
call :writeLine quit
exit /b


:B
call :readLine
if !ln! equ quit exit /b
call :delay 1
set /a ln+=10
call :writeLine !ln!
goto :B


:readLine
set "ln="
set /p "ln="
if not defined ln goto :readLine
set "ln=!ln:~0,-1!"
>&2 echo !prog!  reads !ln!
exit /b


:writeLine
>&2 echo !prog! writes %*
echo(%*.
exit /b


:delay
setlocal
set /a cnt=%1+1
ping localhost /n %cnt% >nul
exit /b

--ВЫХОД--

C:\test>test
A writes 1
B  reads 1
B writes 11
A  reads 11
A writes 12
B  reads 12
B writes 22
A  reads 22
A writes 23
B  reads 23
B writes 33
A  reads 33
A writes 34
B  reads 34
B writes 44
A  reads 44
A writes 45
B  reads 45
B writes 55
A  reads 55
A writes 56
B  reads 56
B writes 66
A  reads 66
A writes quit
B  reads quit

Жизнь немного проще с языком высшего уровня. Ниже приведен пример, который использует VBScript для процессов A и B. Я все еще использую пакет для запуска процессов. Я использую очень крутой метод, описанный в разделе Возможно ли встроить и выполнить VBScript в пакетном файле без использования временного файла? встроить несколько VBS-скриптов в один пакетный скрипт.

С более высоким языком, таким как VBS, мы можем использовать обычный канал для передачи информации от A к B. Нам нужен только один временный файл "pipe" для передачи информации от B обратно к A. Поскольку у нас теперь есть работающий канал, A процессу не нужно отправлять сообщение «выход» в B. Процесс B просто зацикливается, пока не достигнет конца файла.

Конечно, приятно иметь доступ к правильной функции сна в VBS. Это позволяет мне легко вводить небольшую задержку в функцию readLine, чтобы дать CPU перерыв.

Однако в readLIne есть одна морщина. Сначала я получал периодические сбои, пока не понял, что иногда readLine обнаруживает информацию, доступную на stdin, и сразу же пытается прочитать строку, прежде чем B сможет закончить писать строку. Я решил проблему, введя небольшую задержку между проверкой конца файла и чтением. Задержка в 5 мсек, казалось, сделала мой трюк, но я удвоил это до 10 мсек, чтобы быть в безопасности. Очень интересно, что партия не страдает этой проблемой. Мы кратко обсудили это (5 коротких сообщений) по адресу http://www.dostips.com/forum/viewtopic.php?f=3&t=7078#p47432 .

<!-- : Begin batch script
@echo off
copy nul pipe.txt >nul
cscript //nologo "%~f0?.wsf" //job:A <pipe.txt | cscript //nologo "%~f0?.wsf" //job:B >>pipe.txt
del pipe.txt
exit /b


----- Begin wsf script --->
<package>

<job id="A"><script language="VBS">

  dim ln, n, i
  writeLine 1
  for i=1 to 5
    ln = readLine
    WScript.Sleep 1000
    writeLine CInt(ln)+1
  next
  ln = readLine

  function readLine
    do
      if not WScript.stdin.AtEndOfStream then
        WScript.Sleep 10 ' Pause a bit to let B finish writing the line
        readLine = WScript.stdin.ReadLine
        WScript.stderr.WriteLine "A  reads " & readLine
        exit function
      end if
      WScript.Sleep 10 ' This pause is to give the CPU a break
    loop
  end function

  sub writeLine( msg )
    WScript.stderr.WriteLine "A writes " & msg
    WScript.stdout.WriteLine msg
  end sub

</script></job>

<job id="B"> <script language="VBS">

  dim ln, n
  do while not WScript.stdin.AtEndOfStream
    ln = WScript.stdin.ReadLine
    WScript.stderr.WriteLine "B  reads " & ln
    n = CInt(ln)+10
    WScript.Sleep 1000
    WScript.stderr.WriteLine "B writes " & n
    WScript.stdout.WriteLine n
  loop

</script></job>

</package>

Вывод такой же, как и для чистого пакетного решения, за исключением того, что конечных строк "выход" нет.

dbenham
источник
4

Примечание. Оглядываясь назад, снова читая вопрос, он не выполняет то, что ему задают. Поскольку, хотя он связывает два процесса вместе (интересным образом, который бы работал даже по сети!), Он не связывает оба пути.


Я надеюсь, что вы получите несколько ответов на это.

Вот мой ответ, но не принимайте его, ждите других ответов, мне очень хотелось бы увидеть другие ответы.

Это было сделано из Cygwin. И используя команду 'nc' (умная). 'Wc -l' просто считает строки. Так что я связываю любые две команды, в данном случае, echo и wc, используя nc.

Команда слева была сделана первой.

nc - это команда, которая может a) создать сервер или b) подключиться, как команда telnet в режиме raw, к серверу. Я использую использование 'a' в левой команде и использование 'b' в правой команде.

Таким образом, nc сидел и слушал, ожидая ввода, а затем направил бы этот ввод wc -lи посчитал количество строк и вывел число введенных строк.

Затем я запустил строку, чтобы отразить некоторый текст и отправить этот raw на 127.0.0.1:123, который является упомянутым сервером.

введите описание изображения здесь

Вы можете скопировать команду nc.exe из cygwin и попробовать использовать ее и нужный файл cygwin1.dll в той же директории. Или вы можете сделать это из самого Cygwin, как я. Я не вижу nc.exe в gnuwin32. У них есть поиск http://gnuwin32.sourceforge.net/ и nc или netcat не подходят. Но вы можете получить Cygwin https://cygwin.com/install.html

barlop
источник
Мне понадобилось десять раз, чтобы прочитать это, прежде чем я понял, что происходит ..... это довольно умно
DarthRubik
@DarthRubik да, и вы можете использовать, netstat -aon | find ":123"чтобы увидеть, что команда слева создала сервер
barlop
Но как вы общаетесь в другом направлении (т. Е. От wcспины к echoкоманде).
ДартРубик
Когда я пытаюсь с помощью nc сделать это обоими способами, я не могу заставить его работать, возможно, nc зацикливается.
Барлоп
nc -lp9999 | prog1 | prog2 | nc 127.0.0.1 9999возможно, потребуется предпринять шаги для своевременного
сброса
2

Один из способов (я бы предпочел не делать этого, но сейчас я собираюсь сделать это) - написать приложение на C #, которое сделает это за вас. Я не реализовал некоторые ключевые функции в этой программе (например, фактически использовал предоставленные мне аргументы), но вот он:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;


namespace Joiner
{
    class Program
    {
        static Process A;
        static Process B;
        static void AOutputted(object s, DataReceivedEventArgs a)
        {
            Console.WriteLine("A:" + a.Data);
            //Console.WriteLine("Sending to B");
            B.StandardInput.WriteLine(a.Data);
        }
        static void BOutputted(object s, DataReceivedEventArgs a)
        {
            Console.WriteLine("B:" + a.Data);
            //Console.WriteLine("Sending to A");
            A.StandardInput.WriteLine(a.Data);
        }
        static void Main(string[] args)
        {

            A = new Process();
            B = new Process();
            A.StartInfo.FileName = "help";
            B.StartInfo.FileName = "C:\\Users\\Owner\\Documents\\Visual Studio 2010\\Projects\\Joiner\\Test\\bin\\Debug\\Test.exe";

            A.StartInfo.Arguments = "mkdir";
            //B.StartInfo.Arguments = "/E /K type CON";

            A.StartInfo.UseShellExecute = false;
            B.StartInfo.UseShellExecute = false;

            A.StartInfo.RedirectStandardOutput = true;
            B.StartInfo.RedirectStandardOutput = true;

            A.StartInfo.RedirectStandardInput = true;
            B.StartInfo.RedirectStandardInput = true;

            A.OutputDataReceived += AOutputted;
            B.OutputDataReceived += BOutputted;

            A.Start();
            B.Start();

            A.BeginOutputReadLine();
            B.BeginOutputReadLine();



            while (!A.HasExited || !B.HasExited) { }
            Console.ReadLine();

        }
    }
}

Затем, в конце концов, когда эта программа будет полностью функциональной и код отладки будет удален, вы будете использовать его примерно так:

joiner "A A's args" "B B's Args"
DarthRubik
источник