in reply to Windows TCP socket client hangs in perlipc code

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$..$/

Replies are listed 'Best First'.
Re^2: Windows TCP socket client hangs in perlipc code
by Anonymous Monk on Oct 20, 2007 at 00:46 UTC

    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$..$/