I have a daemon process that accepts connections, reads a JSON request via a single sysread, processes it, and sends back a JSON response via syswrite. This works fine. But I wanted to make the reading more robust in case of partial reads. So I used the common technique I see folks use of looping until 0 bytes are read, while specifying to append the input to the end of the buffer. When I do this simple loop, the following syswrite claims it wrote out X bytes, but the caller thinks it read back 0 bytes. It appears that doing one than 1 sysread on a socket will cause a future syswrite on that socket to fail.
ie: I had changed my single sysread to do this instead:
my $done = 0 ;
while ( ! $done ) {
$bytes = sysread( $client, $json_text, BUFSIZ, length( $json_text )) ;
$done++ if ( $bytes == 0 ) ;
}
I have verified that in each case, the request that I receive is the same, and the response that I am sending is the same. The only thing that changed is the additional sysread that read 0 bytes to indicate that I had read everything available.
I have attached a simple stripped down program that illustrates this. Can someone explain to me what is happening here? I've also tried specifically telling syswrite the number of bytes to write and the starting position (0) with the same effect.
#!/usr/bin/env perl
use Socket ;
use JSON ;
use constant BUFSIZ => 1024 ;
my $protocol = getprotobyname( 'tcp' ) ;
my $server ;
if ( ! socket( $server, AF_INET, SOCK_STREAM, $protocol )) {
die( "socket() failed: $!" ) ;
}
if ( ! setsockopt( $server, SOL_SOCKET, SO_REUSEADDR, 1 )) {
die( "setsockopt(): failed: $!" ) ;
}
my $my_addr = sockaddr_in( 2007, INADDR_ANY ) ;
bind( $server, $my_addr ) or die( "bind(): failed: $!" ) ;
listen( $server, SOMAXCONN ) or die( "listen(): failed: $!" ) ;
my ( $remote_addr, $client ) ;
$remote_addr = accept( $client, $server ) ;
$_ = select( $client ); $| = 1; select $_ ; # autoflushing
my $json_text = "" ;
my $bytes = 0 ;
# a later syswrite() doesn't work when sysread() called more than once
my $done = 0 ;
while ( ! $done ) {
$bytes = sysread( $client, $json_text, BUFSIZ, length( $json_text )) ;
$done++ if ( $bytes == 0 ) ;
}
# calling sysread once works ok with following syswrite
# sysread( $client, $json_text, BUFSIZ, length( $json_text )) ;
print "Got a JSON request: $json_text\n" ;
# just make up some dummy response to whatever we received
my $out_struct = {
'id' => 'server6-alive-test',
'output' => [ 'FOOBAR' ],
'errors' => [],
'status' => 0,
'served-by' => 'server6',
};
$json_text = to_json( $out_struct, { utf8 => 1, pretty => 1 } ) ;
my $num = syswrite( $client, $json_text ) ;
print "wrote $num bytes to server\n" ;
and this debug output is from the callers perspective:
# NO while loop
debug: main::set_up_systems: testing if server6 is alive
debug: main::send_cmd_to_daemon: sending command 'alive' to remote
debug: main::check_if_system_alive: Got: 'FOOBAR'
# with while loop version
debug: main::set_up_systems: testing if server6 is alive
debug: main::send_cmd_to_daemon: sending command 'alive' to remote
debug: main::send_cmd_to_daemon: empty JSON string returned
debug: main::process_cmd: server6: No output sent back.
You say the syswrite
in the deamon failed, but you also say it returned a positive number. This would indicate it didn't fail. syswrite
returns undef
on error.
The sysread
loop in the daemon reads until it encounters EOF, which is when sysread
returns 0
. I am guessing the client signaled EOF by closing the entire socket, causing the sysread
in the client to fail (return undef
with $!
set to EBADF
(Bad file descriptor)). Instead, only shut down the writer half using
use Socket qw( SHUT_WR );
shutdown($sock, SHUT_WR)
or die($!);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With