TCP Server using perl fork to accept multiple requests - perl

I'm trying to create a little server who handles multiple clients connections (at least 10). Below the current code that works perfect using fork. At least it accepts several connections from clients.
With the below code, I have the following behaviour:
Client ask for connection ==> Accepted ==> OK
Client sent packet ==> Received and printed ==> OK
Client sent another packet ==> Not received ==> NOK
Most probably, the while cicle will be activated only for each connection request, so that's the reason because I cannot retrieve other packets.
Could someone help me please to adjust the below code? What I need is establish one (or more) client connection, then client send data continuosly (without disconnection) and server should reply on each packet it receives.
#!/usr/bin/perl -w
use IO::Socket::INET;
$SIG{CHLD} = sub {wait ()};
my $socket = new IO::Socket::INET (
LocalHost => '0.0.0.0',
LocalPort => '5000',
Proto => 'tcp',
Listen => 5,
Reuse => 1);
die "cannot create socket $!n" unless $socket;
while ($new_sock = $socket->accept()) {
$pid = fork();
die "Cannot fork: $!" unless defined($pid);
if ($pid == 0) { # This is the fork child
$new_sock->recv(my $data, 500);
print "$data\n";
}
}

You need to loop around the recv call to read more than one package from the client. Also, as it's currently written, the SIGCHLD signal interrupts accept so when the first child process dies, your server program terminates. You could just add a loop around the accept loop to restart the accept call.
Example:
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket::INET;
$SIG{CHLD} = sub { wait; };
my $socket = new IO::Socket::INET (
LocalHost => '0.0.0.0',
LocalPort => '5000',
Proto => 'tcp',
Listen => 5,
Reuse => 1);
die "cannot create socket $!n" unless $socket;
sub child {
my $sock = shift;
my $data;
print "$$ connected\n";
# loop for as long as there's something coming in
while($sock->recv($data, 500) || $data) {
print "$$ $data"; # prepend the data with the process id
}
print "$$ disconnected\n";
exit 0;
}
while(1) {
while (my $new_sock = $socket->accept()) {
my $pid = fork();
die "Cannot fork: $!" unless defined($pid);
if ($pid == 0) { # This is the fork child
child($new_sock);
}
}
print "accept interrupted - restarting\n";
}

Related

Perl, IO::Socket::SSL, multi-threading

