How can I share Perl data structures through a socket? - perl

In sockets I have written the client server program. First I tried to send the normal string among them it sends fine. After that I tried to send the hash and array values from client to server and server to client. When I print the values using Dumper, it gives me only the reference value. What should I do to get the actual values in client server?
Server Program:
use IO::Socket;
use strict;
use warnings;
my %hash = ( "name" => "pavunkumar " , "age" => 20 ) ;
my $new = \%hash ;
#Turn on System variable for Buffering output
$| = 1;
# Creating a a new socket
my $socket=
IO::Socket::INET->new(LocalPort=>5000,Proto=>'tcp',Localhost =>
'localhost','Listen' => 5 , 'Reuse' => 1 );
die "could not create $! \n" unless ( $socket );
print "\nUDPServer Waiting port 5000\n";
my $new_sock = $socket->accept();
my $host = $new_sock->peerhost();
while(<$new_sock>)
{
#my $line = <$new_sock>;
print Dumper "$host $_";
print $new_sock $new . "\n";
}
print "$host is closed \n" ;
Client Program
use IO::Socket;
use Data::Dumper ;
use warnings ;
use strict ;
my %hash = ( "file" =>"log.txt" , size => "1000kb") ;
my $ref = \%hash ;
# This client for connecting the specified below address and port
# INET function will create the socket file and establish the connection with
# server
my $port = shift || 5000 ;
my $host = shift || 'localhost';
my $recv_data ;
my $send_data;
my $socket = new IO::Socket::INET (
PeerAddr => $host ,
PeerPort => $port ,
Proto => 'tcp', )
or die "Couldn't connect to Server\n";
while (1)
{
my $line = <stdin> ;
print $socket $ref."\n";
if ( $line = <$socket> )
{
print Dumper $line ;
}
else
{
print "Server is closed \n";
last ;
}
}
I have given my sample program about what I am doing. Can any one tell me what I am doing
wrong in this code? And what I need to do for accessing the hash values?

When you say
print $ref;
, you're in part instructing Perl to turn $ref into a string (since only strings can be printed). It turns out that references don't turn into very useful strings by default.
You need to turn $ref into a string that you can send across the wire and then decode on the other side to get the data back out. This process is referred to as "serialization". Data::Dumper's output is actually a valid serialization of its arguments, but the basic serialization module in Perl is Storable.
Procedurally, you can say[1]
use Storable qw(nfreeze); # nfreeze rather than freeze because
# we want a network-portable string
...
print nfreeze($ref);
on one side and
use Storable qw(thaw);
...
my $ref = thaw($line);
on the other.
There's also an OO interface; read the Storable documentation for more information.
[1]: Notice the yaddayaddas. This is incomplete code that merely illustrates the key differences from your code.

Problem is you are sending references to the data, not the data itself. You need to serialize the data somehow. JSON is a very easy way to do that. There is also YAML.

Related

How to setup a Message Head on a network level in Perl Socket programming

