Perl 使用AnyEvent::Handle和tcp\u connect重新连接

Perl 使用AnyEvent::Handle和tcp\u connect重新连接,perl,anyevent,Perl,Anyevent,我使用TCP\u-connect和TCP\u-server编写了一个简单的TCP服务器和客户端。客户端连接到服务器并每5秒发送字符串测试消息 如果服务器是可访问的,则此操作不会出现问题。但是,如果服务器在客户端启动时不可用,或者变得不可用,则客户端脚本从不尝试重新连接 如果连接句柄不可用(已破坏?),我希望它尝试重新连接。如果不可用,请执行操作(可能打印状态消息),但每5秒钟尝试重新连接一次将是理想的结果 我不知道该怎么做。我将客户端和服务器代码缩减为以下内容 客户 服务器 您可以使用以下选项:

我使用
TCP\u-connect
TCP\u-server
编写了一个简单的TCP服务器和客户端。客户端连接到服务器并每5秒发送字符串
测试消息

如果服务器是可访问的,则此操作不会出现问题。但是,如果服务器在客户端启动时不可用,或者变得不可用,则客户端脚本从不尝试重新连接

如果连接句柄不可用(已破坏?),我希望它尝试重新连接。如果不可用,请执行操作(可能打印状态消息),但每5秒钟尝试重新连接一次将是理想的结果

我不知道该怎么做。我将客户端和服务器代码缩减为以下内容

客户 服务器
您可以使用以下选项:

package MyConnector;

use strict;
use warnings;

use AE               qw( );
use AnyEvent::Handle qw( );
use Scalar::Util     qw( );

sub new {
   my $class = shift;
   my %opts = @_;

   my $self = bless({}, $class);

   {
      Scalar::Util::weaken(my $self = $self);

      my $on_connect       = delete($opts{on_connect});
      my $on_connect_error = delete($opts{on_connect_error});

      my $tries    = delete($opts{tries})    ||  5;
      my $cooldown = delete($opts{cooldown}) || 15;

      $self->{_connect} = sub {
         $self->{_timer} = undef;

         $self->{_handle} = AnyEvent::Handle->new(
            %opts,

            on_connect => sub {
               my ($handle, $host, $port, $retry) = @_;

               $self->{handle} = $handle;
               delete @{$self}{qw( _connect _handle _timer )};

               $on_connect->($handle, $host, $port, $retry)
                  if $on_connect;
            },

            on_connect_error => sub {
               my ($handle, $message) = @_;

               if (!$tries--) {
                  $on_connect_error->($handle, $message)
                     if $on_connect_error;

                  delete @{$self}{qw( _connect _handle _timer )};

                  return;
               }

               # This will happen when this callback returns,
               # but that might not be for a while, so let's
               # do it now in case it saves resources.
               $handle->destroy();

               $self->{_timer} = AE::timer($cooldown, 0, $self->{_connect});
            },
         );
      };

      $self->{_connect}->();
   }

   return $self;
}

sub handle {
   my ($self) = @_;
   return $self->{handle};
}

1;
我很确定它没有内存泄漏(不像你的代码)。您可以按如下方式使用它:

use strict;
use warnings;

use AE          qw( );
use MyConnector qw( );

my $host = $ARGV[0] || 'www.stackoverflow.com';
my $port = $ARGV[1] || 80;

my $conn_cv = AE::cv();

my $connector = MyConnector->new(
   connect   => [ $host, $port ],
   keepalive => 1,

   on_connect => sub {
       print("Connected successfully\n");
       $conn_cv->send();
   },

   on_connect_error => sub {
       warn("Could not connect: $_[1]\n");
       $conn_cv->send();
   },

   # ...
);

$conn_cv->recv();

多亏了ikegami,我想我可能希望跟踪连接状态,并将其与另一个AnyEvent计时器监视程序结合使用,以便在连接未建立时重新连接。如果连接状态($isConnected)为零,则每秒都会尝试连接。同时,当重新建立连接时,事件将排队等待

如果有一个更简单的方法来实现这一点,我洗耳恭听,但现在我认为这将解决这个问题

my @bulk;
my $host = '127.0.0.1';
my $port = 9999;
my $isConnected = 0;

my $conn_cv = AnyEvent->condvar;
my $conn_hdl;

