Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Basic network chat app in Perl

I am trying to write a basic network chat app in Perl for learning purposes. I currently have a server and client program that function almost as I want them to. Multiple clients can connect to the server and send messages to and from it. However, I'm not really sure how to go about sending messages from one client to another and would appreciate a push in the right direction here. Here is the code I have so far, thoughts?

Note: This is my first ever attempt at using networking or using Perl for a proper project so any other guidance on how it's written would also be appreciated.

chat_server.pl

#!/usr/bin/perl -w
# chat_server.pl
use strict;
use IO::Socket::INET;

my $port = shift or die "Port required!\n";
my $socket = IO::Socket::INET->new(
        LocalPort   => $port,
        Proto       => 'tcp',
        Listen      => SOMAXCONN
    ) or die "Can't create socket: $!!\n";
my $child;

print "Listening for clients on $port...\n";
REQUEST:
while(my $client = $socket->accept) {
    my $addr = gethostbyaddr($client->peeraddr, AF_INET);
    my $port = $client->peerport;

    if($child = fork) {
        print "New connection from $addr:$port\n";
        close $client;
        next REQUEST;
    } die "fork failed!\n" unless defined $child;

    while (<$client>) {
        print "[$addr:$port] says: $_";
        print $client "[$addr:$port] says: $_";
    }   
}
close $socket;

chat_client.pl

#!/usr/bin/perl -w
# chat_client.pl
use strict;
use IO::Socket::INET;

my $port            = shift or die "No port\n";
my $server          = shift or die "No server\n";
my $client_socket   = IO::Socket::INET->new(
        PeerPort    =>  $port,
        PeerAddr    =>  $server,
        Proto       =>  'tcp'
    ) or die "Can't create send socket: $!!\n";

my $child;

if($child = fork) {
    while(1) {
        sleep(1);
        print scalar <$client_socket>;
    }
}

die "fork failed!\n" unless defined $child;
print "Connected to $server:$port!\n";

do {
    print "> ";
    print $client_socket $_ if defined $_;
} while(<STDIN>);

print "Closing connection";
close $client_socket;
like image 992
jess Avatar asked Sep 22 '15 22:09

jess


1 Answers

A single client to a single server isn't too difficult - what you're doing with your code there is - effectively - creating a 1 to 1 relationship. Your forked server is talking exclusively to your client.

To get information to propagate (via the server) between multiple clients, you're going to have to get a bit more complicated - because you have separate processes, these processes now need to communicate with each other. This is a big enough question that there's a whole segment of the perl documentation about it: perlipc.

This is actually going to increase the complexity of your code substantially, because you're moving to a 1-to-many relationship on your communications, and they'll all be happening asynchronously.

Socket based communication is one form of inter-process communication (IPC) and you're already doing that. But your 'gotcha' here is that you're moving from 1 to 1 comms to 1 to many comms. You need to be able to broadcast, and this mode of communications doesn't support that particularly well.

What I would suggest is look at IO::Pipe - I've some example code here: How to extract data from Parallel::ForkManager in perl

Then use IO::Select and can_read to asynchronously decide if there's any data coming in on the pipe. You'll probably need an array of pipes - one per client - otherwise you might get concurrent stuff overlapping.

E.g.:

(From IO::Pipe doc page:

 my $pipe = IO::Pipe->new();
if($pid = fork()) { # Parent
        $pipe->reader();
        while(<$pipe>) {
        ...
        }
    }
    elsif(defined $pid) { # Child
        $pipe->writer();
        print $pipe ...
    }

Unfortunately there's a slight gotcha here - your pipes will be created by the forking process, but that in turn means you'll need to figure out how to handle an array of pipe and checking to see if they're readable.

That means you can't sit in a while loop around accepting sockets any more - that blocks, so you'd have messages queued until another client connects (which is really not going to be what you want). So you'll also need to use select again to check whether there's something ready to accept first.

like image 133
Sobrique Avatar answered Oct 10 '22 20:10

Sobrique