Dwa programy z połączonymi StdIn i StdOut

11

Załóżmy, że mam dwa programy o nazwie ProgramAi ProgramB. Chcę je uruchomić jednocześnie w interpreterze cmd systemu Windows. Ale chcę StdOutod ProgramApodpięty do StdInz ProgramBoraz StdOutod ProgramBpodpięty do StdInz ProgramA.

Coś takiego

 ________________ ________________
| | | |
| StdIn (== ← === ← == (StdOut |
| Program A | | Program B |
| | | |
| StdOut) == → === → ==) StdIn |
| ________________ | | ________________ |

Czy jest na to jakaś komenda - jakiś sposób na osiągnięcie tej funkcjonalności z cmd?

DarthRubik
źródło
4
W unixie użyłbym nazwanych potoków, aby to zrobić, Windows ma coś, co nazywa się potokami nazwanymi, co jest zupełnie inne i prawdopodobnie nie dotyczy.
Jasen
@Jasen zgodnie z komentarzem tutaj linuxjournal.com/article/2156 nazwane potoki mogą działać na cygwinie .. jeśli tak, to może mógłbyś opracować i opublikować jako odpowiedź, choć warto najpierw przetestować na
cygwinie
Podejrzewam, że programy również musiałyby być napisane, aby nie zapętlały. Więc jeśli stdout jest pustą linią, to nie przesyłaj do stdin, a jeśli nic nie ma do stdin, to wyjdź. Unikając w ten sposób nieskończonej pętli? Ale z braku zainteresowania, jaka jest aplikacja?
barlop
2
powiedz, że chcesz mieć dwa wystąpienia „Eliza” do rozmowy?
Jasen
Jeśli programy nie są starannie zaprojektowane do obsługi tego, mogą znaleźć się w impasie - oba czekają na dane wejściowe od drugiego i nic się nigdy nie dzieje (lub oba próbują pisać do pełnych buforów i nigdy nie czytają niczego, aby je opróżnić )
Random832,

Odpowiedzi:

5

Można to zrobić nie tylko, ale tylko za pomocą pliku wsadowego! :-)

Problem można rozwiązać, używając pliku tymczasowego jako „potoku”. Komunikacja dwukierunkowa wymaga dwóch plików „potokowych”.

Proces A odczytuje stdin z „potoku 1” i zapisuje stdout na „potoku 2”
Proces B odczytuje stdin z „potoku 2” i zapisuje stdout na „potoku 1”

Ważne jest, aby oba pliki istniały przed uruchomieniem któregokolwiek z procesów. Pliki powinny być puste na początku.

Jeśli plik wsadowy próbuje odczytać z pliku, który znajduje się na bieżącym końcu, po prostu nic nie zwraca, a plik pozostaje otwarty. Więc moja procedura readLine ciągle czyta, dopóki nie otrzyma niepustej wartości.

Chcę móc czytać i pisać pusty ciąg, więc moja procedura writeLine dołącza dodatkowy znak, który readLine usuwa.

Mój proces A kontroluje przepływ. Inicjuje rzeczy, pisząc 1 (wiadomość do B), a następnie wchodzi w pętlę z 10 iteracjami, w której odczytuje wartość (wiadomość z B), dodaje 1, a następnie zapisuje wynik (wiadomość do B). W końcu czeka na ostatnią wiadomość z B, a następnie zapisuje wiadomość „wyjdź” do B i kończy działanie.

Mój proces B znajduje się w warunkowo nieskończonej pętli, która odczytuje wartość (wiadomość z A), dodaje 10, a następnie zapisuje wynik (wiadomość do A). Jeśli B kiedykolwiek czyta komunikat „wyjdź”, to natychmiast się kończy.

Chciałem wykazać, że komunikacja jest w pełni synchroniczna, dlatego wprowadzam opóźnienie zarówno w pętlach procesowych A, jak i B.

Zauważ, że procedura readLine znajduje się w ciasnej pętli, która stale nadużywa zarówno procesora, jak i systemu plików, czekając na dane wejściowe. Do pętli można dodać opóźnienie PING, ale wówczas procesy nie będą tak responsywne.

Używam prawdziwej potoku jako wygody do uruchamiania zarówno procesów A, jak i B. Ale rura nie działa, ponieważ nie przechodzi przez nią komunikacja. Cała komunikacja odbywa się za pośrednictwem moich tymczasowych plików „potokowych”.

Równie dobrze mogłem użyć START / B do uruchomienia procesów, ale potem muszę wykryć, kiedy oba kończą się, aby wiedzieć, kiedy należy usunąć tymczasowe pliki „potoku”. Korzystanie z rury jest znacznie prostsze.

Zdecydowałem się umieścić cały kod w jednym pliku - skrypcie głównym, który uruchamia A i B, a także kod A i B. Mogłem użyć osobnego pliku skryptu dla każdego procesu.

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

--WYNIK--

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