# Flush Timer
my $timer = AnyEvent->timer(
    after => 5,
    interval => 5,
    cb => sub {
        push(@bulk,"Test message");
        if ($isConnected == 1) {
            flush(\@bulk);
            undef @bulk;
        }   
    }
);

# Reconnect Timer
my $reconn = AnyEvent->timer(
    after => 1,
    interval => 1,
    cb => sub {

        if ($isConnected == 0) {

            $conn_hdl = AnyEvent::Handle->new(
                connect => [$host, $port],
                keepalive => 1,
                on_connect => sub {
                    $isConnected = 1;
                },
                on_connect_error => sub {
                    warn "Could not connect: $_[1]\n";
                    $conn_hdl->destroy;
                    $isConnected = 0;       
                },
                on_error => sub {
                    my ($out_hdl, $fatal, $msg) = @_;
                    AE::log error => $msg;
                    $conn_hdl->destroy;
                    $isConnected = 0;
                },
                on_eof => sub {
                    warn "EOF\n";
                    $conn_hdl->destroy;
                    $isConnected = 0;
                },
                on_read => sub {
                    my ($self) = @_;
                        $self->unshift_read(line => sub {
                            my ($hdl,$data) = @_;
                            print $data."\n";
                    });
                }
            );  
        }
    }
);

$conn_cv->recv;

谢谢你的帮助。这在技术上是可行的,但是on_connect_错误将导致大量连接尝试,直到成功。此选项对网络不太友好,在重新建立连接之前,会将cpu固定在while循环中。在这里添加休眠或任意计数器似乎违背了事件处理框架(如AnyEvent)的本质。具体来说,我需要它能够做其他事情,即使在网络关闭时,但在某些时间间隔尝试重新连接。如果连接成功,继续正常。所以只需使用计时器!当然,对AnyEvent::Handle的调用应该移到回调中,这会使事情变得复杂。请参阅更新。虽然我使用计时器使代码正常工作,但您的选项肯定更加健壮,并且易于重用。我不能说我完全理解(由于我的经验水平),但它确实说明了我需要研究的一些主题。你提到内存泄漏,我很好奇你的意思。如果是@bulk失控,我计划处理它。这就是你说的还是我遗漏了什么?再次感谢!存储在
$conn_hdl
捕获
$conn_hdl
中的对象回调,形成一个引用循环。我通过使用参考周和使用
$\u0]
而不是
$conn\uhdl
避免了参考周期。
use strict;
use warnings;

use AE          qw( );
use MyConnector qw( );

my $host = $ARGV[0] || 'www.stackoverflow.com';
my $port = $ARGV[1] || 80;

my $conn_cv = AE::cv();

my $connector = MyConnector->new(
   connect   => [ $host, $port ],
   keepalive => 1,

   on_connect => sub {
       print("Connected successfully\n");
       $conn_cv->send();
   },

   on_connect_error => sub {
       warn("Could not connect: $_[1]\n");
       $conn_cv->send();
   },

   # ...
);

$conn_cv->recv();
my @bulk;
my $host = '127.0.0.1';
my $port = 9999;
my $isConnected = 0;

my $conn_cv = AnyEvent->condvar;
my $conn_hdl;

# Flush Timer
my $timer = AnyEvent->timer(
    after => 5,
    interval => 5,
    cb => sub {
        push(@bulk,"Test message");
        if ($isConnected == 1) {
            flush(\@bulk);
            undef @bulk;
        }   
    }
);

# Reconnect Timer
my $reconn = AnyEvent->timer(
    after => 1,
    interval => 1,
    cb => sub {

        if ($isConnected == 0) {

            $conn_hdl = AnyEvent::Handle->new(
                connect => [$host, $port],
                keepalive => 1,
                on_connect => sub {
                    $isConnected = 1;
                },
                on_connect_error => sub {
                    warn "Could not connect: $_[1]\n";
                    $conn_hdl->destroy;
                    $isConnected = 0;       
                },
                on_error => sub {
                    my ($out_hdl, $fatal, $msg) = @_;
                    AE::log error => $msg;
                    $conn_hdl->destroy;
                    $isConnected = 0;
                },
                on_eof => sub {
                    warn "EOF\n";
                    $conn_hdl->destroy;
                    $isConnected = 0;
                },
                on_read => sub {
                    my ($self) = @_;
                        $self->unshift_read(line => sub {
                            my ($hdl,$data) = @_;
                            print $data."\n";
                    });
                }
            );  
        }
    }
);

$conn_cv->recv;