in reply to Re: Windows TCP socket client hangs in perlipc code
in thread Windows TCP socket client hangs in perlipc code

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?

  • Comment on Re^2: Windows TCP socket client hangs in perlipc code

Replies are listed 'Best First'.
Re^3: Windows TCP socket client hangs in perlipc code
by liverpole (Monsignor) on Oct 20, 2007 at 13:39 UTC
    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$..$/