Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Perl: Общие вопросы > AnyEvent::Handle, виснет push_read


Автор: shootnix 21.11.2011, 13:01
Привет всем. 
У меня возникла довольно глупая проблема, не могу понять, где порылась собака.  smile 
Задача простая: подключиться к серверу через AnyEvent::Socket, чтобы понадежнее держать соединения + всякие плюшки из коробки, выбрал AnyEvent::Handler. Такой код в итоге получается пока:

Код

sub socket_call {
    my ($msg, $host, $port) = @_;
    
    return unless $msg;
    
    $host ||= 'localhost';
    $port ||= 12345;
    
    my $cv = AnyEvent->condvar;
    say 'Try to connect';
    
    tcp_connect $host, $port, sub {
    my ($fh) = @_ or $cv->send;
    
    say 'Connecting...';
    
    my $handle;
    $handle = AnyEvent::Handle->new(
        fh => $fh,
        poll => 'rw',
        on_error => sub {
        say 'Error!';
        shift->destroy;
        $cv->send;
        },
        on_eof => sub {
        say 'EOF!';
        shift->destroy;
        $cv->send;
        },
    );
    
    say 'Try to write';
    $handle->push_write($msg);
    
    say 'Try to read';
    $handle->push_read( json => sub {
        my ($handle, $line) = @_;
        
        say 'line: ' . $line;
        $cv->send;
    });
    };
    
    $cv->recv;
    
    return $res;
}
 

На экране наблюдаю такое:

Код

Try to connect
Connecting...
Try to write
Try to read


скрипт висит и явно чего-то еще ждет, чего — не могу понять. То ли коннект теряется, то ли еще какая лажа, в общем, плиз, хелп!

UPD: Кое-что еще о поведении программы.
1. Сервер получает нужную строку, пытается ее отдать обратно.
2. Скрипт строку в ответ не получает, просто висит и чего-то ждет.
3. Если при зависшем скрипте остановить сервер, ничего не изменится, скрипт продолжит висеть. 

Я делаю вывод, что происходит какая-то блокировка, мб, сокета? Пробую push_write много раз подряд — все гуд, на чтении — такой глюк.

Автор: Pfailed 21.11.2011, 14:44
У AnyEvent::Handle есть опция poll?
Уверены что сервер отдает валидный json? Может начать с простого и попытаться прочитать просто строку?

Автор: shootnix 21.11.2011, 19:10
Валиный/невалидный — не имеет значения, не работает и все тут. 
Пробую:
Код

push_read(line => sub {});

Та же песня…

Автор: shootnix 21.11.2011, 20:00
Вообще, сдается мне, не срабатывает событие чтения. Как запустить?

Автор: Pfailed 21.11.2011, 20:05
Вы не сохраняете ссылку на $handle, объект попросту уничтожается.
Замените например shift->destroy на $handle->destroy

Автор: shootnix 21.11.2011, 20:22
Цитата(Pfailed @ 21.11.2011,  20:05)
Вы не сохраняете ссылку на $handle, объект попросту уничтожается.
Замените например shift->destroy на $handle->destroy

Спасибо, конечно, но в документации именно что написано:
Код

$_[0]->destroy;

Думаю, это не случайно. 

Автор: Pfailed 21.11.2011, 20:30
Проблема то решена или в верю-неверю поупражняемся? Могу лишь посоветовать перечитать документацию еще раз.

Автор: shootnix 21.11.2011, 20:35
Проблема не решена. На текущий момент код такой (не весь):
Код

tcp_connect $host, $port, sub {
    my ($fh) = @_ or $cv->send;
    
    say 'Connecting...';
    
    my $handle;
    $handle = AnyEvent::Handle->new(
        fh => $fh,
        on_error => sub {
        say 'Error!';
        shift->destroy;
        $cv->send;
        },
        on_eof => sub {
        say 'EOF!';
        shift->destroy;
        $cv->send;
        },
    );
    
    say 'Try to write';
    $handle->push_write($msg);
    
        say 'Try to read';
    $handle->push_read(
        line => sub {
        my ($h, $line) = @_;
        
        say 'Got line: ' . $line;
        $cv->send($line);
        }
    );
    };
    
    my ($result) = $cv->recv;

Вот эту строчку «say 'Got line: ' . $line;» я не вижу, замирает на «say 'Try to read';».
Обработчики on_error и on_eof отрабатывают корректно, дело явно не в шифтах.

Автор: Pfailed 21.11.2011, 21:09
server.pl
Код

use strict;
use IO::Socket;

my $serv = IO::Socket::INET->new(LocalPort => 1111, Listen => 1)
    or die $@;

while (1) {
    my $client = $serv->accept();
    unless ($client) {
        warn $!;
        next;
    }
    warn 'client accepted';
    
    eval {
        $client->sysread(my $buf, 1024) or die "read failed: $!";
        warn $buf, ' from the client';
        $client->syswrite(scalar(reverse $buf)."\n") or die "write failed: $!";
    };
    if ($@) {
        warn $@;
    }
    
    $client->close();
    warn 'client connection closed';
}


client.pl
Код

use strict;
use v5.10;
use AnyEvent;
use AnyEvent::Socket;
use AnyEvent::Handle;

socket_call('To be, or not to be', 'localhost', 1111);

