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

Greetings, monks! I'm new at Perl and network programming using sockets. I've run into a problem that I can't quite make sense of regarding treating sockets as filehandles on Windows XP Pro. This has bothered me so much that tinkering around with Perl has begun to lose some of its fun! Please shed some light on this problem if you can; so far everything I've gotten stuck on with Perl has had a solution that made every bit of sense. Here goes a description of my troubles:

The client and server code below are from http://perldoc.perl.org/perlipc.html#Internet-TCP-Clients-and-Servers. The server code has been modified to echo the client's selection back over the connection instead of calling external programs.

When both the client and server are run using active state perl 5.8.8 from winxp pro S2's command shell, the client hangs after a command is entered; when CTRL-C is entered from the client, any "excess" commands entered during the hung state are entered to the windows shell and executed after client termination.
Windows shell => Linux and Solaris also fails as described above.

successes?:
- I can connect to a running server via telnet in windows
- The client doesn't hang (and seems to work fine) when i use a linux system (perl 5.8.8)
- I also have success talking between perl 5.8.5 on a solaris system and perl 5.8.8 on a linux system
- I also have success connecting from linux/solaris => windows

client:
=======

#!/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; # 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; } }

server:
=======

#!/usr/bin/perl -w use IO::Socket; use Net::hostent; # for OO version of gethostbyaddr $PORT = 9000; # pick something not in use $server = IO::Socket::INET->new( Proto => 'tcp', LocalPort => $PORT, Listen => SOMAXCONN, Reuse => 1); die "can't setup server" unless $server; print "[Server $0 accepting clients]\n"; while ($client = $server->accept()) { $client->autoflush(1); print $client "Welcome to $0; type help for command list.\n"; $hostinfo = gethostbyaddr($client->peeraddr); printf "[Connect from %s]\n", $hostinfo ? $hostinfo->name : $client +->peerhost; print $client "Command? "; while ( <$client>) { next unless /\S/; # blank line if (/quit|exit/i) { last; + } elsif (/date|time/i) { print $client "SELECTED: date/time\n" +; } elsif (/who/i ) { print $client "SELECTED: who\n"; + } elsif (/cookie/i ) { print $client "SELECTED: cookie\n"; + } elsif (/motd/i ) { print $client "SELECTED: motd\n"; + } else { print $client "Commands: quit date who cookie motd\n"; } } continue { print $client "Command? "; } close $client; }

Replies are listed 'Best First'.
Re: Windows TCP socket client hangs in perlipc code
by BrowserUk (Patriarch) on Oct 19, 2007 at 23:29 UTC

    Your client uses fork which on Win32 is only an emulation that uses threads. The result is that instead of having two separate processes, you have a single process with two threads.

    The means that the 'child' thread is in a wait state trying to read from the tcp port. At the same time, the 'parent' thread is trying to write to the same port. This doesn't work, at least not on Win32--YMMV on other platforms. The print will block until the port exits the read wait state, which it won't do until the server sends something. But the server won't send anything until the client succeeds in printing something.

    The result is a classic deadlock.

    The code was probably written for use on a POSIX platform where a real fork (and signals and...) are available from the OS. Such code does not port directly to Win32.


    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.