I have a requirement about sending socket data over the TCP/IP Protocol to a different party/server, and one of the core requirements is setting up the message length in the message head. Can we set such an operation in Perl Socket Programming where both the header and body can be set?
I have seen a reference in the PHP language where the message length can be sent as described in the examples - https://www.php.net/manual/en/sockets.examples.php, and it would be helpful if we have something in that front in Perl.
I verified with Socket, IO::Socket::INET modules but hardly could find anything related to this point
Understanding your requirement
With a streaming protocol like TCP, information needs to be inserted into the stream which allows the reader to split the stream into individual messages.
It could be something as simple as inserting line feeds between messages. That is the approach used by the examples to which you link. Contrary to your claim, the sender does not provide the length of the messages in those examples.[1]
The problem with that approach is that line feeds can't appear as part of the message. One could introduce an escaping mechanism, but this slows down reading.
Another very common approach is to send a length prefix. This refers to sending the length of the message in some kind of header before the message. Reading a message from the stream involves reading the header first, determining the size of the message, then reading the rest of the message.
This is the approach you have chosen to take.
Sending and receiving a length-prefixed message
The following senders use this technique:
print $fh pack( "N", length( $msg ) ) . $msg;
print $fh pack( "N/a", $msg );
These identical snippets precede the message with its length (as a 32-bit BE integer). The reader would start by reading the length. Armed with that information, it knows how much it needs to read to obtain the full message. The corresponding reader might therefore look like the following:[2]
my $buf = '';
while ( 1 ) {
my $n = read_uint32be( $sock, $buf );
die $! if !defined( $n );
my $msg = read_exactly( $sock, $buf, $n );
die $! if !defined( $msg );
...
}
And the readers are buggy, relying on the completely bogus assumption that socket_read will return exactly one line.
With the help of this:
use Errno qw( ENODATA );
use constant CHUNCK_SIZE => 64 * 1024;
# Returns `undef` and sets `$!` to `ENODATA` on eof.
# Returns `undef` and sets `$!` on error.
sub read_exactly {
my $fh = shift;
my $buf_ref = \shift;
my $n = shift;
$$buf_ref //= '';
my $to_read = $n - length( $$buf_ref );
while ( $to_read > 0 ) {
my $rv = sysread( $fh, $$buf_ref, CHUNK_SIZE, length( $$buf_ref ) );
if ( !$rv ) {
$! = ENODATA if defined( $rv ); # EOF
return undef;
}
$to_read -= $rv;
}
return substr( $$buf_red, 0, $n );
}
# Returns `undef` and sets `$!` to `ENODATA` on eof.
# Returns `undef` and sets `$!` on error.
sub read_uint32be {
my $packed = read_exactly( $_[0], $_[1], 4 );
return undef if !defined( $packed );
return unpack( "N", $packed );
}

perl storing hash in %ENV

I want to (ab-)use the global %ENV to store a hash. This seems to work differently for %ENV than for ordinary hashes. in the program below, the $ENV{myhash} still contains 'myhash' => 'HASH(0x7ffad8807380)' and the %ahash is still around. is it possible to convert the hex address back to point at its location, instead of just containing the string? I guess I could serialize and unserialize the hash instead. what is the right way to do this?
#!/usr/bin/perl -w
use common::sense;
use Data::Dumper;
my %QENV = ( nohash => 'noh' );
my %ahash= ( hv1 => 'htext1', hv2 => 'htext2' );
$QENV{'myhash'} = \%ahash;
print "works: ". Dumper(\%QENV)."\n\n\n";
$ENV{'myhash'} = \%ahash;
print "fails: ". Dumper(\%ENV)."\n";
%ENV is a magical hash. It reflects the process's environment. Reading from it reads from the environment, and writing to it changes the environment.
If you can guarantee the referenced variable is still alive (by it still being in scope or by it having its REFCNT increased), you can indeed create a reference to it from the address.
use strict;
use warnings;
use Data::Dumper;
use Inline C => <<'__EOS__';
SV* _newRV(IV iv) {
return newRV((SV*)iv);
}
__EOS__
my %hash = ( a => 1, b => 2 );
my $ref = \%hash;
my $ref_string = "$ref";
my $addr = hex( ( $ref_string =~ /\((.*)\)/ )[0] );
my $ref2 = _newRV($addr);
print(Dumper($ref2));
I have no idea why you'd want to do this. It would not permit another process to access the data since one process can't access the memory of another.
You seem to want to share data. Here's an example I put out there often that shows how to store data in a JSON file, then retrieve it. JSON is cross-language, so the data can be used by many programming languages, not just Perl. Although this example is within a single script, imagine it being two different Perl applications:
use warnings;
use strict;
use JSON;
my $file = 'data.json';
my %h = (
foo => 'bar',
raz => {
ABC => 123,
XYZ => [4, 5, 6],
}
);
my $json = encode_json \%h;
# write the JSON to a file, and close it
open my $fh, '>', $file or die $!;
print $fh $json;
close $fh or die $!;
# open the JSON file, and read it
open my $json_file, '<', $file or die $!;
my $read_json;
{
local $/;
$read_json = <$json_file>;
}
my $perl_hash = decode_json $read_json;

How to flush pipe handle obtained from open?

