Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

Re: share socket connections amongst multiple threads?

by zentara (Archbishop)
on Jul 08, 2005 at 21:11 UTC ( [id://473566]=note: print w/replies, xml ) Need Help??


in reply to share socket connections amongst multiple threads?

Awhile back an anonymous monk figured out a threaded-chat-echo-server at thread:shared. To save you time, here is the final code we came up with: (I 've included a forked client at the end too)
#!/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 c +onsole 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 clie +nt,". "\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\.1 +0\.\d+/ && $pAddr !~ /^192\.168\.0\.\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 $Elm $Elm .= "$1:0;"; cond_broadcast($Elm); my $r = threads->new(\&Reader::run, client => $client, "ID", +"$1", "Addr", $pAddr ); $r->detach(); my $w = threads->new( \&Writer::run, client => $client, "ID", +"$1" ); $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 $Time = time; printf "$ID\t%12s has connected at %s\n",$self->{Addr}, scalar +(localtime($Time)); my $l; while(defined ($l = <$socket>) ){ # only 1 Client don't echo! print "$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, "$ID\t$1"; cond_broadcast(@chat); } print "$ID\t$1\n" if ($p2Cons); } # end while printf "$ID\t%12s disconnected at %s after %s\n",$self->{Add +r}, scalar(localtime(time)), s2T(time-$Time); print "Reader $ID\n\tI'm going to die, bye ..\n" if $verbose; lock($NoClient); lock($Elm); $l = ''; # used here as tmp foreach ( split /;/, $Elm ) { $l .= "$_;" if ( $_ !~ /^$ID\:/ && $_ =~/:/); } $Elm = $l; print "\tnew Client indexes:$Elm\n" if $verbose; $NoClient--; cond_broadcast($NoClient); cond_broadcast($Elm); lock(@chat); cond_broadcast(@chat); ##: nothing } sub s2T { #: calcs sec into days hh:mm:ss # my $dur = shift #: Duration in sec. transfrmd into D +ays hh:mm: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 } ##### 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 (%E, $min); while( 1 ) { lock(@chat); cond_wait(@chat); # shall I die? last if ( $Elm !~ /$ID:/ ); next unless (@chat); 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 $verbose; foreach ( @chat[$E{$ID} .. $#chat] ) { # all before $ +E{$ID} has been send by me /(.+?)\s(.+)[\n\r]*/; # split into $ID and or +g. line # and send only the line ONLY if it is not fro +m 'my' Reader 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" } @chat[0 .. ($min-1)]),"\n" if $verbose; print "\tdelets from \@chat $min lines\n" if $verbose; @chat = @chat[$min .. $#chat]; # to eliminate all bef +ore $min 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 print "Writer $ID\n\tdies too, ..\n" if $verbose; ##: nothing } 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 o +r an empty list } __END__ ## a test client##################### #!/usr/bin/perl -w use strict; use IO::Socket; my ( $host, $port, $kidpid, $handle, $line ); ( $host, $port ) = ('192.168.0.1',3333); #my $name = shift || ''; #if($name eq ''){print "What's your name?\n"} #chomp ($name = <>); # 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() ); # the if{} block runs only in the parent process if ($kidpid) { # copy the socket to standard output while ( defined( $line = <$handle> ) ) { print STDOUT $line; } kill( "TERM", $kidpid ); # send SIGTERM to child } # the else{} block runs only in the child process else { # copy standard input to the socket while ( defined( $line = <STDIN> ) ) { #print $handle "$name->$line"; print $handle "$line"; } } __END__

I'm not really a human, but I play one on earth. flash japh

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://473566]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others musing on the Monastery: (4)
As of 2024-04-18 00:30 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found