Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:
I've got allways good advices here! Now, with this ChatServer.pl and my DocTool (see under this Topic) I would like to give s.th back to the 'Perlies', but Setp by Step.
Based on the ChatServer written by Patric Haller I wrote this program mainly to exchange results of programs that are working parallel on different computers. This seems to me a quick, easy, nice and good working solution - therefore the default setup (no echo to client and no log to console).
If you are interested please have a look and let me know if you see a mistake or if you would agree to publish that in your code-corner.
Furthermore I put in some tags for my 'DocTool.pl' to demonstate this. I wrote this DocTool.pl to have a simple way to look at my packages, methods, .. if I can't remeber anymore how to use them. It is meant as an additional, personal (quick and easy) way to document Perl-Progs. For more details please search for DocTool
Thanks in advance, Carl
#!/usr/bin/perl # buffered Chat Server (build on the threaded chat server of Patric Ha +ller) # that does NOT echo the lines back to the # Client where they are from. Therefore: $ID which is used in the loop # of Reader and the Writer. # Furthermore it doesn't do anything if there is only 1 Client. use strict; use warnings; use threads; # pull in threading routines use threads::shared; # and variable sharing routines use IO::Socket::INET; # and rock the sock et use File::Temp qw/ :POSIX /; #t: buffered, threaded Chat Server, options -cev: our $p2Cons = ( "@ARGV" =~ /^\-[ev]*c/i ) ? 1 : 0; #: (-c) print +incomminge lines to console (used in Reader), default: no print to co +nsole our $e2Client = ( "@ARGV" =~ /^\-[cv]*e/i ) ? 1 : 0; #: (-e) echo inco +mming lines back to Client (used Reader & Writer), default: no echo our $verbose = ( "@ARGV" =~ /^\-[ec]*v/i ) ? 1 : 0; #: (-v) verbose mo +de, default: not verbose, (for logging use: >> File.log) if ( "@ARGV" =~ /\?|h/) { print "\nuse of this program:\n\t$0 [-celv]". "\n\t-c: print incomming lines to console,". "\n\t-e: echo incomming lines back to the client,". "\n\t-v: verbose mode\n\n"; exit; } # internal Variables our @chat:shared = (); # buffer for incomming lines # $Elm is used as hash, but the single Items cannot be protected by lo +ck # Therefore I choosed a string our $Elm:shared = ''; # our $NoClient:shared = 0; # No. of Clients connected our $EOL = "\r\n"; # signal for the Writer to die, # otherwise $NoClient and $Elm would be hard do admin. tmpnam() =~ /.+mp\/(.+)/; our $kill = $1.'my Socket broke'.$1; # to have a secure kill-code (no +matter how it looks like) $SIG{PIPE} = 'ignore'; sub ignore { ; } #We ignore SIGPIPEs generated by clients trying to work #with closed connections. SIGPIPEs, if not handled, cause deat +h. my $server = IO::Socket::INET->new(LocalPort => 3333, Type => SOCK +_STREAM, Reuse => 1, Listen => 10) or die $!; while (my $client = $server->accept()){ #forea +ch $client my $pAddr = $client->peerhost(); if ($pAddr !~ /^127\.0\.0\./ && $pAddr !~ /^10\.10\.10\.\d+/) +{ print $client 'Sorry not for you..',$EOL; print "Ooops, who was that? Addr: $pAddr\n"; close($client); next; } lock($NoClient); $NoClient++; cond_broadcast($NoClient); tmpnam() =~ /.+mp\/(.+)/; # get a secure ID lock($Elm); # add this ID to $E +lm $Elm .= "$1:0;"; cond_broadcast($Elm); my $r = threads->new(\&Reader::run, client => $client, "ID", +"$1" ); $r->detach(); my $w = threads->new( \&Writer::run, client => $client, "ID", +"$1", "Addr", $pAddr ); $w->detach(); } ##### package Reader; #: detached process to receive the Client's Input use threads; use threads::shared; sub new { #: create Reader my $pkg = shift; #: Package my $self = { @_ }; return bless($self, $pkg); ##: arr of blessed (self, pkg) } sub run { #: runs until the socket of this Reader dies; reads from the + socket and pushs it into @chat my $self = Reader->new(@_); #: Me my $socket = $self->{client}; #: The socket of the Client # my $ID = $self->{ID}; #: The ID (same as for the Writer) my $l; while(defined ($l = <$socket>) ){ # only 1 Client don't echo! print "$self->{ID} <\t$l" if $verbose; next if ($NoClient < 2 && (!$e2Client) ); # skip empty lines: this may not work for everyone $l =~ /(.+)[\n\r]+/; if ($1) { lock(@chat); # add ID, so that the Writer knows what NOT to send => NO +echo! push @chat, "$self->{ID}\t$1"; cond_broadcast(@chat); } print "$self->{ID}\t$1\n" if ($p2Cons); } # end while print "Reader $self->{ID}\n\tSocket-broke, kill-code $kill sent\n" + if $verbose; lock(@chat); # for The Writer: die! push @chat, "$self->{ID}\t$kill"; cond_signal(@chat); ##: nothing } ##### package Writer; #: detached process to print to the socket for the cli +ent use threads; use threads::shared; sub new { #: create a Writer my $pkg = shift; #: Package my $self = { @_ }; #: Me return bless($self, $pkg); ##: arr of blessed (self, pkg) } sub run { #: runs until it gets the code to die from the Reader my $self = Writer->new(@_); #: Me my $socket = $self->{client}; #: Socket to the Client my $ID = $self->{ID}; #: The Writer/Readers ID my $Time = time; my (%E, $min); printf "$ID\t%12s has connected at %s\n",$self->{Addr}, scalar(loc +altime($Time)); while( 1 ) { lock(@chat); cond_wait(@chat); # shall I die? last unless ( "@chat" !~ /$ID\s$kill/ ); lock($Elm); %E = (map { _split($_) } (split /;/, $Elm)); print "Writer $ID\n\tsends ",(($e2Client) ? ' ' : 'up to '),(@ +chat - $E{$ID})," lines from ",(scalar @chat)," of \@chat\n" if $verb +ose; foreach ( @chat[$E{$ID} .. $#chat] ) { # all before $E{$ID} + has been send by me /(.+?)\s(.+)[\n\r]*/; # split into $ID and org. line # and send only the line ONLY if it is not from 'my' Reade +r and it is not the kill-code if ($e2Client) { print $socket $2,$EOL if ( $2 ne $kill); } else { print $socket $2,$EOL if ( $1 ne $ID && $2 ne $kill); } } # now rewrite $Elm and Chat $E{$ID} = @chat; $min = min(values %E); # print "deleting form chat-buffer:\n",(map { $_."\n" } @ch +at[0 .. ($min-1)]),"\n" if $verbose; print "\tdelets from \@chat $min lines\n" if $verbose; @chat = @chat[$min .. $#chat]; # to eliminate all before $m +in and keep the rest $Elm =''; # to rewrite + $E foreach ( keys %E ) { $Elm .= "$_:".(($_ eq $ID) ? @chat : ($E{$_} - $min) ).';' +; } print "\tnew \@chat, size: ",scalar @chat,";\n\tClient indexes +:$Elm\n" if $verbose; cond_broadcast($Elm); } # end while # now eliminate the Writer's ID from $Elm and adj. NoClient lock($Elm); $min = ''; # used here as tmp foreach ( split /;/, $Elm ) { $min .= "$_;" if ( $_ !~ /^$ID\:/ && $_ =~/:/); } $Elm = $min; print "Writer $ID\n\tdies\n\tnew Client indexes:$Elm\n" if $verbos +e; cond_broadcast($Elm); lock($NoClient); $NoClient--; cond_broadcast($NoClient); printf "$ID\t%12s disconnected at %s after %s\n",$self->{Addr}, +scalar(localtime(time)), s2T(time-$Time); ##: nothing } sub s2T { #: calcs sec into days hh:mm:ss # my $dur = shift #: Duration in sec. transfrmd into Days hh:m +m:ss ##: formated string [d] hh:mm:ss if ( $_[0] > 86400 ) { my $ti = ( ($_[0]%86400)/3600 )%100; my $t = ($_[0]%86400) - ($ti*3600); return sprintf(" %i d %3i:%02i:%02i", int($_[0] / 86400),$ti,(($t/60)%60),($t-((($t/ +60)%60)*60))); } my $ti = ( $_[0]/3600 )%100; my $t = $_[0] - ($ti*3600); return sprintf("%3i:%02i:%02i",$ti,(($t/60)%60),($t-((($t/60)% +60)*60))); #: return fotmatted transcripted duration } sub min { #: min of value-list # @_ = #: LIST of values (int,float) my $m = shift; foreach (@_) { $m = $_ if $m > $_ } return $m; ##: min of list } sub _split { #: internal use to split a string 'key:item' into key + and item for a hash # my $_[0] #: String to be splitted at ':' /(.+):(.+)/; return ($1) ? ($1 => $2) : (); ##: pair Key => Item or an emp +ty list }
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: ChatServer
by zentara (Cardinal) on Jan 07, 2004 at 18:47 UTC | |
|
Re: ChatServer
by zentara (Cardinal) on Jan 07, 2004 at 21:14 UTC | |
by Anonymous Monk on Jan 08, 2004 at 09:56 UTC | |
by Anonymous Monk on Jan 08, 2004 at 12:47 UTC | |
|
Re: ChatServer
by zentara (Cardinal) on Jan 08, 2004 at 19:05 UTC |