I am trying to flush a pipe handle obtained from open using either autoflush() and flush() methods from the IO::Handle module, but I think it is not working. Here is an example:
host.pl:
use feature qw(say);
use strict;
use warnings;
my $client_pid = open ( my $fh, '|-', 'client.pl' )
or die "Could not open client: $!";
#$fh->autoflush(1); # adding this line does not help
sleep 2;
say "Host: sent message";
print $fh "Hello";
#print $fh "Hello\n"; # adding a newline works fine
$fh->flush() or warn "$!"; # this does not work
sleep 2;
say "Host exits.";
close $fh;
client.pl:
use feature qw(say);
use strict;
use warnings;
say "Client running..";
chomp (my $line = <STDIN>);
say "Client got line: '$line'";
sleep 1;
say "Client exits..";
The output of running host.pl is:
Client running..
Host: sent message
Host exits.
Client got line: 'Hello'
Client exits..
Expected output would be:
Client running..
Host: sent message
Client got line: 'Hello'
Client exits..
Host exits.
I know I can fix this by adding a newline at the end of string to be printed:
print $fh "Hello\n";
but I am curious why $fh->flush() is not working here?
The data is being sent to the client immediately, but the client waits for a newline to arrive.
readline (for which <> is a shortcut in your program) reads until a newline is encountered before returning (although changing $/ can change that behaviour. If you want a call that returns as soon as data is available, use sysread.
use BLOCK_SIZE => 64*1024;
say "Client running..";
while (1) {
my $rv = sysread(\*STDIN, my $buf, BLOCK_SIZE);
die($!) if !defined($rv);
last if !$rv;
say "Got: $buf";
}
Note a single print can result in data being received in multiple chunks. In practice, especially with a socket instead of a pipe, you'd need some way of framing your messages in order to reliably identify them. For example, the following client expects sentinel-terminated messages (the sentinel being a newline):
use BLOCK_SIZE => 64*1024;
say "Client running..";
my $buf = '';
while (1) {
my $rv = sysread(\*STDIN, $buf, BLOCK_SIZE, length($buf));
die($!) if !defined($rv);
last if !$rv;
while ($buf =~ s/^([^\n]*)\n//) {
my $msg = $1;
say "Got: $msg";
}
say "Got a partial message" if length($buf);
}
die("Premature EOF\n") if length($buf);
Try sending:
$fh->autoflush();
print($fh "abc");
sleep(1);
print($fh "def\n");
sleep(1);
print($fh "ghi\njkl\nmno");
sleep(1);
print($fh "pqr\n");
This can be adapted to handle length-prefixed messages or any other message format.

Perl script to read 500 entries from a file as input

I'm trying to run a perl script which reads a text file that contains say 500 entries, read one entry at a time and send a command.
The command is server hostname where the value of hostname is the list of hostnames in the text file.
I'm new to programming,As per my understanding we need to open the file that contains the host name and read it open (ENABLE, "<hostanmes.txt") || die "could not open output file";
use a for loop to read the 512 host names in it for($i=1; $i<=512; $i++)
But I'm not sure how to connect this file to the command server hostname
The program is incomplete.I'm struck and not really sure.Can somebody please help me with this ?
#!/usr/bin/perl
## Library import
use Net::SSH::Expect;
use strict;
use warnings;
use autodie;
print "\n [INFO] script Execution Started \n";
my $ssh = Net::SSH::Expect->new (host => "ip addr",
password=> 'pwd',
user => 'username',
raw_pty => 1);
my $login_output = $ssh->login();
print "\n [INFO] add host rules \n";
open (ENABLE, "<hostanmes.txt") || die "could not open output file";
for($i=1; $i<=512; $i++)
{
my $cfg = $ssh->exec("config");
my $cmd = $ssh->exec("server www.google.com");
my $cmd = $ssh->exec("exit");
}
close(ENABLE);
The essence of the answer is that you can interpolate the value of scalar or array variables into a double-quoted string by just naming them inside the string. For instance
my $x = 42;
print "x = $x\n";
will print
x = 42
Here are some other points about your program
The use for any modules should come after use strict and use warnings, which should ordinarily be the very first lines of a program
It is best practice to use lexical file handles with the three-parameter form of open, and if you have use autodie in place then it is pointless to check the success of the open as it has already been done for you. So
open (ENABLE, "<hostanmes.txt") || die "could not open output file";
should be
open my $enable, '<', 'hostnames.txt';
Unless you need the array indices for another reason, it is best in Perl to iterate over just the array values.
Here is a rewrite of your code that takes into account these points. It looks like it will do what you need
use strict;
use warnings;
use autodie;
use Net::SSH::Expect;
print "\n[INFO] script Execution Started\n";
my $ssh = Net::SSH::Expect->new(
host => "ip addr",
password => 'pwd',
user => 'username',
raw_pty => 1,
);
my $login_output = $ssh->login;
print "\n[INFO] add host rules\n";
open my $enable, '<', 'hostnames.txt';
while (my $server = <$enable>) {
chomp $server;
$ssh->exec('config');
$ssh->exec("server $server");
$ssh->exec('exit');
}
To iterate through ENABLE once you have it you should use a simple while loop:
while(<ENABLE>){
chomp;
//each line read from ENABLE will be stored in $_ per loop
}
This way you do not need a for loop to iterate. So in essence you would run the "server hostname" command in this while loop:
...
while(<ENABLE>) {
chomp;
$ssh->exec("server $_");
}
...
Check here for details.

Perl Socket File Transfer Problems

I have my connection working and data transfer working.... to an extent. I am designing a small program that sets up a client and server socket. Once connected, the client can send files to the server.
My problem is, when I begin to send my "test" file, the server never ends it's while loop. It keeps concatenating data into the output file. Even stranger, the data in the output file is correct, except there is extra white space between the lines.
I know this is because I am not chomping the \n character, and I add another \n character on the server. But if I chomp on the client, then everything is on one line. Therefore the server (regardless of it adding a newline character) outputs it all on one line, because it only received one line. If I chomp on the server side, I get an empty text file... which confuses me.
Furthermore, the server never stops concatenating... it makes an infinite loop even after the client disconnected. The terminal outputs this indefinitely:
Use of uninitialized value $data in concatenation (.) or string at ./tcp_server.pl line 51, <GEN2> line 14.
Here is my code for the server:
#!/usr/bin/perl
# Flushing to STDOUT after each write
$| = 1;
use warnings;
use strict;
use IO::Socket::INET;
use v5.10;
# Server side information
my $listen_port = '7070';
my $protocal = 'tcp';
# Finds IP address of host machine
# Connects to example site on HTTP
my $ip_finder = IO::Socket::INET->new(
PeerAddr=> "www.google.com",
PeerPort=> 80,
Proto => "tcp"
) or die "The IP can not be resolved: $!\n";
# The found IP address of Host
my $ip_address = $ip_finder->sockhost;
# Creating socket for server
my $server = IO::Socket::INET->new (
LocalPort => $listen_port,
Proto => $protocal,
Listen => 5,
Reuse => 1,
) or die "Socket could not be created, failed with error: $!\n"; # Prints error code
print "Socket created using IP: $ip_address\n";
print "Waiting for client connection on port $listen_port\n";
# Accept connection
my $client_socket = $server->accept();
open(my $fh, ">out")
or die "File can not be opened: $!";
while($client_socket) {
# Retrieve client information
my $client_address = $client_socket->peerhost();
my $client_port = $client_socket->peerport();
print "Client accepted: $client_address, $client_port\n";
my $data = <$client_socket>;
print $fh "$data\n";
}
close $fh;
$server->close();
and the client:
#!/usr/bin/perl
# Flushing to STDOUT after each write
$| = 1;
use warnings;
use strict;
use IO::Socket::INET;
use v5.10;
# Client side information
# Works by setting $dest to server address, needs to be on same domain
# my $dest = '<IP goes here>';
# my $dest = '<IP goes here>';
my $dest = '<host>.cselabs.umn.edu';
my $port = '7070';
my $protocal = 'tcp';
my $client = IO::Socket::INET->new (
PeerHost => $dest,
PeerPort => $port,
Proto => $protocol,
) or die "Socket could not be created, failed with error: $!\n"; # Prints error code
print "TCP connection established!\n";
open(my $fh, "<test")
or die "Can't open: $!";
while(my $line = <$fh>) {
print $client $line;
}
close $fh;
# sleep(10);
$client->close();
You don't add newline at the client. The client just reads a line from the file (including newline) and prints it to the socket, but does not add another newline. But at the server you read the line from the socket (including the newline) and add another newline. Also, the loop condition in the server is not correct. You should not check if the socket exists because it will exists even after the connection got closed. Instead you should check for error while reading (that's where you undefined warnings come from). And, it may be better not to read line by line but instead chunks independent from the lines, e.g.
while (read($client_socket, my $data, 8192)) {
print $fh $data;
}