Как сделать чтение неблокирующего IPC в Windows?

У меня есть сценарий Perl, который использует внешний инструмент (cleartool) для сбора информации о списке файлов. Я хочу использовать IPC, чтобы не создавать новый процесс для каждого файла:

use IPC::Open2;
my ($cin, $cout);
my $child = open2($cout, $cin, 'cleartool');

Команды, которые возвращают одну строку, работают хорошо. например

print $cin "describe -short $file\n";
my $description = <$cout>;

Команды, которые возвращают несколько строк, ставят меня в тупик, как использовать весь ответ, не зависая из-за блокирующего чтения:

print $cin "lshistory $file\n";
# read and process $cout...

Я попытался установить дескриптор файла для неблокирующего чтения через fcntl:

use Fcntl;
my $flags = '';
fcntl($cout, F_GETFL, $flags);
$flags |= O_NONBLOCK;
fcntl($cout, F_SETFL, $flags);

но Fcntl умирает с сообщением «Ваш поставщик не определил макрос Fcntl F_GETFL».

Я пытался использовать IO::Handle для установки $cout->blocking(0), но это не помогло (он возвращает undef и устанавливает $! в "Неизвестная ошибка").

Я пытался использовать select, чтобы определить, доступны ли данные, прежде чем пытаться читать:

my $rfd = '';
vec($rfd, fileno($cout), 1) = 1;
while (select($rfd, undef, undef, 0) >= 0) {
    my $n = read($cout, $buffer, 1024);
    print "Read $n bytes\n";
    # do something with $buffer...
}

но это зависает, даже ничего не читая. Кто-нибудь знает, как заставить это работать (в Windows)?


person Michael Carman    schedule 20.10.2009    source источник
comment
Что вы планируете делать, не блокируя IO?   -  person jrockway    schedule 21.10.2009
comment
Я хочу читать, пока не перестану получать данные, что кажется более надежным, чем попытка определить, что я достиг конца, анализируя уже полученные данные. (В данных нет указания, что это последняя строка.)   -  person Michael Carman    schedule 21.10.2009
comment
В поисках связанной проблемы я нашел этот коммит на GitHub, который, кажется, вызывает черную магию для создания неблокирующего сокета: " rel="nofollow noreferrer">github.com/kthakore/frozen-bubble/commit/   -  person Nate Glenn    schedule 14.10.2014


Ответы (3)


select работает только с сокетами в Windows. Похоже, что IPC::OpenX использует обычные файловые дескрипторы, поэтому вы не сможете использовать select с созданными им дескрипторами.

Если вам не нужен тайм-аут/обнаружение активности, который предоставляет select, вы можете установить дескрипторы как неблокирующие и просто читать или писать как обычно.

Если вам нужен более тонкий контроль, вам может подойти IPC::Run.

Вы также можете посмотреть на создание socketpair и использовать эти дескрипторы со своими дочерними процессами. Более новые perls (5.8 и выше) поддерживают эмуляцию socketpair в Windows с использованием сокетов TCP.

Если вы попытаетесь клонировать STDOUT и STDERR для программы, работающей без консоли (т.е. запущенной с помощью wperl, а не perl), вы не сможете получить данные через STDIO.

На практике это было огромной болью для меня в нескольких проектах. Лучше всего, как мне показалось, написать дочерний процесс для подключения к родительскому серверу через TCP. Если вы не управляете дочерними процессами, посмотрите на IPC::Run или socketpair.

person daotoad    schedule 20.10.2009
comment
Да, винда отстой. При запуске IPC::Run выполняет упомянутый вами трюк с сокетом. - person ephemient; 21.10.2009
comment
Как я писал в вопросе, мне не удалось сделать дескриптор неблокирующим через Fcntl или IO::Handle. Если вы знаете способ сделать это (который работает в Windows), поделитесь им. - person Michael Carman; 21.10.2009
comment
@Michael: запустите блокирующий ввод-вывод в отдельном процессе. Взаимодействуйте с основным процессом, используя сокеты. Дочерний процесс может блокироваться, но основной процесс может выполнять неблокирующий ввод-вывод на коммуникационных сокетах. Это то, что предлагает даожаба с socketpair. - person ephemient; 21.10.2009
comment
@ephemient, мне пришлось очень постараться, чтобы не включить то же самое в свой пост. - person daotoad; 21.10.2009

Еще один ляп — использовать sysread с большим или маловероятным размером буфера.

    print $cin "$command_for_cleartool\n";
    my ($description, $maxlen, $buffer) = ("", 65336);
    while (my $n = sysread $cout, $buffer, $maxlen) {
        $description .= $buffer;
        last if $n < $maxlen;
    }
    ... do something with $description ...

sysread зависнет, если ровно 0 байтов ввода ожидают чтения. Таким образом, приведенный выше код будет зависать, если cleartool выдаст число, кратное 65336 байтам. Если вы знаете хорошую верхнюю границу размера вывода программы, вы можете использовать это значение для $maxlen выше. В противном случае вы могли бы выбрать большое и маловероятное число и молиться...

person mob    schedule 08.12.2009

Неблокирующий ввод-вывод затруднен в Windows. В этом случае вы можете отправить вывод cleartool в обычный файл и использовать seek для сброса флага eof в файле каждый раз при чтении из файла:

my($cin, $cout);
my $somefile = "some/file";
open($cin, "| cleartool > $somefile");
open($cout, '<', $somefile);

...

print $cin "$command_for_cleartool\n";
# if necessary, wait until cleartool finishes with new output
seek $cout, 0, 1;     # clear eof condition from previous read
my @cleartool_output = <$cout>;  # capture recent output
... process output ...

Хотя это, вероятно, не будет работать так хорошо, если cleartool буферизует свой вывод.

person mob    schedule 08.12.2009