Perl read from socket missing first character - perl

I am trying to read from an instrument connected over network using TCP protocol from Perl.
The code I have used is below:
$socket = new IO::Socket::INET (
PeerHost => '210.232.14.204',
PeerPort => '23',
Proto => 'tcp',
) or die "ERROR in Socket Creation";
while(!($data=~m/"ABC"/))
{
$temp = <$socket>;
$data = $data + $temp;
print $temp;
}
What happens is the first character of every line that is read over the TCP is not printed. Instead it is replace with a space. Why does this happen?
Example:
Expected output
Copyright (c) ACME Corporation
2009 - 2010
Actual Output
opyright (c) ACME Corporation
009 - 2010
Thanks...

The telnet protocol has a little bit of negotiation at its startup, something I sometimes jokingly refer to as a “secret handshake”. You should use a more straight-through service/port to get up to speed with sockets.
Also, you really need two different threads of control for this sort of thing; otherwise it’s too hard. Here’s a simple telnetish program from 1998:
use strict;
use IO::Socket;
my ($host, $port, $kidpid, $handle, $line);
unless (#ARGV == 2) { die "usage: $0 host port" }
($host, $port) = #ARGV;
# create a tcp connection to the specified host and port
$handle = IO::Socket::INET->new(Proto => "tcp",
PeerAddr => $host,
PeerPort => $port)
or die "can't connect to port $port on $host: $!";
$handle->autoflush(1); # so output gets there right away
print STDERR "[Connected to $host:$port]\n";
# split the program into two processes, identical twins
die "can't fork: $!" unless defined($kidpid = fork());
if ($kidpid) {
# parent copies the socket to standard output
while (defined ($line = <$handle>)) {
print STDOUT $line;
}
kill("TERM" => $kidpid); # send SIGTERM to child
}
else {
# child copies standard input to the socket
while (defined ($line = <STDIN>)) {
print $handle $line;
}
}
exit;
And here’s a more complete implementation, a program that sits on your firewall and waits for internal connections to some outside port:
#!/usr/bin/perl -w
# fwdport -- act as proxy forwarder for dedicated services
use strict; # require declarations
use Getopt::Long; # for option processing
use Net::hostent; # by-name interface for host info
use IO::Socket; # for creating server and client sockets
use POSIX ":sys_wait_h"; # for reaping our dead children
my (
%Children, # hash of outstanding child processes
$REMOTE, # whom we connect to on the outside
$LOCAL, # where we listen to on the inside
$SERVICE, # our service name or port number
$proxy_server, # the socket we accept() from
$ME, # basename of this program
);
($ME = $0) =~ s,.*/,,; # retain just basename of script name
check_args(); # processing switches
start_proxy(); # launch our own server
service_clients(); # wait for incoming
die "NOT REACHED"; # you can't get here from there
# process command line switches using the extended
# version of the getopts library.
sub check_args {
GetOptions(
"remote=s" => \$REMOTE,
"local=s" => \$LOCAL,
"service=s" => \$SERVICE,
) or die <<EOUSAGE;
usage: $0 [ --remote host ] [ --local interface ] [ --service service ]
EOUSAGE
die "Need remote" unless $REMOTE;
die "Need local or service" unless $LOCAL || $SERVICE;
}
# begin our server
sub start_proxy {
my #proxy_server_config = (
Proto => 'tcp',
Reuse => 1,
Listen => SOMAXCONN,
);
push #proxy_server_config, LocalPort => $SERVICE if $SERVICE;
push #proxy_server_config, LocalAddr => $LOCAL if $LOCAL;
$proxy_server = IO::Socket::INET->new(#proxy_server_config)
or die "can't create proxy server: $#";
print "[Proxy server on ", ($LOCAL || $SERVICE), " initialized.]\n";
}
sub service_clients {
my (
$local_client, # someone internal wanting out
$lc_info, # local client's name/port information
$remote_server, # the socket for escaping out
#rs_config, # temp array for remote socket options
$rs_info, # remote server's name/port information
$kidpid, # spawned child for each connection
);
$SIG{CHLD} = \&REAPER; # harvest the moribund
accepting();
# an accepted connection here means someone inside wants out
while ($local_client = $proxy_server->accept()) {
$lc_info = peerinfo($local_client);
set_state("servicing local $lc_info");
printf "[Connect from $lc_info]\n";
#rs_config = (
Proto => 'tcp',
PeerAddr => $REMOTE,
);
push(#rs_config, PeerPort => $SERVICE) if $SERVICE;
print "[Connecting to $REMOTE...";
set_state("connecting to $REMOTE"); # see below
$remote_server = IO::Socket::INET->new(#rs_config)
or die "remote server: $#";
print "done]\n";
$rs_info = peerinfo($remote_server);
set_state("connected to $rs_info");
$kidpid = fork();
die "Cannot fork" unless defined $kidpid;
if ($kidpid) {
$Children{$kidpid} = time(); # remember his start time
close $remote_server; # no use to master
close $local_client; # likewise
next; # go get another client
}
# at this point, we are the forked child process dedicated
# to the incoming client. but we want a twin to make i/o
# easier.
close $proxy_server; # no use to slave
$kidpid = fork();
die "Cannot fork" unless defined $kidpid;
# now each twin sits around and ferries lines of data.
# see how simple the algorithm is when you can have
# multiple threads of control?
# this is the fork's parent, the master's child
if ($kidpid) {
set_state("$rs_info --> $lc_info");
select($local_client); $| = 1;
print while <$remote_server>;
kill('TERM', $kidpid); # kill my twin cause we're done
}
# this is the fork's child, the master's grandchild
else {
set_state("$rs_info <-- $lc_info");
select($remote_server); $| = 1;
print while <$local_client>;
kill('TERM', getppid()); # kill my twin cause we're done
}
exit; # whoever's still alive bites it
} continue {
accepting();
}
}
# helper function to produce a nice string in the form HOST:PORT
sub peerinfo {
my $sock = shift;
my $hostinfo = gethostbyaddr($sock->peeraddr);
return sprintf("%s:%s",
$hostinfo->name || $sock->peerhost,
$sock->peerport);
}
# reset our $0, which on some systems make "ps" report
# something interesting: the string we set $0 to!
sub set_state { $0 = "$ME [#_]" }
# helper function to call set_state
sub accepting {
set_state("accepting proxy for " . ($REMOTE || $SERVICE));
}
# somebody just died. keep harvesting the dead until
# we run out of them. check how long they ran.
sub REAPER {
my $child;
my $start;
while (($child = waitpid(-1,WNOHANG)) > 0) {
if ($start = $Children{$child}) {
my $runtime = time() - $start;
printf "Child $child ran %dm%ss\n",
$runtime / 60, $runtime % 60;
delete $Children{$child};
} else {
print "Bizarre kid $child exited $?\n";
}
}
# If I had to choose between System V and 4.2, I'd resign. --Peter Honeyman
$SIG{CHLD} = \&REAPER;
};
As I said, that’s from 1998. These days I’d use warnings and possibly use autodie, but you still should be able to learn a good bit from it.

Related

how to have perl get https website links to trigger cron job

Currently I have a perl script to trigger a cron job on multiple websites however the script is only working for http and not for https. In other words when the script tries to trigger a link on a domain with an SSL certificate, the cron never gets triggered. What is required to trigger the https cron URL?
#!/usr/bin/perl
#
# Enter domains, one per line.
#
# They do not have to be indented.
#
# Do not use an '#' on the beginning of your domains.
#
# Enter domains below next line: (without #)
my #domains = qw/#############################
#www.example.com
#www.example.com
#www.example.com
###############_END_OF_DOMAINS_###############/;
my $num_domains = 200; # number of domains
my $cycle_time = 900; # seconds per cycle
# So if $num_domain=200 and $cycle_time=900,
# there will be 4.5 seconds between sites.
# Nothing below here to service.....
#############################################################################
#############################################################################
#############################################################################
#############################################################################
use strict;
use warnings;
use IO::Socket;
use constant DEBUG => 0;
my $time_wait = $cycle_time / $num_domains;
my #stdin = -t STDIN
? ()
: grep !/^$/, map { s/^\s+//; s/\s+$//; s/\#.*?$//; $_ } <STDIN>
;
for my $site ( grep !/^\s*\#/, #domains, #stdin ) {
chomp $site;
print "contacting: $site\n" if DEBUG;
my $sock = IO::Socket::INET->new(
PeerHost => $site,
PeerPort => 'http(80)',
Proto => 'tcp',
Type => SOCK_STREAM,
Timeout => 10,
);
unless ( defined $sock ) {
warn "Couldn't connect to $site: $!\n" if DEBUG;
next;
}
#Example target:
# http://www.example.com/index.php?option=com_acymailing&ctrl=cron
print $sock
"GET /index.php?option=com_acymailing&ctrl=cron HTTP/1.0\n" .
"Host: $site\n\n"
;
# wait for response...
while (defined( $_ = scalar <$sock> )) {
# we dont care what it said...
print 'recv: ', $_ if DEBUG;
}
print "\ndone\n" if DEBUG;
$sock->close;
}
continue {
print "sleeping $time_wait seconds\n" if DEBUG;
select( undef, undef, undef, $time_wait );
}
__END__
It seems likely that the problem is because you're always configuring your socket object to use the HTTP port (PeerPort => 'http(80)'). The HTTPS port is 443, not 80.
But I have no idea why you would write code like this using low-level socket programming. You would be far better advised to look at something like LWP::Simple (you will also want to install LWP::Protocol::https for HTTPS support).

Docker: hostname different from container name

I am using an docker overlay network
I am starting a server, myserver.pl, in a container
use strict;
use warnings;
use POSIX qw(:sys_wait_h :errno_h :fcntl_h strftime);
use Getopt::Long;
use File::Basename;
use IO::Socket;
use IO::Select;
STDOUT->autoflush(1);
use Fcntl;
use Socket;
use Carp;
use FileHandle;
use Cwd qw{abs_path cwd};
use Sys::Hostname;
our $HOST = hostname();
our $PUBQDIR = '/x/eng/site/build/altload';
our $PWD = cwd();
our $EMPTY = q{};
our $NL = "\n";
our $SPACE = q{ };
our $ANY_RE = qr{.*};
our $REDIS_OS_RE = qr{^br-redis$};
our $HOST_SPLIT_RE = qr{[.][^:]*};
our $bash_and_list_op = '&&';
my $Request_File = $EMPTY;
# options from command line
my $Opt_Class = 'L';
my $Redis = $EMPTY;
my %Published_Requests = (); # Track published queue requests for cleanup
chomp $HOST;
$HOST =~ s/[.].*//g;
chomp $PWD;
my $Pool = $EMPTY;
sub sock_initialize {
my $sock = q{};
my $port = q{};
# Get a port for our server.
$sock = IO::Socket::INET->new(
Listen => SOMAXCONN, # listen queue depth
LocalPort => 0,
Reuse => 1
);
die "Unable to bind a port: $!" if !$sock;
$port = $sock->sockport();
# Determine and log the dispatcher queue id
#my $ip = inet_ntoa( scalar gethostbyname( $HOST ));
my $ip = "";
my $uid = (getpwuid( $> ))[2];
my $queue = join(":", $ip, $port, $$, $uid);
# Include in the published queue name:
# - job class, which must be the same for all jobs we submit
# - Our Hostname and Port
# Can't lower hostname to IP yet, it might give 127.0.0.1
print sprintf("Connect me at $HOST on port $port ($$), SOMAXCONN=%d\n", SOMAXCONN);
return $sock;
} ## end sub sock_initialize
my $listen_sock = sock_initialize();
while (1) {
#my $xsock = Accept();
my $xsock;
while (1) {
$! = 0;
# Accept can block. Need to use nonblocking poll (Stevens)
$xsock = $listen_sock->accept; # ACCEPT
last if defined $xsock;
next if $! == EINTR;
die "accept error: $!";
if ( defined $xsock ) {
$xsock->blocking(0); # mark executor socket nonblocking
$xsock->sockopt( SO_KEEPALIVE() => 1 ) or die "sockopt: $!";
}
}
my $buff = "";
while (1) {
my $nbytes = sysread $xsock, $buff, 32768, length($buff); # SYSCALL
if ( !defined $nbytes ) { # read error
next if $! == EINTR;
last if $! == EWOULDBLOCK; # normal
return;
}
last if $nbytes == 0; # EOF
}
print "received $buff\n";
last;
by docker run -v /home/:/home/ --name myhost01 --network test-net perl perl /home/myserver.pl
it outputs:
Connect me at ebcf3c65c3e1 on port 39580 (1), SOMAXCONN=128
I can connect to this server from another container on different host (docker daemon) attached to the same overlay network via : i.e. myhost01:39580
my $host = "myhost01";
my $port = 39850;
my $s = IO::Socket::INET->new (PeerAddr => $host,
PeerPort => $port,
Type => SOCK_STREAM,
Proto => 'tcp',
Timeout => 1);
So, thats fine but i was aware of the container name beforehand so i knew to connect to it, I want the myserver.pl to advertise its hostname as myhost01, not ebcf3c65c3e1
Connect me at myhost01 on port 39580 (1), SOMAXCONN=128
Is there a way to set the hostname inside the container that it thinks the hostname is the same as container name and that perl's Sys::Hostname:hostname() or python's socket.gethostname() or whatever ways to get hostname would think that and not using container id such as ebcf3c65c3e1 as hostname?
[EDIT]
i see the option --hostname for docker run but do i really need to supply both --name and --hostname? Is there other ways to automatically inherit container name as its hostname?
Personal opinion: you should leave the hostname alone. If you need to spawn multiple, similar containers with scale: <n>, any attempt to set the hostname would not work.
But you can obtain the container name from inside the container via docker:socket, and use that for many of your other purposes, such as for the code to identify itself or other containers.
See my example here

Better way to handle perl sockets to read/write to active proccess

First of all I would thank you guys not offering a work around as a solution (although it would be cool to know other ways to do it). I was setting up tg-master project (telegram for cli) to be used by check_mk alert plugin. I found out that telegram runs on a stdin/stdout proccess so I tought it would be cool to "glue" it, so i wrote with a lot of building blocks from blogs and cpan the next 2 pieces of code. They already work (i need to handle broken pipes sometimes) but I was wondering if sharing this could come from some experts new ideas.
As you could see my code relies on a eval with a die reading from spawned process, and I know is not the best way to do it. Any suggestions? :D
Thank you guys
Server
use strict;
use IO::Socket::INET;
use IPC::Open2;
use POSIX;
our $pid;
use sigtrap qw/handler signal_handler normal-signals/;
sub signal_handler {
print "what a signal $!\nlets kill $pid\n";
kill 'SIGKILL', $pid;
#die "Caught a signal $!";
}
# auto-flush on socket
$| = 1;
# creating a listening socket
my $socket = new IO::Socket::INET(
LocalHost => '0.0.0.0',
LocalPort => '7777',
Proto => 'tcp',
Listen => 5,
Reuse => 1
);
die "cannot create socket $!\n" unless $socket;
print "server waiting for client connection on port 7777\n";
my ( $read_proc, $write_proc );
my ( $uid, $gid ) = ( getpwnam "nagios" )[ 2, 3 ];
POSIX::setgid($gid); # GID must be set before UID!
POSIX::setuid($uid);
$pid = open2( $read_proc, $write_proc, '/usr/bin/telegram' );
#flush first messages;
eval {
local $SIG{ALRM} = sub { die "Timeout" }; # alarm handler
alarm(1);
while (<$read_proc>) { }
};
while (1) {
my $client_socket = $socket->accept();
my $client_address = $client_socket->peerhost();
my $client_port = $client_socket->peerport();
print "connection from $client_address:$client_port\n";
# read until \n
my $data = "";
$data = $client_socket->getline();
# write to spawned process stdin the line we got on $data
print $write_proc $data;
$data = "";
eval {
local $SIG{ALRM} = sub { die "Timeout" }; # alarm handler
alarm(1);
while (<$read_proc>) {
$client_socket->send($_);
}
};
# notify client that response has been sent
shutdown( $client_socket, 1 );
}
$socket->close();
Client
echo "contact_list" | nc localhost 7777
or
echo "msg user#12345 NAGIOS ALERT ... etc" | nc localhost 7777
or
some other perl script =)
If you are going to implement a script that performs both reads and writes from/to different handles, consider using select (the one defined as select RBITS,WBITS,EBITS,TIMEOUT in the documentation). In this case you will totally avoid using alarm with a signal handler in eval to handle a timeout, and will only have one loop with all of the work happening inside it.
Here is an example of a program that reads from both a process opened with open2 and a network socket, not using alarm at all:
use strict;
use warnings;
use IO::Socket;
use IPC::Open2;
use constant MAXLENGTH => 1024;
my $socket = IO::Socket::INET->new(
Listen => SOMAXCONN,
LocalHost => '0.0.0.0',
LocalPort => 7777,
Reuse => 1,
);
# accepting just one connection
print "waiting for connection...\n";
my $remote = $socket->accept();
print "remote client connected\n";
# simple example of the program writing something
my $pid = open2(my $localread, my $localwrite, "sh -c 'while : ; do echo boom; sleep 1 ; done'");
for ( ; ; ) {
# cleanup vectors for select
my $rin = '';
my $win = '';
my $ein = '';
# will wait for a possibility to read from these two descriptors
vec($rin, fileno($localread), 1) = 1;
vec($rin, fileno($remote), 1) = 1;
# now wait
select($rin, $win, $ein, undef);
# check which one is ready. read with sysread, not <>, as select doc warns
if (vec($rin, fileno($localread), 1)) {
print "read from local process: ";
sysread($localread, my $data, MAXLENGTH);
print $data;
}
if (vec($rin, fileno($remote), 1)) {
print "read from remote client: ";
sysread($remote, my $data, MAXLENGTH);
print $data;
}
}
In the real production code you will need to carefully check for errors returned by various function (socket creation, open2, accept, and select).

Perl IO::Socket/IO::Select - reading from a "ready-to-read" socket

This is probably not Perl-specific, but my demo is in Perl.
My master program opens a listening socket, and then forks a child process. The child's first job is to connect to the master and say HELLO. Then it continues its initialization, and when it is ready, it sends READY to the master.
The master, after having forked the child, waits for HELLO and then goes about other initialization (forking other children, mainly). Once it has forked all the children and heard HELLO back from each, it proceeds to wait for all of them to say READY.
It does this using IO::Select->can_read, and then $socket->getline to retrieve the message.
In short, the parent is failing to receive the READY, even though it's being sent by the child.
Here is a hastily-stripped-down version of my program that demo's the bug (I tried to remove irrelevancies but a few may remain). I'm still confused by issues like whether message boundaries are preserved, and whether "\n" is needed, and which method to use for reading from a socket. I really don't want to have to think about assembling message fragments, and I'm hoping IO::Select will spare me that.
The demo only spawns one child, for simplicity.
#!/usr/bin/env perl
use warnings;
use strict;
use Carp;
use File::Basename;
use IO::Socket;
use IO::Select;
use IO::File; # for CONSTANTS
use Net::hostent; # for OO version of gethostbyaddr
use File::Spec qw{rel2abs}; # for getting path to this script
use POSIX qw{WNOHANG setsid}; # for daemonizing
use 5.010;
my $program = basename $0;
my $progpath = File::Spec->rel2abs(__FILE__);
my $progdir = dirname $progpath;
$| = 1; # flush STDOUT buffer regularly
# Set up a child-reaping subroutine for SIGCHLD. Prevent zombies.
#
say "setting up sigchld";
$SIG{CHLD} = sub {
local ( $!, $^E, $# );
while ( ( my $kid = waitpid( -1, WNOHANG ) ) > 0 ) {
say "Reaping child process $kid";
}
};
# Open a port for incoming connections
#
my $listen_socket = IO::Socket::INET->new(
Proto => 'tcp',
LocalPort => 2000,
Listen => SOMAXCONN,
Reuse => 1
);
croak "Can't set up listening socket: $!\n" unless $listen_socket;
my $readers = IO::Select->new($listen_socket)
or croak "Can't create the IO::Select read object";
say "Forking";
my $manager_pid;
if ( !defined( $manager_pid = fork ) ) {
exit;
}
elsif ( 0 == $manager_pid ) {
#
# ------------------ BEGIN CHILD CODE HERE -------------------
say "Child starting";
my ($master_addr, $master_port) = split /:/, 'localhost:2000';
my $master_socket = IO::Socket::INET->new(
Proto => "tcp",
PeerAddr => $master_addr,
PeerPort => $master_port,
) or die "Cannot connect to $master_addr:$master_port";
say "Child sending HELLO.";
$master_socket->printflush("HELLO\n");
# Simulate elapsed time spent initializing...
#
say "Child sleeping for 1 second, pretending to be initializing ";
sleep 1;
#
# Finished initializing.
say "Child sending READY.";
$master_socket->printflush("READY\n");
say "Child sleeping indefinitely now.";
sleep;
exit;
# ------------------- END CHILD CODE HERE --------------------
}
# Resume parent code
# The following blocks until we get a connect() from the manager
say "Parent blocking on ready readers";
my #ready = $readers->can_read;
my $handle;
for $handle (#ready) {
if ( $handle eq $listen_socket ) { #connect request?
my $manager_socket = $listen_socket->accept();
say "Parent accepting connection.";
# The first message from the manager must be his greeting
#
my $greeting = $manager_socket->getline;
chomp $greeting;
say "Parent received $greeting";
}
else {
say( $$, "This has to be a bug" );
}
}
say "Parent will now wait until child sends a READY message.";
say "NOTE: if the bug works, Ill never receive the message!!";
################################################################################
#
# Wait until all managers have sent a 'READY' message to indicate they've
# finished initializing.
#
################################################################################
$readers->add($handle); # add the newly-established socket to the child
do {
#ready = $readers->can_read;
say "Parent is ignoring a signal." if !#ready;
} until #ready;
# a lot of overkill for demo
for my $socket (#ready) {
if ( $socket ne $listen_socket ) {
my $user_input;
$user_input = $socket->getline;
my $bytes = length $user_input;
if ( $bytes > 0 ) {
chomp $user_input;
if ( $user_input eq 'READY' ) {
say "Parent got $user_input!";
$readers->remove($socket);
}
else {
say( $$, "$program RECVS $user_input??" );
}
}
else {
say( $$, "$program RECVs zero length message? EOF?" );
$readers->remove($socket);
}
}
else {
say( $$, "$program RECVS a connect on the listen socket??" );
}
} # end for #ready
say "Parent is ready to sleep now.";
I don't know if that's your (only) problem, but always use sysread with select. Never used buffered IO like getline. getline doubly makes no sense since it can block for data that hasn't been received yet.
Your select loop should look like:
For ever,
Wait for sockets to become ready for reading.
For each socket ready to be read,
sysread($that_socket, $buffer_for_that_socket, 64*1024,
length($buffer_for_that_socket));
If sysread returned undef,
Handle error.
If sysread return false,
Handle closed socket. Don't forget about data left in the buffer.
Otherwise, handle read data:
while ($buffer_for_that_socket =~ s/^(.*)\n//) { my $msg = $1; ... }

How do I save sockets in a hash and loop over them from another thread?

I am working on a mulithreaded TCP server. In the main thread, I listen on a socket and create a new thread for new incoming connections. I want to save all incoming connections in a hash so that I can access them from yet another thread.
From the monitor thread, I can not read any newly added connections. It seems a new clients hash is created when creating the monitor thread.
How do i keep list of all sockets and loop them from my monitor thread?
Current code:
#!/usr/bin/perl
use strict;
use IO::Socket;
use threads;
use Thread::Queue;
# init
my $clients = {};
my $queue = Thread::Queue->new;
# thread that monitors
threads->create("monitor");
# create the listen socket
my $listenSocket = IO::Socket::INET->new(LocalPort => 12345,
Listen => 10,
Proto => 'tcp',
Reuse => 1);
# make sure we are bound to the port
die "Cant't create a listening socket: $#" unless $listenSocket;
print "Server ready. Waiting for connections on 34567 ... \n";
# wait for connections at the accept call
while (my $connection = $listenSocket->accept) {
# set client socket to non blocking
my $nonblocking = 1;
ioctl($connection, 0x8004667e, \\$nonblocking);
# autoflush
$connection->autoflush(1);
# debug
print "Accepted new connection\n";
# add to list
$clients->{time()} = $connection;
# start new thread and listen on the socket
threads->create("readData", $connection);
}
sub readData {
# socket parameter
my ($client) = #_;
# read client
while (<$client>) {
# remove newline
chomp $_;
# add to queue
$queue->enqueue($_);
}
close $client;
}
sub monitor {
# endless loop
while (1) {
# loop while there is something in the queue
while ($queue->pending) {
# get data from a queue
my $data = $queue->dequeue;
# loop all sockets
while ( my ($key, $value) = each(%$clients) ) {
# send to socket
print $value "$data\n";
}
}
# wait 0,25 seconds
select(undef, undef, undef, 0.25);
}
}
close $listenSocket;
You need to share $clients via share from threads::shared:
my $clients = &share({});
The old-fashioned syntax is due to a documented issue with Perl’s prototypes. If you have at least Perl 5.8.9, use the nicer
my $clients = shared_clone({});
instead.
You also want to protect $clients with a lock, e.g.,
my $clients_lock : shared;
{
lock $clients_lock;
$clients->{time()} = fileno $connection;
}
Finally, because IO::Socket::INET instances are Perl typeglobs, you can’t share them, so instead add their socket descriptors (from fileno) to $clients and then fdopen the socket when necessary with
open my $fh, ">&=", $sockdesc or warn ...
The program below repeats inbound data to the other connected sockets:
#!/usr/bin/perl
use strict;
use IO::Socket;
use threads;
use threads::shared;
use Thread::Queue;
# init
my $clients = &share({});
my $clients_lock : shared;
my $queue = Thread::Queue->new;
# thread that monitors
threads->create("monitor");
# create the listen socket
my $port = 12345;
my $listenSocket = IO::Socket::INET->new(
LocalPort => $port,
Listen => 10,
Proto => 'tcp',
Reuse => 1
);
# make sure we are bound to the port
die "Can't create a listening socket: $#" unless $listenSocket;
print "Server ready. Waiting for connections on $port ... \n";
# wait for connections at the accept call
while (my $connection = $listenSocket->accept) {
# set client socket to non blocking
my $nonblocking = 1;
ioctl($connection, 0x8004667e, \\$nonblocking);
# autoflush
$connection->autoflush(1);
# debug
print "Accepted new connection\n";
# add to list
{
lock $clients_lock;
$clients->{time()} = fileno $connection;
}
# start new thread and listen on the socket
threads->create("readData", $connection);
}
sub readData {
# socket parameter
my ($client) = #_;
# read client
while (<$client>) {
chomp;
$queue->enqueue($_);
}
close $client;
}
sub monitor {
# endless loop
while (1) {
# loop while there is something in the queue
while ($queue->pending) {
# get data from a queue
my $data = $queue->dequeue;
# loop all sockets
{
lock $clients_lock;
while ( my ($key, $value) = each(%$clients) ) {
# send to socket
if (open my $fh, ">&=", $value) {
print $fh "$data\n";
}
else {
warn "$0: fdopen $value: $!";
}
}
}
}
# wait 0,25 seconds
select(undef, undef, undef, 0.25);
}
}
close $listenSocket;
Don't have too much experience using threads in Perl, but I think you just want to share your client list:
use threads::shared;
my $clients : shared = {};
Update:
Perl complains about:
my $hash : shared = {};
but it seems to be ok with:
my $hash = {};
share($hash);
Also, this code:
my $hash = { key1 => "value1" };
share($hash);
seems to clear the hashtable, but
my $hash = {};
share($hash);
$hash->{key1} = "value1";
works like I'd expect.