I've implemented a small webserver in Perl. It is listening with IO::Socket::INET and IO::Socket::SSL parallel.
If a connect appears at the HTTP-port I start a thread and handle the IO::Socket::INET reference.
Because of thread-limitations in Net::SSLeay (IO::Socket::SSL says in the doc is is not thread-safe below 1.43) I did NOT parallelize the SSL. I just call the handler-function in the same context.
In the parallelized case of HTTP the handler-function is the threads function.
All this is working as expected for a longer time.
I have now updated my system. Now my Net::SSLeay is 1.72 and I tried to paralellize the SSL too - the same way I do with the HTTP. But I get a segmentation fault at the first time I do a read.
use strict;
use warnings;
use IO::Handle;
use Fcntl ("F_GETFL", "F_SETFL", "O_NONBLOCK");
use Time::HiRes ("usleep");
use Socket;
use IO::Socket::SSL;
use threads;
STDOUT->autoflush ();
my $port = "4433";
my $cer = "cer.cer";
my $key = "key.key";
my $sock = IO::Socket::SSL->new (Listen => SOMAXCONN, LocalPort => $port,
Blocking => 0, Timeout => 0, ReuseAddr => 1, SSL_server => 1,
SSL_cert_file => $cer, SSL_key_file => $key) or die $#;
my $WITH_THREADS = 0; # the switch!!
for (;;)
{
eval
{
my $cl = $sock->accept ();
if ($cl)
{
print ("\nssl connect");
if ($WITH_THREADS == 0)
{
# this is no multi-threading
client ($cl);
}
else {
# with multithreading
my $th = threads->create (\&client, $cl);
$th->detach ();
}
}
}; # eval
if ($#)
{
print "ex: $#";
exit (1);
}
usleep (100000);
} # forever
sub client # worker
{
my $cl = shift;
# unblock
my $flags = fcntl ($cl, F_GETFL, 0) or die $!;
fcntl ($cl, F_SETFL, $flags | O_NONBLOCK) or die $!;
print "\n" . $cl->peerhost . "/" . $cl->peerport;
my $ret = "";
for (my $i = 0; $i < 100; $i ++)
{
$ret = $cl->read (my $recv, 5000);
# faults here if with threads!
if (defined ($ret) && length ($recv) > 0)
{
print "\nreceived $ret bytes";
}
else
{
print "\nno data";
}
usleep (200000);
}
print "\nend client";
$cl->close ();
}
I have also read some posts where they said IO::Socket::SSL is not threadsafe but I am not sure that is still the case.
Does anyone know if it is possible that way? Or maybe it is possible but I am handling it the wrong way...
Thanks,
Chris
EDIT: I use Debian 8.3 with Perl 5.20.2.
Net::SSLeay is 1.72, IO::Socket::SSL is 2.024.
OpenSSL 1.0.1k
EDIT: Changed the code-sample to fully functional little sample-program.
TL;TR:
don't duplicate an established SSL socket into another thread.
Details:
You accept the SSL socket in the master thread into $cl and then create a new thread which works on the new socket. In effect this means you have the same file descriptor (kernel), the same OpenSSL data structure (user space) but two Perl variables using this single data structure (perl threads are shared nothing - so the Perl part is duplicated).
This only causes trouble because you then implicitly close the socket in the master ($cl gets out of scope) but continue to use it in the client thread. The close in the master thread causes an SSL shutdown and then frees the underlying OpenSSL structure. Thus $cl in the client thread points to some freed memory which causes the crash. You actually get something like this (without crash) also if you use forking instead of threading, because there is still the SSL shutdown done in the master process so the peer considers the socket closed and the child will not be able to make further use of the socket.
Instead of doing the SSL accept in the master thread you should move every SSL activity into the client thread. This will be done by doing the accept on a normal socket object and then upgrading it to SSL in the client thread. This is the preferred way anyway, see Basic SSL server in the IO::Socket::SSL documentation for details.
In the end your code would be changed like this:
my $port = "4433";
my $cer = "cer.cer";
my $key = "key.key";
# don't create a SSL socket but an INET socket
my $sock = IO::Socket::IP->new (
Listen => SOMAXCONN, LocalPort => $port, Blocking => 0, ReuseAddr => 1
) or die $!;
my $WITH_THREADS = 1; # the switch!!
....
sub client # worker
{
my $cl = shift;
# upgrade INET socket to SSL
$cl = IO::Socket::SSL->start_SSL($cl,
SSL_server => 1,
SSL_cert_file => $cer,
SSL_key_file => $key
) or die $#;

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).

Programming a chat room in perl, I'm having issues with the client?

I'm following this guide explaining how to do a server using IO::Async but I'm having issues with my client code. I have it where I send first then receive. This makes me press enter on each client before receiving any data. I figured I'd have to listen till I wanted to type something but I'm not really sure how. Below is my current client code.
use IO::Socket::INET;
# auto-flush on socket
$| = 1;
# create a connecting socket
my $socket = new IO::Socket::INET (
PeerHost => 'localhost',
PeerPort => '12345',
Proto => 'tcp',
);
die "cannot connect to the server $!\n" unless $socket;
print "My chat room client. Version One.\n";
while (1) {
my $data = <STDIN>;
$socket->send($data);
my $response = "";
$socket->recv($response, 1024);
print ">$response";
last if (index($data, "logout") == 0);
}
$socket->close();
I actually had this problem myself a few weeks ago when trying to make a client/server chat for fun.
Put it off until now.
The answer to your problem of having to hit enter to receive data, is that you need to use threads. But even if you use threads, if you do $socket->recv(my $data, 1024) you won't be able to write anything on the command line.
This isn't using your code, but here is my solution after banging my head against a wall for the last 24hrs. I wanted to add this as an answer, because though the question is out there on stackoverflow, none of the answers seemed to show how to use IO::Select.
Here is the server.pl script, it does not use threading:
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket::INET;
use IO::Select;
$| = 1;
my $serv = IO::Socket::INET->new(
LocalAddr => '0.0.0.0',
LocalPort => '5000',
Reuse => 1,
Listen => 1,
);
$serv or die "$!";
print 'server up...';
my $sel = IO::Select->new($serv); #initializing IO::Select with an IO::Handle / Socket
print "\nAwaiting Connections\n";
#can_read ( [ TIMEOUT ] )
#can_write ( [ TIMEOUT ] )
#add ( HANDLES )
#http://perldoc.perl.org/IO/Select.html
while(1){
if(my #ready = $sel->can_read(0)){ #polls the IO::Select object for IO::Handles / Sockets that can be read from
while(my $sock = shift(#ready)){
if($sock == $serv){
my $client = $sock->accept();
my $paddr = $client->peeraddr();
my $pport = $client->peerport();
print "New connection from $paddr on $pport";
$sel->add($client); #Adds new IO::Handle /Socket to IO::Select, so that it can be polled
#for read/writability with can_read and can_write
}
else{
$sock->recv(my $data, 1024) or die "$!";
if($data){
for my $clients ($sel->can_write(0)){
if($clients == $serv){next}
print $clients $data;
}
}
}
}
}
}
And the client.pl, which uses threads:
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket::INET;
use threads;
use IO::Select;
$| = 1;
my $sock = IO::Socket::INET->new("localhost:5000");
$sock or die "$!";
my $sel = IO::Select->new($sock);
print "Connected to Socket ". $sock->peeraddr().":" . $sock->peerport() . "\n";
#This creates a thread that will be used to take info from STDIN and send it out
#through the socket.
threads->create(
sub {
while(1){
my $line = <>;
chomp($line);
for my $out (my #ready = $sel->can_write(0)){
print $out $line;
}
}
}
);
while(1){
if(my #ready = $sel->can_read(0)){
for my $sock(#ready){
$sock->recv(my $data, 1024) or die $!;
print "$data\n" if $data;
}
}
}
There is one other problem that arises though, when the client receives data and prints it to the console, your cursor goes to a new line, leaving behind any characters you had typed.
Hope this helps and answers your question.
For a simple "just send from STDIN, receive to STDOUT" client, you could use any of telnet, nc or socat. These will be simple enough to use for testing.
$ telnet localhost 12345
$ nc localhost 12345
$ socat stdio tcp:localhost:12345
If you actually want to write something in Perl, because you want to use it as an initial base to start a better client from, you probably want to base that on IO::Async. You could then use the netcat-like example here. That will give you a client that looks-and-feels a lot like a simple netcat.
I am guessing you need to set the MSG_DONTWAIT flag on your recv call, and print the response only if it is non-null.
$socket->recv($response, 1024, MSG_DONTWAIT);
print ">$response" if ($response ne "");

Perl HTTP server not reading requests from clients

I have created an HTTP server in Perl to accept requests from clients.
At the moment only one client is sending the request.
This is how my set-up is:
Client --> Server (this is proxy server as well connecting to the internet), Apache 2 running on Ubuntu.
This is the Perl code for my server:
#!/usr/bin/perl
use IO::Socket::INET;
use strict;
use warnings;
use LWP::Simple;
# auto-flush on socket
$| = 1;
my $port = 7890;
# Create a listening port
my $socket = new IO::Socket::INET(
LocalHost => '127.0.0.1',
LocalPort => shift || $port,
Proto => 'tcp',
Listen => SOMAXCONN,
Reuse => 1
) or die "cannot create socket $!\n";
# open a file and write client requests to the file
$| = 1;
open(FH, '>>', '/home/suresh/clientrequest.txt')
or die "could not open the /home/suresh/clientrequest : $!\n";
print FH "server waiting for client on port\n"
or die "could not write to file : $!\n";
while (my $client_socket = $socket->accept()) {
$client_socket->autoflush(1);
#print FH "Welcome to $0 \n";
my $client_address = $socket->peerhost();
my $client_port = $client_socket->peerport();
print FH "connection from $client_address:$client_port\n";
# read from connected client
my $data = "";
$client_socket->recv($data, 1024);
print FH "Data received from $client_address:$client_port: $data\n";
# write response data to the client
$data = "Sucessfully processed your request";
$client_socket->send($data);
shutdown($client_socket, 1);
}
close(FH);
$socket->close();
When I bring this server up and try sending a request from a client, the request is written to the file, so it looks like the requests are captured by the server.
Can anyone please let me know what other configurations I need to do at server side and at client?
If you write
$| = 1;
then flushing is only activated for the default output filehandle. This is STDOUT unless changed with the select() builtin. So FH is not flushed here — I guess this was your intention. Instead, you have to write
FH->autoflush(1);

problem with IO::Socket TCP connection

I am trying to write a simple IO::Socket connection in perl. However, I am running into some problems. Here is the code on the server side:
my $listener =
IO::Socket::INET->new( LocalPort => 8000, Listen => 1, Reuse => 1 );
die "Can't create socket for listening: $!" unless $listener;
print "Listening for connections on port 8000\n";
while(1) {
while ($client = $listener->accept()) {
while ( <$client>) {
my #arguments = split(/ /, $_ );
my $result = "something" ;# here we do something in my code
warn $result;
print $client $result;
close $client;
}
}
}
And the client code:
use IO::Socket;
my $sock = new IO::Socket::INET (
PeerAddr => 'xx.xxx.xxx.xxx',
PeerPort => '8000',
Proto => 'tcp',
);
die "Could not create socket: $!\n" unless $sock;
$sock->autoflush(1);
print $sock "somethin something";
print "sent\n";
while ( <$sock> ) { print }
close $sock;
My problem now is that the data seems to be only sent from the client to teh sever when I close the client Perl program. I get the "sent" message on the client side, but the "something" message on the server side does not appear until after I have manually closed the client side.
Also, I want to get the server response. Thus far, since I have to close the script manually, the response does not et to the client side.
Can anyone help?
while ( <$sock> ) -- waits for a line. That is for a string, ended by "\n" character.
You must add "\n" to strings, or use 'read' function instead.