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

(In a previous, Net::Server blocks on non-blocking socket with SSL, post i talked about problems with Net::Server and SSL and was asked to provide better examples. Since it took me a while to boil down the code to the relevant parts, i'm posting this as a new question)

I have a problem with Net::Server when running in SSL mode. Non-blocking sockets block even though they shouldn't. When i run the same software in non-SSL mode, everything works correctly.

Here's my test code (in readmore-tags)

First, let's try non-SSL, here's the client:

#!/usr/bin/env perl use strict; use warnings; use IO::Socket::INET; # create a connecting socket my $peerhost = 'localhost'; my $peerport = 8000; my $socket = new IO::Socket::INET ( PeerHost => $peerhost, PeerPort => $peerport, Proto => 'tcp', ); binmode($socket, ':bytes'); $socket->blocking(0); for(my $i = 0; $i < 5; $i++) { syswrite($socket, chr($i)); sleep(2); }

And the server:

#!/usr/bin/env perl use strict; use warnings; package TestServer; use base qw(Net::Server); sub process_request { my ($self) = @_; binmode(STDIN, ':bytes'); STDIN->blocking(0); while(1) { my $buf; sysread(STDIN, $buf, 1); if(defined($buf) && length($buf)) { my $num = ord($buf); print STDERR "GOT $num\n"; last if($num == 4); } else { print STDERR "...doing something else...\n"; sleep(1); } } print STDERR "Done.\n"; } TestServer->run(port => 8000);

As expected, the server runs the "doing something else" part when there's no data arriving from the client:

2015/04/09-10:58:23 TestServer (type Net::Server) starting! pid(7799) Resolved [*]:8000 to [::]:8000, IPv6 Not including resolved host [0.0.0.0] IPv4 because it will be handled +by [::] IPv6 Binding to TCP port 8000 on host :: with IPv6 Group Not Defined. Defaulting to EGID '1000 4 20 24 27 30 46 108 124 +125 130 1000' User Not Defined. Defaulting to EUID '1000' GOT 0 ...doing something else... ...doing something else... GOT 1 ...doing something else... ...doing something else... GOT 2 ...doing something else... ...doing something else... GOT 3 ...doing something else... ...doing something else... GOT 4 Done.

So far, so good. So, let's change this to SSL. First, we need a self signed certificate:

yes '' | openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert. +pem -days 10 -nodes

Next, we modify the client to use IO::Socket::SSL instead of IO::Socket::INET:

#!/usr/bin/env perl use strict; use warnings; use IO::Socket::SSL; # create a connecting socket my $peerhost = 'localhost'; my $peerport = 8000; my $socket = new IO::Socket::SSL ( PeerHost => $peerhost, PeerPort => $peerport, Proto => 'tcp', SSL_verify_mode => SSL_VERIFY_NONE, ); binmode($socket, ':bytes'); $socket->blocking(0); for(my $i = 0; $i < 5; $i++) { syswrite($socket, chr($i)); sleep(2); }

Then we configure Net::Server to use SSL and our self-signed certs:

#!/usr/bin/env perl use strict; use warnings; package TestServer; use base qw(Net::Server); sub process_request { my ($self) = @_; binmode(STDIN, ':bytes'); STDIN->blocking(0); while(1) { my $buf; sysread(STDIN, $buf, 1); if(defined($buf) && length($buf)) { my $num = ord($buf); print STDERR "GOT $num\n"; last if($num == 4); } else { print STDERR "...doing something else...\n"; sleep(1); } } print STDERR "Done.\n"; } TestServer->run(port => 8000, proto => 'ssl', SSL_key_file => 'key.pem', SSL_cert_file => 'cert.pem');

Here's the result of this run:

2015/04/09-11:04:03 TestServer (type Net::Server) starting! pid(8551) Resolved [*]:8000 to [::]:8000, IPv6 Not including resolved host [0.0.0.0] IPv4 because it will be handled +by [::] IPv6 Binding to SSL port 8000 on host :: with IPv6 Group Not Defined. Defaulting to EGID '1000 4 20 24 27 30 46 108 124 +125 130 1000' User Not Defined. Defaulting to EUID '1000' GOT 0 GOT 1 GOT 2 GOT 3 GOT 4 Done.

The server blocks on sysread() and does not run the "doing something else" part.

Here's the list of the (in my opinion) relevant version infos:

I tried to diagnose further, but i'm stuck. What i could find out (and i'm not sure why some of that happens) is:

Any ideas?

"For me, programming in Perl is like my cooking. The result may not always taste nice, but it's quick, painless and it get's food on the table."

Replies are listed 'Best First'.
Re: Non-blocking sockets with Net::Server and SSL
by noxxi (Pilgrim) on Apr 09, 2015 at 21:06 UTC
    >     STDIN->blocking(0);
    

    That part is the problem. Net::Server tries to handle SSL in a special way but this way is not working with the code you use. The blocking(0) will not be done on the socket to the client, but instead on fd 0 which is at this moment /dev/null. Thus it will not have the desired effect.

    One way to deal with the problem is to reach into the internals of Net::Server to get access to the real file descriptor behind STDIN:

    tied(*STDIN)->[0]->blocking(0);
    
    A better way is to use the file descriptor which is given to the process_request function:
    sub process_request {
        my ($self,$fd) = @_;
        $fd->blocking(0);
    
    In both variants the real socket will be set non-blocking.

    Apart from that, note that the SSL handshake will still be done blocking by Net::Server.

      It works! It really works! You Madam/Sir/Other are my hero!

      I just tried

      my ($self,$fd) = @_; $fd->blocking(0);
      and it works!

      You really deserve a hug! I've been trying for the last ten days to fix my websocket implementation and you did it in two lines!

      Very much appreciated!

      "For me, programming in Perl is like my cooking. The result may not always taste nice, but it's quick, painless and it get's food on the table."