Link to home
Start Free TrialLog in
Avatar of Chad K
Chad K

asked on

Need a perl wizard to help with POE::Component::Client::Ping Script

Hello-

I am an old school VBScript/Javascript guy, but need to write a specific script in perl to run on a linux server.  The purpose of the script is to grab a list of unique IDs and IP addresses from a server API, then to move through that list and asynchronously ping the addresses that meet a criteria (not equal to '0.0.0.0') and then post the results back to a second API.  So far I have the script pulling the data and splitting it into an array.  I have been studying the synopsis of this component here: https://metacpan.org/pod/POE::Component::Client::Ping, but just not understanding how to put it into action.  Would really appreciate some help.  If I could just return the original ID, $response_address, $roundtrip_time, $reply_time, and $reply_ttl back into another multidemensional array I can handle looping through it and writing the results back to my API.  here is my code so far:

use POE qw(Component::Client::Ping);
use REST::Client;

POE::Component::Client::Ping->spawn();

my @pingarray;

my $client = REST::Client->new();
$client->GET('http://www.mywebsite.com/api/heartbeat-get.asp?key=asdfsdc3bs&identifier=-1');
#print $client->responseContent();

my @response = split "\n", $client->responseContent();

foreach my $responseline (@response)
        {
        my ($id, $ip) = split /\|/, $responseline;
        #print "$id\n";
        #print "$ip\n";

        if ($ip ne '0.0.0.0'){
                        #add to array to ping (ID, ID as user_data)
                        #print "IP was $ip\n";

                        push @pingarray, [$id, $ip];
                }

        }
print "Array - @$_\n" for @pingarray;

sub some_event_handler {
  $kernel->post(
        "pinger",
        "ping",
        "pong",
        $address
  );
}

