spraff spraff - 2 months ago 7
Perl Question

Not all socket data sent before shutdown is received

I am writing Perl scripts to send strings between a client and server over TCP. The connection is made but data send from the client isn't received.

Full code for the server

#!/usr/bin/perl -w
use strict;
use Socket;

my $port = 8021;
my $proto = getprotobyname ('tcp');
my $server = "localhost";

socket (SOCKET, PF_INET, SOCK_STREAM, $proto)
or die "Can't open socket: $!";

bind (SOCKET, pack_sockaddr_in ($port, inet_aton ($server)))
or die "Can't bind $server:$port: $!";

listen (SOCKET, 5)
or die "Can't listen on $server:$port: $!";

my $client_addr;

while ($client_addr = accept (NEW_SOCKET, SOCKET))
{
my $name = gethostbyaddr ($client_addr, AF_INET);

while (<NEW_SOCKET>)
{
print "Recieved from client: $_<<ENDL\n";
}

print NEW_SOCKET "Smile from the server.";

print "Connection recieved from $name\n";

close NEW_SOCKET or warn "Couldn't close socket: $!";
}


Full code for the client. Look for the
SEE HERE
comment.

#!/usr/bin/perl -w
use strict;
use Socket;

my $port = 8021;
my $proto = getprotobyname ('tcp');
my $server = "localhost";

$server = $ARGV[0] if (1 == @ARGV);

socket (SOCKET, PF_INET, SOCK_STREAM, $proto)
or die "Can't create a socket: $!\n";

connect (SOCKET, pack_sockaddr_in ($port, inet_aton ($server)))
or die "Can't connect to $server:$port: $!\n";

# SEE HERE
# my $s = select (SOCKET);
# $| = 1; # make it hot
# select ($s);
print SOCKET "Test message from client to $server:$port.";

shutdown (SOCKET, 1) or warn "Couldn't shutdown socket to $server:$port.";

while (<SOCKET>)
{
print "Server $server:$port responds with: $_<<ENDL\n";
}

close SOCKET or die "Can't close socket to $server:$port: $!";


As it stands, the server outputs

Connection recieved from ANantes-256-1-226-50.w2-0.abo.wanadoo.fr


And the client outputs

Server localhost:8021 responds with: Smile from the server.<<ENDL


Clearly the client's
shutdown
command correctly causes the server's
while(<NEW_SOCKET>)
loop to exit, although the
Test message
which the client prints isn't read in that loop.

However, if I uncomment the lines below
SEE HERE
, the server prints the test message before sending its response.

Surely when you
shutdown
, all pending data is supposed to be flushed, right?


Why do I need the
$| = 1
line?

Also, can I trust the
$| = 1
line? As in, if I can't trust
shutdown
(or, presumably)
close
to not truncate messages, how can I trust
$|=1
?

Answer

This is a typical case of: Suffering from Buffering?. I recommend you to read the article if you are interested in more details.

Surely when you shutdown, all pending data is supposed to be flushed, right?

That's not the case. If you check the perldoc of shutdown, you won't find any hint about flushing. Also the man page of shutdown(3) doesn't mention this behaviour at all.

The actual problem is the following line in your client code (see code comment):

...

socket (SOCKET, PF_INET, SOCK_STREAM, $proto)
    or die "Can't create a socket: $!\n";

connect (SOCKET, pack_sockaddr_in ($port, inet_aton ($server)))
    or die "Can't connect to $server:$port: $!\n";

# Here is the problem
print SOCKET "Test message from client to $server:$port.";

shutdown (SOCKET, 1) or warn "Couldn't shutdown socket to $server:$port.";

...

The filehandle SOCKET is fully buffered, which means your data is not instantly printed to the network but gets accumulated in an internal buffer first. As soon as the internal buffer is filled, it's data will be sent. The message you try to send by print is just a few bytes long - far from filling the internal buffer to it's maximum size.

You already figured out how to modify the default buffering behaviour by using the special Perl variable $|. By changing the default value of this variable, you can make the currently active filehandle hot. That means the internal buffer will get flushed after every print to the filehandle.

Now let's see what different options you have to replace the problematic line mentioned above if you want to send the following message:

my $msg = "Test message from client to $server:$port.";

Option #1:

use FileHandle;
SOCKET->autoflush(1);
print SOCKET $msg;

You can use autoflush() from FileHandle to disable buffering of your SOCKET filehandle.

Option #2:

use IO::Handle; # or: use FileHandle
print SOCKET $msg;
SOCKET->flush();

You can also use the method flush() to manually flush after print.

Option #3:

{
    my $oldfh = select SOCKET;
    local $| = 1; # make SOCKET hot
    print SOCKET $msg;
    select $oldfh;
}

This selects the filehandle SOCKET to make it hot by setting $| to 1 in a local scope. After that your message $msg will be sent by print and the old filehandle gets restored by reselecting it. The old value of the global variable $| remains unaffected because we changed it only locally inside the { } block.

Option #4:

syswrite SOCKET, $msg;

Furthermore you can simply use syswrite() to completely bypass the internal buffering.

Option #5:

use IO::Socket::INET;
my $sock = IO::Socket::INET->new(
    PeerAddr => $server,
    PeerPort => $port,
    Proto    => "tcp"
);
$sock->send($msg);

This is simple and straightforward to use and replaces almost all of your code. According to the documentation of IO::Socket:

As of VERSION 1.18 all IO::Socket objects have autoflush turned on by default. This was not the case with earlier releases.

All of these 5 options will give you the behaviour you are looking for.