Życie jest trochę łatwiejsze dzięki językowi wyższego poziomu. Poniżej znajduje się przykład, który używa VBScript dla procesów A i B. Nadal używam partii do uruchomienia procesów. Używam bardzo fajnej metody opisanej na stronie Czy można osadzić i uruchomić VBScript w pliku wsadowym bez użycia pliku tymczasowego? aby osadzić wiele skryptów VBS w jednym skrypcie wsadowym.

W języku wyższym, takim jak VBS, możemy użyć zwykłego potoku, aby przekazać informacje z A do B. Potrzebujemy tylko jednego tymczasowego pliku „potoku”, aby przekazać informacje z B z powrotem do A. Ponieważ mamy teraz działający potok, A proces nie musi wysyłać komunikatu „wyjdź” do B. Proces B po prostu zapętla się, aż osiągnie koniec pliku.

Na pewno miło jest mieć dostęp do odpowiedniej funkcji uśpienia w VBS. Pozwala mi to łatwo wprowadzić krótkie opóźnienie w funkcji readLine, aby przerwać procesor.

Jednak w jednym czytaniu jest jedna zmarszczka. Na początku pojawiały się sporadyczne awarie, dopóki nie zdałem sobie sprawy, że czasami readLine wykryje, że informacje są dostępne na standardowym wejściu, i od razu spróbuję odczytać linię, zanim B będzie miał szansę zakończyć pisanie linii. Rozwiązałem problem, wprowadzając krótkie opóźnienie między testem końca pliku a odczytem. Wydawało mi się, że opóźnienie wynoszące 5 ms jest dla mnie wystarczające, ale podwoiłem to do 10 ms, aby być po bezpiecznej stronie. To bardzo interesujące, że partia nie cierpi z powodu tego problemu. Omówiliśmy to krótko (5 krótkich postów) na stronie 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>

Dane wyjściowe są takie same jak w przypadku czystego rozwiązania wsadowego, z tym wyjątkiem, że nie ma tam końcowych wierszy „quit”.

dbenham
źródło
4

Uwaga: Patrząc wstecz, ponownie czytając pytanie, nie robi to, o co zostanie poproszony. Ponieważ chociaż łączy dwa procesy razem (w interesujący sposób, który działałby nawet w sieci!), Nie łączy obu sposobów.


Mam nadzieję, że uzyskasz kilka odpowiedzi na to pytanie.

Oto moja odpowiedź, ale nie akceptuj jej, poczekaj na inne odpowiedzi, chcę zobaczyć inne odpowiedzi.

Zostało to zrobione z cygwina. I za pomocą polecenia „nc” (sprytnego). „Wc -l” po prostu zlicza linie. Łączę więc dowolne dwa polecenia, w tym przypadku echo i wc, używając nc.

Polecenie po lewej zostało wykonane jako pierwsze.

nc to polecenie, które może a) utworzyć serwer lub b) połączyć się z serwerem jak polecenie telnet w trybie surowym. Używam użycia „a” w lewym poleceniu, a użycie „b” w prawym poleceniu.

Więc nc siedział i nasłuchiwał, czekając na dane wejściowe, a następnie wc -lprzesyłać dane wejściowe do i zliczać linie i wyprowadzać liczbę wprowadzonych linii.

Następnie uruchomiłem wiersz, aby powtórzyć jakiś tekst i wysłać go na adres 127.0.0.1:123, który jest wspomnianym serwerem.

wprowadź opis zdjęcia tutaj

Możesz skopiować polecenie nc.exe z cygwin i spróbować użyć tego pliku cygwin1.dll w tym samym katalogu. Albo możesz to zrobić od samego cygwina, tak jak ja. Nie widzę nc.exe w gnuwin32. Mają wyszukiwanie http://gnuwin32.sourceforge.net/ i nc lub netcat nie pojawia się. Ale możesz uzyskać cygwin https://cygwin.com/install.html

barlop
źródło
Przeczytanie tego zajęło mi dziesięć razy, zanim zrozumiałem, co się dzieje ... to całkiem sprytne
DarthRubik
@DarthRubik tak, i możesz netstat -aon | find ":123"zobaczyć, że polecenie po lewej stronie utworzyło serwer
barlop
Ale w jaki sposób komunikujesz się w innym kierunku (tj. Od wctyłu do echopolecenia).
DarthRubik
kiedy próbuję z nc zrobić to na dwa sposoby, nie mogę zmusić go do działania, może nc przechodzi w jakąś pętlę.
barlop
nc -lp9999 | prog1 | prog2 | nc 127.0.0.1 9999konieczne może być podjęcie kroków w celu zapewnienia, że ​​bufory zostaną opróżnione w odpowiednim czasie
Jasen
2

Jednym z hacków (wolałbym tego nie robić, ale o to teraz chodzi) jest napisanie aplikacji C # , która zrobi to za Ciebie. Nie wdrożyłem niektórych kluczowych funkcji w tym programie (takich jak używanie argumentów podanych mi), ale oto:

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();

        }
    }
}

Następnie, gdy ten program będzie w pełni funkcjonalny i kod debugowania zostanie usunięty, użyjesz go w następujący sposób:

joiner "A A's args" "B B's Args"
DarthRubik
źródło