Zaseknuté čtení z proc_open rubrika: Programování: PHP

6 Andreaw Fean
položil/-a 20.7. 21:51

Ahoj. Mohl by mi prosím někdo vysvětlit následující chování?

Mám takovýto script:

#!/bin/env php
<?php
$count = 0;
while (True) {
    echo "<123456";
    $str = fgets(STDIN);
    if (trim($str) == ':q') {
        break;
    }
    fwrite(STDOUT, "> $str\n");
    fwrite(STDERR, "(count: $count)\n");
    $count++;
}
 
exit(0);

A teď se pokouším tento script obsluhovat. Otevřu si process:

$process = proc_open('bin/readwrite.php', [
    0 => array('pipe', 'r'),
    1 => array('pipe', 'w'),
    2 => array('pipe', 'w'),
], $pipes, __dir__);

tak, a teď mám takovýto problém:
První problém je, že když načtu velkej blok najednout, tak je to v pořádku:

echo fread($pipes[1], 1024);

Když ale budu číst malé bloky, tak pokud čtu více, než kolik zbejvá, tak se to kousne:

echo fread($pipes[1], 4);
echo fread($pipes[1], 4);

Chápu to tak, že u prvního volání může být velikost větší než požadovaná, ale u druhého si musím nejdřív zjistit, jestli nějaké zbývají pomocí:

stream_get_meta_data($pipes[1])['unread_bytes'];
a musí to být přesně. A já se ptám, proč je to tak? Proč se to kousne? Druhá otázka je k tomu, jak mám přečíst chybovej stream z dotyčného scriptu. Naivní pokus:
echo fread($pipes[1], 1024);
echo fread($pipes[2], 1024);
ani jen
echo fread($pipes[2], 1024);
mi nefunguje :-( Kousne se to, analogicky k předchozímu. Akorád že na velikosti bufferu nezáleží. Pokud byste někdo věděl, tak díky za vysvětlení.
odkaz
7 roman.hocke
odpověděl/-a 26.7. 16:07
 
upravil/-a 29.7. 11:14

Nekousne se, říká se tomu blokující volání. Když voláš fread() a v bufferu není ani bajt (ani EOF), tak on čeká, až se tam aspoň jeden bajt objeví, do té doby "blokuje" (čeká ve fread()). Podívej se na stream_select() - ten ti otestuje, ze kterých streamů můžeš aktuálně číst bez blokování (t.j. ve kterých streamech na tebe čeká aspoň jeden bajt), resp. zablokuje se, dokud aspoň v jednom ze streamů něco nepřijde.

V podstatě chceš asi něco jako (neotestováno):

<?php
 
$process = proc_open('./readwrite.php', [
    0 => array('pipe', 'r'),
    1 => array('pipe', 'w'),
    2 => array('pipe', 'w'),
], $pipes, __DIR__);
 
$to_be_written = "aaa\n";
 
for (;;) {
 
    $sr = array($pipes[1], $pipes[2]);
    $sw = array();
    $se = array($pipes[0], $pipes[1], $pipes[2]);
 
    if (strlen($to_be_written) !== 0)
        $sw[] = $pipes[0];
 
    // cekej max. 1 vterinu na jakoukoli aktivitu ve streamech
    $changed = stream_select($sr, $sw, $se, 1);
    if ($changed === FALSE)
        die("nejaka chyba v stream_select()\n");
 
    // zadna aktivita na streamech, jen uplynul timeout
    if ($changed === 0) {
 
        echo "tick...\n";
        continue;
    }
 
    // koukni, na kterych streamech byla jaka aktivita a zpracuj ji
    if (in_array($pipes[1], $sr))
        printf("stdout: %s\n", fread($pipes[1], 1024));
    if (in_array($pipes[2], $sr))
        printf("stderr: %s\n", fread($pipes[2], 1024));
 
    if (in_array($pipes[0], $sw)) {
 
        $written = fwrite($pipes[0], $to_be_written, 1024);
        if ($written === FALSE)
            die("nejaka chyba v fwrite()\n");
 
        $to_be_written = substr($to_be_written, $written);
    }
 
    if (in_array($pipes[0], $se))
        die("chyba na stdin\n");
    if (in_array($pipes[1], $se))
        die("chyba na stdout\n");
    if (in_array($pipes[2], $se))
        die("chyba na stderr\n");
}

Komentáře

  • Andreaw Fean : Díky, funkci stream_select() neznám. Mám tam problém s tím, že ať vracím v tom scriptu cokoliv, zápis do STDOUT, STDERR, nebo čtení z STDIN, tak mi to stejně vždycky vrací vyplněný $sw a v něm $pipes[0]. Čím to je? 26.7. 23:05
  • roman.hocke : Do $sw dáváš stream, do kterého chceš zapisovat (odesílat data). Když je ten stream schopný nechat odeslat další data, select() ti ho vrátí. To znamená, že na něj můžeš volat fwrite(). 27.7. 22:53
  • Andreaw Fean : To jsem pochopil. Ale nechová se to tak. 28.7. 12:17
  • Andreaw Fean : Tedy, pochopil jsem to tak, že když mám v `bin/readwrite.php` jen výpis a žádné čtení, tak by mě to mělo vracet vyplněný $sr, a $sw by mělo být prázdné. Ale tak se to nechová. Ať udělám co udělám, vždycky to vrátí jen $sw, a já tak nejsem schopen zjistit, jestli ten script čeká, až něco napíšu, nebo má jen pauzu, nebo jen vypisuje, nebo nedělá nic a už skončil. Pokud se ta funkce chová takto, tak mi je na nic. Jsem na tom stejně jako bez ní. 28.7. 12:21
  • roman.hocke : Asi jsem se špatně vyjádřil. Do $sw dáváš stream, když do něj chceš zapisovat (poslat data na STDIN toho spuštěného procesu) a select() ti řekne, jestli je ten stream na to připraven. Upravil jsem kód a imho běhá tak jak má (testnuto). U fread() si ještě otestuj, jestli nevrátil FALSE. To se totiž stane, pokud se spuštěný proces ukončil (pípa zanikla). Taky testuj, jestli proces sám o sobě neskončil. 29.7. 11:16

Pro zobrazení všech 2 odpovědí se prosím přihlaste:

Rychlé přihlášení přes sociální sítě:

Nebo se přihlaste jménem a heslem:

Zadejte prosím svou e-mailovou adresu.
Zadejte své heslo.