sub socket_call {
    my ($msg, $host, $port) = @_;
    
    return unless $msg;
    
    $host ||= 'localhost';
    $port ||= 12345;
    
    my $cv = AnyEvent->condvar;
    say 'Try to connect';
    
    tcp_connect $host, $port, sub {
    my ($fh) = @_ or $cv->send;
    
    say 'Connecting...';
    
    my $handle;
    $handle = AnyEvent::Handle->new(
        fh => $fh,
        on_error => sub {
        say 'Error!';
        shift->destroy;
        $cv->send;
        },
        on_eof => sub {
        say 'EOF!';
        $handle->destroy;
        $cv->send;
        },
    );
    
    say 'Try to write';
    $handle->push_write($msg);
    
        say 'Try to read';
    $handle->push_read(
        line => sub {
        my ($h, $line) = @_;
        
        say 'Got line: ' . $line;
        $cv->send($line);
        }
    );
    };
    
    my ($result) = $cv->recv;
}


УМВР. ЧЯДНТ? Заметьте,  с shift не работает.

Автор: shootnix 21.11.2011, 21:31
Хм, кстати да, работает отменно, если сервер однопоточный. У меня — мультиплексный, вероятно, тут есть свои тонкости… Пошел искать, спасибо за помощь. 

Автор: shootnix 21.11.2011, 23:13
Почти разобрался =) 
Скорее всего, ответ от сервера шлется не туда, т.е., код навроде этого не работает (на стороне сервера):
Код

$handle->push_write("Hello from server!");

Кто знает, как писать ответы сервера в случае с AnyEvent? AnyEvent::Handle? Помогите, пожалуйста, у меня пока опыта маловато с этим… 

Автор: Pfailed 22.11.2011, 05:11
Код сервера покажите.

Автор: shootnix 22.11.2011, 09:19
У меня, на самом деле, несколько имплементаций, но цель моя — сделать на том и на другом конце AnyEvent-решения, используя AnyEvent::Handle — не достигнута. Пытаюсь сделать по образу и подобию того, что есть в интернетах:

Код

my @clients;

tcp_server undef, 12345, sub {
    my ($fh) = @_ or die $!;
    my $h = AnyEvent::Handle->new( fh => $fh );

    $h->on_read(sub {
        shift->push_read( line => sub {
            my ($h, $line) = @_;
        
        say 'line: ' . $line;

            my $client = $clients[ fileno($fh) ];

            $line .= '!';
            $client->{handle}->push_write($line);
        });
    });

    $h->on_error(sub {
        my ($h, $fatal, $msg) = @_;
        $h->destroy;
    });

    $clients[ fileno($fh) ] = {
        name   => undef,
        handle => $h,
    };
};

AE::cv->wait;


Подключаюсь телнетом, все гуд, скриптом — данные не ходят ни туда, ни обратно. К слову, предыдущий сервер на основе IO::Select работал норм, данные принимал, но клиент не мог их поймать. Вот примерный код его:

Код

my $port = 12345;
my $server = IO::Socket->new(
    Domain => PF_INET,
    Proto => 'tcp',
    LocalPort => $port,
    Listen => SOMAXCONN,
);
die 'Cannot bind: ' . $! unless $server;

my $selector = IO::Select->new($server);
$SIG{PIPE} = 'IGNORE';

say 'Multiplex server started on port ' . $port;
while ( my @clients = $selector->can_read() ) {
    for my $client (@clients) {
    if ( $client == $server ) {
        ### Новый коннект
        my $new_client = $server->accept;
        #syswrite $new_client, 'hello';
        
        my $port = $new_client->peerport;
        my $name = $new_client->peerhost;
        say "New client $port:$name";
        $selector->add($new_client);
    }
    else {
        ### Клиент уже подключился ранее
        my $port = $client->peerport;
        my $name = $client->peerhost;
        my $message;
        if ( sysread $client, $message, 1024 ) {
        say "Client $name:$port sent: $message";
        
        }
        else {
        $selector->remove($client);
        $client->shutdown(SHUT_RDWR);
        say "Client disconnected";
        }
    }
    }
}


Автор: DurRandir 22.11.2011, 15:50
Вы так и не привели в одном посте код клиента и сервера) Но проблема в том, что читаете вы через push_read в line-mode что клиентом, что сервером. А этот режим отбрасывает разделитель строк, и возвращает вам строчку без него. Потом вы её (не добавляя разделитель!) пишете обратно через push_write сервером ...и клиент её получает, но разделителя то нет - push_read для эхо-ответа вызван не будет. Вообще, через telnet для кода сервера из последнего поста это очень хорошо видно)

Автор: shootnix 22.11.2011, 20:13
Цитата(DurRandir @ 22.11.2011,  15:50)
Вы так и не привели в одном посте код клиента и сервера) Но проблема в том, что читаете вы через push_read в line-mode что клиентом, что сервером. А этот режим отбрасывает разделитель строк, и возвращает вам строчку без него. Потом вы её (не добавляя разделитель!) пишете обратно через push_write сервером ...и клиент её получает, но разделителя то нет - push_read для эхо-ответа вызван не будет. Вообще, через telnet для кода сервера из последнего поста это очень хорошо видно)

Огромное спасибо за совет, действительно, такой момент я упустил. Исправил, все работает, еще раз спасибо! 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)