# This is the sub which is called when the session receives a "pong"
# event.  It handles responses from the Ping component.
sub got_pong {
  my ($request, $response) = @_[ARG0, ARG1];

  my ($req_address, $req_timeout, $req_time)      = @$request;
  my ($resp_address, $roundtrip_time, $resp_time, $resp_ttl) = @$response;


  # The response address is defined if this is a response.
  if (defined $resp_address) {
    printf(
      "ping to %-15.15s at %10d. pong from %-15.15s in %6.3f s\n",
      $req_address, $req_time,
      $resp_address, $roundtrip_time,
    );
    return;
  }

  # Otherwise the timeout period has ended.
  printf(
    "ping to %-15.15s is done.\n", $req_address,
  );

Open in new window


Everything after line 28 is sample code and doesn't work.  Line 28 just writes the array back to the page.  I have obscured my source API get call, but it just gets 1234 | 123.123.123.123 line by line a couple hundred items.
Avatar of David Favor
David Favor
Flag of United States of America image

Ah... POE...

Writing POE code is a great way to end up in a fetal position sobbing in a dark corner...

Try https://metacpan.org/pod/Net::Async::Ping instead + you'll have this working in a few minutes.

Certainly can be done in POE... will just take someone... willing to use POE to help you...

If someone has a POE script around which will do this, use POE...

If no POE script shows up, consider using Net::Async::Ping which is specifically designed for your use case.
Avatar of Chad K
Chad K

ASKER

POE was a requirement set by my company so I really need to stay in that realm.  I believe because of the return variables to keep ttl and roundtrip, etc.  Thanks for the input, though, I really appreciate it.
Avatar of Chad K

ASKER

Consequently if I can get them to change path and allow me to use this component, could you show me a quick example of how I could load my array into it and where in the loop I would do my postback to API?  Getting it to allow me to hit several hundred IPs at the same time and writeback to API once it has a response is really the goal, so if this will do it perhaps I can get them to change the requirement on POE.  Thanks!
ASKER CERTIFIED SOLUTION
Avatar of David Favor
David Favor
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Chad K

ASKER

David- Thanks for the link.  It is very helpful.  I am making great progress but think I have some sort of format issue when I pass IPs from my own array that is build from my API.  If I run the POE against the array @pingarray, it works as it should.  If I run the POE loop against the array @addresses, I get a weird output such as:

Starting to ping hosts.
 at Fri Jun 26 10:10:49 2020
 at Fri Jun 26 10:10:49 2020
 at Fri Jun 26 10:10:49 2020
 at Fri Jun 26 10:10:49 2020
 at Fri Jun 26 10:10:49 2020
 at Fri Jun 26 10:10:49 2020
 at Fri Jun 26 10:10:49 2020
.ime's up for responses from 10.16.17.66
.ime's up for responses from 10.12.12.27
.ime's up for responses from 10.12.12.26
.ime's up for responses from 10.12.12.75
.ime's up for responses from 10.12.12.74
.ime's up for responses from 10.15.15.3
.ime's up for responses from 10.15.15.186

If I run the code against @pingarray, I get the following output:

Starting to ping hosts.
Pinging 63.134.128.164 at Fri Jun 26 10:17:25 2020
Pinging 63.134.128.165 at Fri Jun 26 10:17:25 2020
Pinging 4.2.2.2 at Fri Jun 26 10:17:25 2020
Pinging 4.2.2.1 at Fri Jun 26 10:17:25 2020
Pinged 63.134.128.164  - Response from 63.134.128.164  in  0.000s
Pinged 63.134.128.165  - Response from 63.134.128.165  in  0.001s
Pinged 4.2.2.2         - Response from 4.2.2.2         in  0.012s
Pinged 4.2.2.1         - Response from 4.2.2.1         in  0.012s
Time's up for responses from 63.134.128.164.
Time's up for responses from 63.134.128.165.
Time's up for responses from 4.2.2.2.
Time's up for responses from 4.2.2.1.

As you can see in the first example it has IP addresses which seem to be formatted correctly as they are reported in the ".ime's up" lines, but something is off since the "T" is missing from the print command.  What am I missing.  My code looks like the following:

use warnings;    # or -w above

BEGIN {
  die "POE::Component::Client::Ping requires root privilege\n"
    if $> and ($^O ne 'VMS');
}
use POE;
use POE::Component::Client::Ping;
use REST::Client;

# How many seconds to wait for ping responses.
sub PING_TIMEOUT () { 5 }

my $client = REST::Client->new();
$client->GET('http://mywebsite.com/api/heartbeat-get.asp?key=asdf2dx4&identifier=-1');
#print $client->responseContent();

my @response = split "\n", $client->responseContent();

foreach my $responseline (@response)
        {
        my ($id, $ip) = split /\|/, $responseline;
        #print "$id\n";
        #print "$ip\n";
        if ($ip ne '0.0.0.0'){
                        #add to array to ping (ID, ID as user_data)
                        #print "IP was $ip\n";

                        #push @pingarray, [$id, $ip];
                        push @addresses, $ip;
                }

        }

#foreach (@addresses) {
#  print "$_\n";
#}

my @pingarray = qw( 63.134.128.164 63.134.128.165 4.2.2.2 4.2.2.1);

#------------------------------------------------------------------------------
# The main loop.
# Create a pinger component.  This will do the work of multiple
# concurrent pings.  It requires another session to interact with it.
POE::Component::Client::Ping->spawn(
  Alias   => 'pinger',        # The component's name will be "pinger".
  Timeout => PING_TIMEOUT,    # The default ping timeout.
);

# Create a session that will use the pinger.  Its parameters match
# event names with the functions that will handle them.
POE::Session->create(
  inline_states => {
    _start => \&client_start,       # Call client_start() to handle "_start".
    pong   => \&client_got_pong,    # Call client_got_pong() to handle "pong".
  }
);

# Start POE's main loop.  It will only return when everything is done.
$poe_kernel->run();
exit;

#------------------------------------------------------------------------------
# Event handlers.
# Handle _start (given by POE itself to start your session) by sending
# several "ping" commands to the component at once.  The component
# will reply over the course of PING_TIMEOUT seconds.
sub client_start {
  my ($kernel, $session) = @_[KERNEL, SESSION];
  print "Starting to ping hosts.\n";
  foreach my $address (@pingarray) {
    print "Pinging $address at ", scalar(localtime), "\n";

    # "Pinger, do a ping and return the results as a pong event.  The
    # ip to ping is $ping."
    $kernel->post(pinger => ping => pong => $address);
  }
}

# Handle a "pong" event (returned by the Ping component because we
# asked it to).  Just display some information about the ping.
sub client_got_pong {
  my ($kernel, $session) = @_[KERNEL, SESSION];

  # The original request is returned as the first parameter.  It
  # contains the address we wanted to ping, the total time to wait for
  # a response, and the time the request was made.
  my $request_packet = $_[ARG0];
  my ($request_address, $request_timeout, $request_time) = @{$request_packet};

  # The response information is returned as the second parameter.  It
  # contains the response address (which may be different from the
  # request address), the ping's round-trip time, and the time the
  # reply was received.
  my $response_packet = $_[ARG1];
  my ($response_address, $roundtrip_time, $reply_time) = @{$response_packet};

  # It is impossible to know ahead of time how many ICMP ping
  # responses will arrive for a particular address, so the component
  # always waits PING_TIMEOUT seconds.  An undefined response address
  # signals that this waiting period has ended.
  if (defined $response_address) {
    printf("Pinged %-15.15s - Response from %-15.15s in %6.3fs\n",
      $request_address, $response_address, $roundtrip_time);
  }
  else {
    print "Time's up for responses from $request_address.\n";
  }
}

Open in new window


Avatar of Chad K

ASKER

Update....i apparently had some characters before/after on my IP addresses coming from my API.  I am not sure how.  I added this function to the top of the script:

sub  trim { my $s = shift; $s =~ s/^\s+|\s+$//g; return $s };

Then when setting my array up I just changed my line to "push @addresses, trim($ip);" which seems to have fixed the issue I was having.  Thanks for the help David!
You're welcome!

Glad you got this working!