I have written a network chat program as my first major project in Perl. It makes simple use of REGEXP's, modules, sockets, command-line option parsing, and forking and uses these features to produce a fairly simple client-server chat program with a basic nickname system that is capable of handling as many clients at once as IO::Socket::INET is capable of.
I would like to know a few things; firstly, is this good Perl? This is my first major project so I am expecting to have made some rookie mistakes in both programming style and use of features. Secondly, what do you think about the readibility of them? I know Perl is pretty notorious for having nearly imposible to read code, but I tried my best to keep this as readable as possible. Finally, how was my use of sockets and the networking features? This is also my first ever networking project so I'm unsure as to whether I've done it well or if I've made any major faux-pas's.
server.pl
use strict;
use warnings;
use IO::Socket::INET;
use Getopt::Long;
my $MAXLEN = 1024;
my $PORTNO;
GetOptions("port=i" => \$PORTNO);
die "Need port!\n" unless defined $PORTNO;
my $sock = IO::Socket::INET->new(
LocalPort => $PORTNO,
Proto => 'udp'
) or die "sock: $!";
print "Waiting for users on $PORTNO...\n";
my %clients;
my $msg;
while ($sock->recv($msg, $MAXLEN)) {
my $ipaddr = gethostbyaddr($sock->peeraddr, AF_INET);
my $port = $sock->peerport;
my $cur_client = "$ipaddr:$port";
my $first_msg = 0;
if (not exists $clients{$cur_client}) {
$clients{$cur_client}->{nick} = "Guest";
$clients{$cur_client}->{address} = $ipaddr;
$clients{$cur_client}->{port} = $port;
$first_msg = 1;
}
if ($msg =~ /\/nick (\w+)/) {
my $prev_nick = $clients{$cur_client}->{nick};
$clients{$cur_client}->{nick} = $1;
if ($first_msg) { # I feel like this section is a bit hackey, is there a better way of doing this?
$msg = "[Server] new user: $1";
$first_msg = 0;
} else {
$msg = "[Server] nick change: $prev_nick -> $1 ";
}
} else {
$msg = join "", $clients{$cur_client}->{nick}, ": ", $msg;
}
print $msg, " ($cur_client)\n";
for (keys %clients) {
close $sock;
my $sock_send = IO::Socket::INET->new( # I feel like this should be unnecesary, is there a way of modifying
LocalPort => $PORTNO, # the existing $sock object instead of creating a new one each time
Proto => 'udp', # I want to send a message?
PeerAddr => $clients{$_}->{address},
PeerPort => $clients{$_}->{port}
) or die "sock: $!";
$sock_send->send($msg);
close $sock_send;
}
$sock = IO::Socket::INET->new(
LocalPort => $PORTNO,
Proto => 'udp'
) or die "sock: $!";
}
client.pl
use strict;
use warnings;
use IO::Socket::INET;
use Getopt::Long;
my ($port, $host);
my $nick = "Guest";
my $MAXLEN = 1024;
GetOptions( "port=i" => \$port,
"host=s" => \$host,
"nick=s" => \$nick);
die "Need Port!\n" unless defined $port;
die "Need host!\n" unless defined $host;
my $sock = IO::Socket::INET->new(
Proto => 'udp',
PeerPort => $port,
PeerAddr => $host
) or die "Creating socket: $!\n";
my $child;
if($child = fork) {
while(1) {
$sock->recv($_, $MAXLEN) or die "recv: $!\n";
print "$_\n";
next;
}
}
die "fork: $!\n" unless defined $child;
print "Connected as $nick to $host:$port\n";
$sock->send("/nick $nick") or die "send: $!\n";
while(<STDIN>) {
chomp;
$sock->send($_) or die "send: $!\n";
}
Note - "client.pl" requires both a host and a port in the form --host=blah --port=blahblah
, and "server.pl" requires a only a port (in the same format).