http://www.perlmonks.org?node_id=548416

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

Hi, I am trying to create a simple text-based chat client. I have one thread (ithreads) listening for input that can arrive at any time from the chat server (using getline) and another thread accepting input from the user and sending it to the server (using print). The problem I am having is that while the listening thread waits for a new line of input in getline, it blocks the socket for writes from the other thread. I have done a similar thing in Java where this works just fine, ie reading from the input stream created from the socket doesn't prevent data from being written to the output stream on the same socket from another thread. I thought that sockets would work in a similar way in both Perl and Java, but either I am missing something, or they don't...? How should I solve this problem? I would appreciate any thoughts and ideas. Are there other ways besides using IO::Select and non-blocking IO? Thanks, sonofason
  • Comment on How to do simultaneous reads and writes to/from a socket?

Replies are listed 'Best First'.
Re: How to do simultaneous reads and writes to/from a socket?
by merlyn (Sage) on May 10, 2006 at 13:05 UTC
    Rather than ithreads, consider an event system like POE. You can attach events to "line ready to read", and when the event triggers, you're the only one running so you can access your shared data easily without having to "lock" or "block".

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

      jesuashok: Perhaps I should have mentioned earlier that I am working under Windows. Apparently, doing non-blocking socket IO under Windows is a little problematic, as discussed here. The polling solution does work well though and hopefully won't consume too much resources if I sleep for a short time (since I want to act immediately on incoming data) in between polls.

      merlyn: I will take a look at POE and see how it can help me.

      MonkE: I do understand that much more is needed for the server, but the code I posted was just a simplified exerpt from the client program. Thanks though!

      Many thanks!
      sonofason

Re: How to do simultaneous reads and writes to/from a socket?
by marto (Cardinal) on May 10, 2006 at 10:04 UTC
    Hi sonofason,

    I notice that this if your first post. Welcome to the Monastery.
    To echo some of what jesuashok has advised, post your code so that people can point out where things are going wrong, or could be improved. Also have a read at the PerlMonks FAQ and How do I post a question effectively? if you have not already done so.

    Thanks

    Martin
      Thanks for the replies. I'll try your suggestions. In the meantime, here is some example code illustrating what I am doing:
      use strict; use threads; use threads::shared; use IO::Socket; my $keep_running : shared = 1; my $socket = IO::Socket::INET->new(PeerAddr => "127.0.0.1", PeerPort = +> 8080, Type => SOCK_STREAM, Proto => 'tcp'); my $sender_thread = threads->new(\&do_send, $socket); my $listener_thread = threads->new(\&do_listen, $socket); $sender_thread->detach(); $listener_thread->join(); $socket->close(); sub do_listen { my $sock = shift; STDOUT->autoflush(1); while ($keep_running) { my $message = $sock->getline(); last unless defined($message); print $message; } } sub do_send { my $sock = shift; while (my $line = <STDIN>) { $sock->print($line); $sock->flush(); } $keep_running = 0; }
Re: How to do simultaneous reads and writes to/from a socket?
by jesuashok (Curate) on May 10, 2006 at 09:57 UTC
    Hi

    If you have opened the socket in Block mode then make it as O_NONBLOCK mode open.
    Also, you can use Poll
    If you need more information on this, post your code also, so that it will be easier for us to help.

    "Keep pouring your ideas"
