 |
Forum PHP Romania - Discutii despre PHP, MySQL, Javascript, AJAX, etc Comunitatea PHP Romania
|
| Subiectul anterior :: Subiectul următor |
| Autor |
Mesaj |
johnutz
Data înscrierii: 20/Iul/2004
Mesaje: 956
Locație: Între scaun și tastatură
|
| Trimis: Mar Iun 21, 2005 2:28 am Titlul subiectului: "Conversatie" cu un proces |
|
|
Vreau sa pornesc o aplicatie de consola si sa conversez cu ea, adica dau o comanda (urmata de un enter) si sa preiau (intr-o variabila) textul pe care mi l-ar fi scuipat in mod normal pe ecran.
Am gasit in acest sens utila functia proc_open(). Am incercat exemplul din manual dar nu e ce vreau eu, si anume sa pot sa citesc output-ul imediat dupa ce am dat o comanda.
E posibil totusi sa dau comenzile una dupa alta si apoi sa citesc _tot_ output-ul generat de toate comenzile, dar asta nu are utilitate in cazl in car imi doresc ca in functie de ce obtin de la comanda n, sa decid care va fi comanda n+1.
Pe baza exemplului din manual am facut urmatoarea incercare:
Cod: /************************************
codul din manual, cu mici ajustari
*************************************/
ob_implicit_flush();
ini_set ('max_execution_time', 6); // ca sa nu astept
$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("file", "error-output.txt", "a") // stderr is a file to write to
);
$process = proc_open("cmd", $descriptorspec, $pipes); // pornesc un command prompt
if (is_resource($process)) {
// $pipes now looks like this:
// 0 => writeable handle connected to child stdin
// 1 => readable handle connected to child stdout
// Any error output will be appended to /tmp/error-output.txt
echo 'dau prima comanda ';
fwrite($pipes[0], "dir\r\n"); // dau comanda "dir"
//fclose($pipes[0]); // mut linia asta mai jos
echo 'rezultatul primei comenzi: ';
while(!feof($pipes[1])) {
echo fgets($pipes[1], 1024);
}
echo 'dau a doua comanda ';
fwrite($pipes[0], "dir c:\r\n");
echo 'rezultatul comenzii nr 2: ';
while(!feof($pipes[1])) {
echo fgets($pipes[1], 1024);
}
fclose($pipes[0]);
fclose($pipes[1]);
// It is important that you close any pipes before calling
// proc_close in order to avoid a deadlock
$return_value = proc_close($process);
//echo "command returned $return_value\n";
}
Rezultatul e ca nu mai iese din primul while si scriptul ruleaza la infinit (chiar daca trece max_execution_time). Daca deschid task manager si din lista de procese inchid "cmd", abia atunci se incheie executia scriptului si apare in pagina eroarea referitoare la max_execution_time.
Ceea ce vreau sa obtin e cam asa: Cod: ob_implicit_flush();
ini_set ('max_execution_time', 6); // ca sa nu astept
/**************************************
incercare (nereusita) cu OOP
**************************************/
class proces {
var $descriptorspec;
var $process;
var $pipes;
var $startup_msg;
function proces($cmd) { // Constuctor. Lanseaza comanda si preia mesajul de intampinare
$this->descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("pipe", "w") // stderr is a file to write to
);
$this->pipes = array();
$this->process = proc_open($cmd, $this->descriptorspec, $this->pipes);
while (!feof($this->pipes[1])) { // nu iese din bucla. si as prefera bucata comentata mai jos, in loc de acest while
//$this->startup_msg .= fgets($this->pipes[1], 1024);
echo fgets($this->pipes[1], 1024);
}
# $read = array($this->pipes[1]);
# $n = 0;
# while ($n < 1) {
# $this->startup_msg .= fgets($this->pipes[1], 1024);
# $n = stream_select($read, $write = NULL, $except = NULL, 0);
# }
echo '<br>am deschis. ' . uniqid(''); // ca sa difere continutul paginii de la o rulare la alta
}
function query($q) { // trimite o comanda catre stdin-ul procesului si returneaza ce obtine de la stdout-ul lui
$retval = null;
fwrite($this->pipes[0], $q);
$read = array($this->pipes[1]);
$n = 0;
while ($n < 1) {
$retval .= fgets($this->pipes[1], 1024);
$n = stream_select($read, $write = NULL, $except = NULL, 0);
}
return $retval;
}
function close() { // curatenie
fclose($this->pipes[0]);
fclose($this->pipes[1]);
fclose($this->pipes[2]);
proc_close($this->process);
echo '<br>am inchis. ' . uniqid(''); // ca sa difere continutul paginii de la o rulare la alta
}
}
$test = new proces('cmd');
echo '<br>mesaj de pornire: ';
echo $test->startup_msg;
echo '<br>rezultatul comenzii: ';
echo $test->query("dir\r\n");
$test->close();
Am gasit prin manual stream_select() dar nu cred ca am folosit-o cum trebuie, pentru ca iese din while dupa o singura iteratie.
Cred ca ar fi util si stream_blocking(), dar nu am reusit sa inteleg mare lucru.
Scuze pentru marimea post-ului. |
|
| Sus |
|
arond
Data înscrierii: 11/Mar/2004
Mesaje: 580
Locație: 127.0.0.1
|
| Trimis: Mar Iun 21, 2005 2:52 am Titlul subiectului: |
|
|
Solutiile simple sunt cele bune, foloseste cu incredere exec. Singurul dezavantaj, dar in multe cazuri neimportant, este ca vei porni alt proces la fiecare executie.
Recte:
Cod: function query($q)
{
exec($q, $o);
return $o;
}
LATER EDIT:
-------------
1. E normal ca !feof(...) sa fie TRUE tot timpul, in cazul primului while... stream-ul ramane deschis cat timp procesul e deschis si nici la EOF nu are cum sa ajunga (ce ar insemna EOF, in cazul asta ? presupun ca nu caracterul ASCII EOF :))..
2. E normal ca a doua varianta sa se comporte ciudat, cam din aceleasi motive. In functie de timing, risti sa citesti output-ul trunchiat sau deloc.
Daca tii neaparat la varianta cu stream_select(), ce trebuie sa faci e sa stabilesti un timeout, in care daca nu se intampla nimic pe streamul de output ($read, in codul tau), sa decizi ca asta a fost, si ce ai adunat e tot output-ul. Recte:
Cod: do
{
$retval .= fgets($this->pipes[1], 1024);
$n = stream_select($read, $write = NULL, $except = NULL, 5);
}while ($n == 1); unde 5 secunde e tocmai timeout-ul cu pricina.
Numa' bune. |
|
| Sus |
|
johnutz
Data înscrierii: 20/Iul/2004
Mesaje: 956
Locație: Între scaun și tastatură
|
| Trimis: Mar Iun 21, 2005 9:36 am Titlul subiectului: |
|
|
M-am inselat cand am zis ca nu iese din while. De fapt ceea ce face e sa citeasca fiecare rand din output si cand nu mai are ce sa citeasca, se blocheaza. Concluzia e ca feof() nu e solutia potrivita ca criteriu de iesire din bucla.
Practic, ce am modificat eu fata de codul din manual a fost sa mut linia fclose($pipes[0]); mai jos, dupa while.
Hmm.. asta inseamna ca fgets($pipes[1], 1024); se blocheaza daca $pipes[0] nu e inchis. Mi se pare absurda treaba asta. |
|
| Sus |
|
arond
Data înscrierii: 11/Mar/2004
Mesaje: 580
Locație: 127.0.0.1
|
| Trimis: Mar Iun 21, 2005 11:38 am Titlul subiectului: |
|
|
Cred ca n-ai fost atent :).
Reluam:
eof() intoarce TRUE daca stream-ul ajunge la EOF (in cazul unui fisier pe disk sau chiar al unui socket, e evident ce inseamna asta). In cazul unui pipe, avand in vedere ca procesul pornit mai devreme e inca activ, eof() nu va intoarce niciodata TRUE pana cand procesul nu e terminat. Din punctul de vedere al pipe-ului, nu are nici o relevanta ce comenzi trimiti shell-ului, el va intoarce fie siruri goale, fie va bloca apelantul pana cand mai apare ceva pe teava. Daca fgets() e blocant sau nu, setezi prin stream_set_blocking(..).
In ceea ce priveste stream_select(...), acesta va astepta fie timeout secunde sau pana cand starea stream-ului se schimba. Daca timeout e 0, se va intoarce brusc, cu starea stream-ului la momentul apelului.
In cazul codului tau, teoretic risti sa citesti output-ul trunchiat pentru ca se poate intampla ca intre fgets(..) si stream_select(..) sa nu se schimbe starea stream-ului (respectiv sa nu apara nici un caracter in pipe si atunci vei iesi din bucla, desi poate ca o sa mai apara ulterior caractere ca rezultat al comenzii date.
Problema se reduce, pana la urma, la gasirea unui criteriu dupa care sa decizi ca o comanda si-a terminat executia, iar asta este dificil in abordarea ta. Repet, din punctul de vedere al pipe-urilor cu pricina, nu are nici o relevanta ca tu executi comenzi, ele doar redirecteaza output-ul procesului initial (consola, shell, whatever) spre script-ul tau, pe masura ce acest output apare. Singurul criteriu care imi vine in minte este "daca timp de X nu apar caractere noi inseamna ca executia comenzii s-a terminat" dar asta e perdant pentru ca poti avea situatia in care o comanda sa stea mai mult decat X pana sa trimita ceva, etc/etc
Sfatul medicului: exec()... :)
Numa' bune. |
|
| Sus |
|
johnutz
Data înscrierii: 20/Iul/2004
Mesaje: 956
Locație: Între scaun și tastatură
|
| Trimis: Mie Iun 22, 2005 12:10 am Titlul subiectului: |
|
|
De ce..
Offf... intr-adevar, exec() e mult mai simplu de folosit, dar vreau sa apelez o alta aplicatie, care sa ruleze in mod interactiv, nu in mod batch. Am dat aici ca exemplu "cmd", pentru ca mi s-a parut simplu, suggestiv etc. As putea sa inlocuiesc "cmd" cu:
- "mysql.exe -u user -p parola"
- "telnet hostname.dom"
- "ssh -l username hostname.dom"
Desigur ca folosirea mysql si telnet in asa mod nu este justificata ;). SSH poate ca da.
arond a scris: In cazul unui pipe, avand in vedere ca procesul pornit mai devreme e inca activ, eof() nu va intoarce niciodata TRUE pana cand procesul nu e terminat.
In exemplul din manual procesul este inchis cu proc_close() la sfarsit, dupa ce feof() e TRUE si iese din while. Sau "a termina procesul" inseamna altceva?
arond a scris: Daca fgets() e blocant sau nu, setezi prin stream_set_blocking(..).
Din pacate nu am reusit sa inteleg (nici macar din manual) la ce se refera aceasta blocare: O sa imi intepeneasca script-ul daca fgets() vrea sa citeasca si nu are ce?
PHP Manual, stream_select() a scris: The streams listed in the read array will be watched to see if characters become available for reading (more precisely, to see if a read will not block - in particular, a stream resource is also ready on end-of-file, in which case an fread() will return a zero length string).
Adica sa vada daca (in cazul in care as incerca sa citesc din pipe) mai are ce sa citeasca, pentru ca sa nu se blocheze?
arond a scris: In ceea ce priveste stream_select(...), acesta va astepta fie timeout secunde sau pana cand starea stream-ului se schimba. Daca timeout e 0, se va intoarce brusc, cu starea stream-ului la momentul apelului.
Inseamna ca greseala mea a fost ca am pus 0. Am incercat bucata aia de cod de la tine cu timeout de 5, dar tot se blocheaza la citire.
rezolvarea
arond a scris: Problema se reduce, pana la urma, la gasirea unui criteriu dupa care sa decizi ca o comanda si-a terminat executia, iar asta este dificil in abordarea ta.
Rezolvarea era deja facuta! Dar nu reusisem sa o vad...
Atunci cand butonezi intr-o consola, de unde poti sa stii ca s-a incheiat comanda si ca poti sa ii mai dai una? Te uiti pe ecran sa vezi daca e un cursor care clipeste? E irelevant. Ala clipeste mereu, ca aia e treaba lui, nu il intereseaza ca intra/iese ceva pe stdin/stdout.
Solutia e sa verific daca in coada string-ului, pe care il construiesc acum citind cate un caracter de pe teava, se afla "command prompt"-ul. Un preg_match() ar face treaba asta.
nedumerire
Am incercat sa folosesc stream_select(), insa fara sa inteleg prea bine ce face. La fel si cu stream_set_blocking().
Ce inseamna ca un stream e in "non-blocking mode" sau in "blocking mode"? |
|
| Sus |
|
arond
Data înscrierii: 11/Mar/2004
Mesaje: 580
Locație: 127.0.0.1
|
| Trimis: Mie Iun 22, 2005 1:19 am Titlul subiectului: |
|
|
Blocking inseamna ca, de exemplu, fgets() va astepta sa citeasca atatea caractere cate i-au fost date sa citeasca. Deci, fgets($s, 1024) va astepta sa fie disponibile 1024 de caractere.
Daca e non-blocking, va intoarce cate caractere sunt disponibile la momentul apelului, chiar daca i-au fost cerute mai multe.
Citeste mai atent comentariile de la proc_open(), si o sa vezi ca bucata aia de cod care te nedumereste -feof() - e data ca exemplu de cum sa produci un deadlock:
The behaviour described in the following may depend on the system php runs on. Our platform was "Intel with Debian 3.0 linux".
If you pass huge amounts of data (ca. >>10k) to the application you run and the application for example echos them directly to stdout (without buffering the input), you will get a deadlock. This is because there are size-limited buffers (so called pipes) between php and the application you run. The application will put data into the stdout buffer until it is filled, then it blocks waiting for php to read from the stdout buffer. In the meantime Php filled the stdin buffer and waits for the application to read from it. That is the deadlock.
A solution to this problem may be to set the stdout stream to non blocking (stream_set_blocking) and alternately write to stdin and read from stdout.
Altul spune ca pe Windows XP nu functioneaza stream_set_blocking()... etc Parerea mea este ca tot mecanismul asta e prea dependent de platforma pentru a fi de folos intr-o aplicatie generica. O parere personala, evident.
Iar in ceea ce priveste "command prompt"-ul... roaga-te sa nu iti apara vreun "command prompt like, regexp wise" in output-ul vreunei comenzi :)...
Numa' bune. |
|
| Sus |
|
arond
Data înscrierii: 11/Mar/2004
Mesaje: 580
Locație: 127.0.0.1
|
| Trimis: Mie Iun 22, 2005 10:42 am Titlul subiectului: |
|
|
Si inca ceva, ca mi-am adus aminte: conditia cu feof() functioneaza doar daca procesul se termina dupa ce si-a varsat output-ul, respectiv atunci cand inchide pipe-ul pe partea lui. In cazul tau, asta nu va functiona (avand pornit un shell/command prompt/whatever, care ramane activ).
Numa' bune. |
|
| Sus |
|
PHPRomania Bot
Bot Member
Data înscrierii: 27/Dec/2007
Mesaje: 1
Locaţie: Server Google |
| Trimis: Mie Dec 26, 2007 7:01 pm Titlul subiectului: Ad |
|
|
|
|
|
| Sus |
|
| |
|