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

8 Andreaw Fean
položil/-a 20.7.2019

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
6 roman.hocke
odpověděl/-a 26.7.2019
 
upravil/-a 29.7.2019

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.2019
  • 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.2019
  • Andreaw Fean : To jsem pochopil. Ale nechová se to tak. 28.7.2019
  • 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.2019
  • 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.2019

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.