Re: How to do simultaneous reads and writes to/from a socket?
by MonkE (Hermit) on May 10, 2006 at 13:33 UTC
    You need to understand that one socket is needed to listen for connections, and another different socket is needed for each active client. So ultimately you will need to keep a list of what clients are connected. I modifed your example code to spawn one thread per client. It now has a separate listener thread also. What the code below still lacks is: tracking of client connections (a must-have for a chat program); sending output to all active clients; and many other niceities such as proper naming of clients, etc. Hopefully this will get you started though.
    use strict; use threads; use threads::shared; use IO::Socket; my $keep_running : shared = 1; my $sender_thread = threads->new(\&do_send); my $listener_thread = threads->new(\&do_listen); $sender_thread->detach(); $listener_thread->join(); sub do_listen { my $listener_sock = IO::Socket::INET->new(LocalHost => "127.0. +0.1", LocalPort => 8080, Type => SOCK_STREAM, Proto => 'tcp', Listen => 1); my $client_sock; my $ClientNumber = 0; STDOUT->autoflush(1); print "Starting listener\n"; while ($keep_running) { my $client_sock = $listener_sock->accept(); my $reader_thread = threads->new(\&do_read, $client_so +ck, $ClientNumber); last unless defined($client_sock); $ClientNumber++; $reader_thread->detach(); print "Accepted a connection\n"; } print "Listener stopped\n"; } sub do_read { my $sock = shift; my $ClientNumber = shift; while (<$sock>) { print $ClientNumber . ":" . $_; } print "Client " . $ClientNumber . " disconnected\n"; } sub do_send { # any messages that the local user types while (my $line = <STDIN>) { # TODO: you need to iterate through all active clients and sen +d them # , not just one # $sock->print($line); # $sock->flush(); # patch for development sleep 1; } $keep_running = 0; }
    I tested it myself with two clients and got this output:
    Starting listener Accepted a connection 0:hi Accepted a connection 1:hi 0:Yo #1 1:Whaddup #0!
Re: How to do simultaneous reads and writes to/from a socket?
by BrowserUk (Patriarch) on May 11, 2006 at 03:30 UTC

    This code is not intended as a good example of using threads, but is intended to show how to do bi-directional communications via a single socket under Win32.

    It looks a little complicated because it is both client and server:

    #! perl -slw use strict; use threads; use IO::Socket; $| = 1; ## The server. async { ## Create the listening port my $server = IO::Socket::INET->new( LocalHost => 'localhost', LocalPort => 9999, Listen => 5, Reuse => 1, ) or die $!; ## Accept connections while( my $client = $server->accept ) { ## Set the socket non-blocking ioctl( $client, 0x8004667e, \1 ) or die $^E; ## Start a thread to receive and display inbound packets async { my $in = ''; while( 1 ) { ## Use to accumulate whatever is available sysread( $client, $in, 100, length( $in ) ); ## When we've accumulated a complete line if( my $p = 1 + index $in, "\n" ) { ## display it and remove it from the buffer printf 'S: %s', substr $in, 0, $p+1, ''; } ## Stop the non-blocking read from running away with t +he cpu Win32::Sleep 100; } }; ## Start another thread to simulate input from other clients async { print $client 'Some text' while sleep 1; }; } }; ## The client ## Connect to the server my $server = IO::Socket::INET->new( 'localhost:9999' ) or die $!; ## Set the socket non-blocking ioctl( $server, 0x8004667e, \1 ); ## Start a thread to recieve and display input from the server async { my $in = ''; while( 1 ) { ## Accumulate input and display when we've got a full line sysread( $server, $in, 100, length( $in ) ); if( my $p = 1 + index $in, "\n" ) { printf 'C: %s', substr $in, 0, $p+1, ''; } Win32::Sleep 100; } }; ## Give the threads a chance to start sleep 2; ## The main thread reads from the keyboard and sends to the server. while( <STDIN> ) { print $server $_; }

    NOTE: No 'select' in sight.


    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.
      the codes have not worked for me . but as i see it's just a server that excepts more then 1 client. am i wrong?
        the codes have not worked for me .

        Oh? How can you tell? What errors did you see?

        but as i see it's just a server that excepts more then 1 client. am i wrong?

        Well actually, it is both client and server all in a single program. This bit (as suggested by the comment) is a multi-threaded, asynchronous, bi-directional server:

        ## The server. async { ## Create the listening port my $server = IO::Socket::INET->new( LocalHost => 'localhost', LocalPort => 9999, Listen => 5, Reuse => 1, ) or die $!; ## Accept connections while( my $client = $server->accept ) { ## Set the socket non-blocking ioctl( $client, 0x8004667e, \1 ) or die $^E; ## Start a thread to receive and display inbound packets async { my $in = ''; while( 1 ) { ## Use to accumulate whatever is available sysread( $client, $in, 100, length( $in ) ); ## When we've accumulated a complete line if( my $p = 1 + index $in, "\n" ) { ## display it and remove it from the buffer printf 'S: %s', substr $in, 0, $p+1, ''; } ## Stop the non-blocking read from running away with t +he cpu Win32::Sleep 100; } }; ## Start another thread to simulate input from other clients async { print $client 'Some text' while sleep 1; }; } };

        And this bit (again, the comment), is a non-blocking bi-directional client with asynchronous keyboard input.

        ## The client ## Connect to the server my $server = IO::Socket::INET->new( 'localhost:9999' ) or die $!; ## Set the socket non-blocking ioctl( $server, 0x8004667e, \1 ); ## Start a thread to recieve and display input from the server async { my $in = ''; while( 1 ) { ## Accumulate input and display when we've got a full line sysread( $server, $in, 100, length( $in ) ); if( my $p = 1 + index $in, "\n" ) { printf 'C: %s', substr $in, 0, $p+1, ''; } Win32::Sleep 100; } }; ## Give the threads a chance to start sleep 2; ## The main thread reads from the keyboard and sends to the server. while( <STDIN> ) { print $server $_; }

        The point of the code is to demonstrate how to achieve asynchronous, bi-directional communications via a single socket. You are then supposed to take that code and use it to understand the way it works and then adapt it to your particular requirements. So far, as far as I have seen in your last two threads, you haven't yet even defined what your requirements are. That's often a sign that you aren't really sure what it is that you want in the first place.

        Several people posted code in you other thread and you seemed to just say that it didn't work for you. Given that I know the quality of the code posted by guys like zentara and others, I know that they don't post non-working code, so the question is why doesn't it work for you?

        For example. Are you looking to allow just two people to talk to each other point to point, or are you looking for something more elaborate?

        So, until you define more clearly what kind of chat program you are trying to write, and demonstrate some propensity to trying to understand the posted code and adapt it to your requirements, it looks very much to me like you are hoping someone will just write the code for you. And if that is the case, why not just download one of the dozens of free IM and IRC programs available and use that?


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.