Re: Windows TCP socket client hangs in perlipc code
by liverpole (Monsignor) on Oct 19, 2007 at 23:45 UTC
    Hi desiv,

    BrowserUK's answer is a good one.

    If you really want to do this on Windows, you're not far off.  Just don't do the fork, but make sure that for every line written from the server, there's a corresponding read in the client code.  Also, you need to terminate each to/from the socket with a newline "\n".

    Here's how I modified your server code:

    #!/usr/bin/perl -w use IO::Socket; use Net::hostent; # for OO version of gethostbyaddr $PORT = 9000; # pick something not in use $server = IO::Socket::INET->new( Proto => 'tcp', LocalPort => $PORT, Listen => SOMAXCONN, Reuse => 1); die "can't setup server" unless $server; print "[Server $0 accepting clients]\n"; while ($client = $server->accept()) { $client->autoflush(1); print $client "Welcome to $0; type help for command list.\n"; $hostinfo = gethostbyaddr($client->peeraddr); my $response = $hostinfo ? $hostinfo->name : $client->peerhost; printf "[Connect from %s]\n", $response; print $client "Command?\n"; while ( <$client>) { next unless /\S/; # blank line if (/quit|exit/i) { last; + } elsif (/date|time/i) { print $client "SELECTED: date/time\n" +; } elsif (/who/i ) { print $client "SELECTED: who\n"; + } elsif (/cookie/i ) { print $client "SELECTED: cookie\n"; + } elsif (/motd/i ) { print $client "SELECTED: motd\n"; + } else { print $client "Commands: quit date who cookie motd\n"; } } continue { print $client "Command?\n"; } close $client; }
    And in conjunction with the following, modified client code, they work together well:
    #!/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; # 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"; # Get/display handshake message from server my $handshake = <$handle>; print $handshake; # Socket loop while (1) { my $servermsg = <$handle>; print $servermsg; while (1) { $line = <STDIN>; last if defined($line); } print $handle $line; }

    s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/

      Thank you both for your great replies! Unfortunately, I did run into some strange behavior with the socket loop in the client code you provided, liverpole. The first command I enter immediately echoes back a response from the server, but the next command will take two enter presses to have the response echoed back. Generally speaking, the nth command takes n enter presses to get an echo back.

      I'm not sure how to analyze this one. Any ideas?

        Yes, I see what you mean.  There are a couple of reasons for this:
        1. You don't need the line "next unless /\S/;"
        2. You don't need the "continue" clause
        3. You need to figure out how to send more than 1 line at a time from the server

        This last is very important, because when you write client/server code, you eventually need a reliable mechanism for distinguishing between "Here's another line of text (and there will be more)", vs. "Here's the last line of text in my response".

        My recommendation for handling this is to choose between two characters at the beginning of the line, one which means "last line", and one which means "not last line".  Then the client code can just trim off the first character and inspect it, and continue to fetch lines from the server until the last line is fetched.

        For example, use "-" to mean more text is coming, and "+" to mean that this is the last line.

        Then your server code might look like this:

        #!/usr/bin/perl -w use IO::Socket; use Net::hostent; # for OO version of gethostbyaddr $PORT = 9000; # pick something not in use $server = IO::Socket::INET->new( Proto => 'tcp', LocalPort => $PORT, Listen => SOMAXCONN, Reuse => 1); die "can't setup server" unless $server; print "[Server $0 accepting clients]\n"; while ($client = $server->accept()) { $client->autoflush(1); print $client "-Welcome to $0; type help for command list.\n"; $hostinfo = gethostbyaddr($client->peeraddr); my $response = $hostinfo ? $hostinfo->name : $client->peerhost; printf "[Connect from %s]\n", $response; print $client "+Command?\n"; while (<$client>) { if (/quit|exit/i) { last; + } elsif (/date|time/i) { print $client "-SELECTED: date/time\n +"; } elsif (/who/i ) { print $client "-SELECTED: who\n"; + } elsif (/cookie/i ) { print $client "-SELECTED: cookie\n"; + } elsif (/motd/i ) { print $client "-SELECTED: motd\n"; + } else { print $client "-Commands: quit date who cookie motd\n"; } print $client "+Command?\n"; } close $client; }

        and your client code like this:

        #!/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; # 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"; # Get/display handshake message from server my $phandshake = get_from_server($handle); map { print $_, "\n" } @$phandshake; # Socket loop while (1) { # Get user input and send it to the server $line = <STDIN>; print $handle $line; # Get and display server response my $plines = get_from_server($handle); map { print "$_\n" } @$plines; } sub get_from_server { my $handle = shift; my @lines; while (1) { my $line = <$handle>; defined($line) or die "Server closed connection\n"; chomp $line; ($line =~ s/^([-+])//) or die "Invalid server response: '$lin +e'\n"; my $char = $1; push @lines, $line; last if ($char eq '+'); } return [ @lines ]; }

        Note that in the client code I've abstracted out the details of parsing the text from the server into a subroutine get_from_server, which takes a single argument (the server handle), reads lines from the server until it gets the last line ('+' as the first character), or the server closes the connection.  Finally, it returns a pointer to the list of lines.  This makes it easier to deal with in the main program section of the client; you only have to do:

        my $plines = get_from_server($handle); map { print "$_\n" } @$plines;

        s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/