danmcb has asked for the wisdom of the Perl Monks concerning the following question:

Hi

My problem is to write a server script which processes incoming queries and responds to them in the usual way ... but also runs a periodic "report" function where it talks back at the client of its own accord. The report code is therefore a seperate thread.

The problem is that I don't find a way to allow the main and reporting threads to both talk back at the client. What seems to happen is that when I hasve the "write to socket" happening in both threads, they block each other in odd ways. (The report lines seem to appear after the first char of a client line is entered.)

This may be only a win32 problem, haven't tried on linux yet.

Here's my first attempt:

use strict; use warnings; use threads; use IO::Socket qw(:DEFAULT :crlf); use Time::HiRes qw( usleep ); my $sock = new IO::Socket::INET (LocalAddr => "localhost", LocalPort => 1000, Proto => 'tcp', Listen => 1, Reuse => 1); my $SOCK; sub sockout{ my $r = shift; print $SOCK "$r\r\n"; } sub chatter{ while (1){ usleep(1000 * 1000); sockout "talkin to myself ..."; } } $SOCK = $sock->accept(); my $thread = threads->create(\&chatter); sockout("CONNECTED!"); while (my $line = <$SOCK>){ sockout("You said ".$line); }

One suggestion that has been made is to use a queue. I tried, same problem. Here it is:

use strict; use warnings; use threads; use Thread::Queue; use IO::Socket qw(:DEFAULT :crlf); use Time::HiRes qw( usleep ); my $sock = new IO::Socket::INET (LocalAddr => "localhost", LocalPort => 1000, Proto => 'tcp', Listen => 1, Reuse => 1); my $SOCK; my $rq; sub sockout{ my $r = shift; print $SOCK "$r\r\n"; } sub chatter{ while (1){ usleep(1000 * 1000); sockout "talkin to myself ..."; } } sub reply_thread{ while (1){ while ($rq->pending){ print "rt pr\n"; print $SOCK $rq->dequeue; }; usleep(1000 * 100); } } $SOCK = $sock->accept(); $rq = new Thread::Queue; my $reply_thread = threads->create(\&reply_thread); my $chatter_thread = threads->create(\&chatter); sockout("CONNECTED!"); while (my $line = <$SOCK>){ sockout("You said ".$line); }

Any ideas?

Replies are listed 'Best First'.
Re: sharing a socket between two threads (win32)
by Melly (Chaplain) on Oct 27, 2006 at 13:06 UTC

    I've done almost no work involving threaded apps, and only one or two projects using sockets, but it occurs to me that you would be better off having your main program request the report from the client via the thread (i.e. always use your thread to handle the socket comms.).

    Tom Melly, tom@tomandlu.co.uk
Re: sharing a socket between two threads (win32)
by zentara (Cardinal) on Oct 27, 2006 at 14:37 UTC
    FileHandles and threads may be useful to you. On linux, at least, you can share filehandles across threads, thru the fileno. That may help you find a way to communicate the fileno of the socket thru shared variables. You might be able to setup a system of shared-variables that signal the threads for that socket when they can send and/or recv. Another possibility would be to pass the socket to the threads in the ->create() statement, then use shared vars to signal them into action of send/recv. But I'm just brainstorming and wondering why you even need 2 threads, when 1 socket can do bi-directional communication? Can't you setup a protocol of some sort, that says, "hey this is a report", and just send it over the single socket? Unless the data is all being sent 1 way, and you don't want to interrupt it.....in that case how about setting up a second socket connection on a separate port for the reports?

    This is the basic client I use, which splits itself for birdirectional communication. So all you need to do is in your server, send reports with a header like <report> $report </report>, and have your client regex for it and handle it.

    #!/usr/bin/perl -w use strict; use IO::Socket; my ( $host, $port, $kidpid, $handle, $line ); #unless ( @ARGV == 2 ) { die "usage: $0 host port" } ( $host, $port ) = @ARGV || ('localhost',8989); # 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 $line; } }

    I'm not really a human, but I play one on earth. Cogito ergo sum a bum

      the reason for having threads is simple : one thread has to wait on a line arriving from the user (client), respond to it, and repeat for ever. The other has to just report back autonomously every second. The responses seen at the client should look like this:

      OK OK ? report: 27 OK ? report: 36 OK

      With OK and ? being the responses to client commands. As you say, they are received by the same piece of code which parses them and acts appropriately. That's fine, and nothing to do with needing threads, which is a result of needing to both wait for user input AND act autonomously.

      The simple solution, which I'll likely take, is juts to poll periodically from the client. I just liked the idea of being able to say "start reporting" ... "stop reporting" instead of that.

Re: sharing a socket between two threads (win32)
by BrowserUk (Patriarch) on Oct 28, 2006 at 06:57 UTC

    Whether it's a limitation of Win32 sockets or Perl or sockets in general, attempting to send to a socket whilst simultaneously in attempting to read from it doesn't work. It's not a limitation of threads, though without threads simultaneously reading and writing is a bit harder :)

    To alleviate the problem, it's necessary to set the socket into non-blocking mode and use sysread rather than the buffered IO primitives.

    This code does pretty much what you've ask for, but don't take it as being a good example of anything :) Just a starting point:

    #! perl -slw use strict; package threads::SimpleServer; use threads; use Thread::Queue; use IO::Socket qw(:DEFAULT :crlf); use Time::HiRes qw( usleep ); our @ISA = 'Thread::Queue'; our $die:shared = 0; sub listener { my( $in, $out, $port ) = @_; my $sock = new IO::Socket::INET ( LocalAddr => "localhost", LocalPort => $port, Proto => 'tcp', Listen => 1, Reuse => 1 ); my $client = $sock->accept; async { ioctl( $client, 0x8004667e, pack( 'V', 1 ) ) or die $^E; my( $buffer, $p ) = ( '', 0 ); while( not $die ) { local $\ = CRLF; print $client $out->dequeue while $out->pending; my $bytes = sysread( $client, $buffer, 1024, length( $buff +er ) ); last unless defined $bytes or 0+$^E == 10035; sleep 1 and next unless $p = 1+index( $buffer, CRLF ); $in->enqueue( substr( $buffer, 0, $p+1, '' ) ) while $p = 1+index( $buffer, CRLF ); } }->detach; } sub new { my( $class, %args ) = @_; my( $in, $out ) = map{ bless Thread::Queue->new(), $class } 1 .. 2 +; threads->create( \&listener, $in, $out, $args{ port } || 1000 )->d +etach; return $in, $out; } 1; package main; use threads; #use threads::SimpleServer; my( $in, $out ) = threads::SimpleServer->new( port => 1000 ); async{ my $reportNo; $out->enqueue( 'report:' . ++$reportNo ) while sleep 1; }->detach; while( my $cmd = $in->dequeue ) { warn "got:'$cmd'"; $out->enqueue( ( rand > 0.5 ) ? 'OK' : '?' ); } END{ our $die = 1; sleep 1 }

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.