Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

IO::Socket::INET -- jettero verses non-blocking in windows

by jettero (Monsignor)
on Jul 30, 2004 at 13:26 UTC ( [id://378675]=perlquestion: print w/replies, xml ) Need Help??

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

My question is almost identical to the question asked in node #292856 -- but still slightly different. I wish to build a non-blocking socket. Apparently you can't in ActivePerl 5.8.3?

I refuse to accept that. I have tried these two methods (which both work great at home in my lunix):

$ioioio = new IO::Socket::INET( Proto => "udp", LocalPort => $port, Blocking => 0, ) or die "socket is dumb: $!"

That returns the unhelpful error "socket is dumb: at line something something."

I seem to get an error from this at least -- assuming I skip the "Blocking => 0" above.

fcntl($ioioio, F_SETFL, O_NONBLOCK);

It reports that my vendor has not defined F_SETFL.

(Sorry in advance, I searched a lot and didn't find much, but I can't imagine this hasn't been asked before.)

Replies are listed 'Best First'.
Re: IO::Socket::INET -- Jettero verses non-blocking in windows
by tachyon (Chancellor) on Jul 30, 2004 at 15:07 UTC

    You can work around it with a select loop. The trick is that although the socket is still blocking it only *actually blocks* if you *actually try to read from it when there is no data*. By using vec and the 4 arg select as shown you can check for data before you go for a blocking read. If there is actual data there it won't actually block, so it is *effectively non blocking* rather than actually non blocking.

    Here is a sample server. Note that because there is vitually no delay between the socket being readable and actually reading it you will only get 1 byte of data as sysread is not buffering. You can do this with the IO::Select can_read() method if you want the OO interface.

    use Socket; $host = pack('C4', 127,0,0,1); $port = 8888; $proto = 6; # TCP $queueSize = 5; # Queue up to 5 connections $pollTime = 0.5; # polling time $delay = 0.5; # slow the select loop down for example socket( SOCK, AF_INET, SOCK_STREAM, $proto ); $address = pack('S n a4 x8', AF_INET, $port, $host); bind(SOCK, $address); listen(SOCK, $queueSize); print STDOUT "Server host: ",join('.',unpack('C4', $host)),"\n"; print STDOUT "Server port: $port\n"; $cAddress = accept(NEWSOCK,SOCK); ($cDomain, $cPort, $cHost) = unpack('S n a4 x8', $cAddress); print STDOUT "Client host: ",join('.',unpack('C4', $cHost)),"\n"; print STDOUT "Client port: $cPort\n"; select(NEWSOCK); $| = 1; select(STDOUT); print NEWSOCK "Welcome to Reverse Echo Server.\r\n"; vec($bits1,fileno(NEWSOCK),1)=1; while(1) { $rc=select($rout1=$bits1,$wout1=$bits1,$eout1=$bits1,$pollTime); +# poll print "$rc=select($rout1,$wout1,$eout1)\n"; if ( vec($rout1,fileno(NEWSOCK),1) ) { sysread( NEWSOCK, $buf, 1 ); print "Got $buf\n"; } select(undef,undef,undef,$delay); # this is not a select, it is a + sleep! } close(NEWSOCK); close(SOCK); exit; __DATA__ # telnet localhost 8888 Welcome to Reverse Echo Server. Hello # server C:\>server.pl Server host: 127.0.0.1 Server port: 8888 Client host: 127.0.0.1 Client port: 3432 1=select( ,?, ) 1=select( ,?, ) 1=select( ,?, ) Got H 2=select(?,?, ) Got e 2=select(?,?, ) Got l 2=select(?,?, ) Got l 2=select(?,?, ) Got o 1=select( ,?, ) 1=select( ,?, ) 1=select( ,?, )

    cheers

    tachyon

      I am apparently in way over my head...

      I'm trying to just make a separate function for OSen without non-blocking sockets.

      In my cool version, I'm doing this:

      my $msg = ""; while( my $portaddr = recv($this->{in}, $msg, 1024, 0) ) { my ($portno, $ipaddr) = sockaddr_in($portaddr); my $ip = inet_ntoa($ipaddr); push @msgs, [$ip, $this->_transform($msg)]; }

      It's intercepting UDP broadcasts, which I'm also new to, and I'm not sure where to get the ipaddr of the source using the vec/select while loop -- which is fascinating btw.

        What vec() does is construct a bit vector, in this case related to the fileno of the client socket. Every filehandle on the OS has an actual file number. Consider:

        print fileno(STDIN), $", fileno(STDOUT), $", fileno(STDERR); __DATA__ 0 1 2

        So STDIN is fileno 0, etc. When you open a file or a socket a unique fileno that identifies it is stored by the OS in the file table. So what the first vec is doing is generating a bitmask in $bits1 that represents that. What 4 arg select does is return values for handles that can be READ, WRITTEN and HAVE ERRORS. It waits the specified time and then returns. It returns the data in a bitmap type format. We use vec again to see if the bit that corresponds to the filehandle we are interested in has been set in the returned value. (vec returns true if it is set, false if not).

        I don't know what you mean about how you get the ip address of the source - it is there in the code and being printed?

        cheers

        tachyon

      I spent a lot of time dealing with pretty much the same problem when trying to write a module that would be able to handle multiple irc connections simultaneously. A number of suggestions were made on how to do this here. I personally found the threading method easier to work with and "cleaner" from a coding point of view than using a select loop. Even though ithreads have a number of flaws, one of them being the amount of memory they guzzle up, it still gets the job done quite well. Threads take a while to get used to, but when used properly allow for a style of coding you never might have thought possible.

      Edit: a little more info.

      Perhaps it would be wise to actually elaborate on WHY I prefer threading over a select loop....silly me

      With a select loop, from my experience, you have to carefully decide just how often you do the check. The last parameter for select() is a timeout, and if you don't set it to a value you'll be back at square 1, since then select itself will block when no data is available. However, if you're going to check for data every millisecond you'll be spending lots and lots of cpu time, which is something I try to avoid like the plague. Another downside i ran across(there might be solutions to this, in which case I just didn't happen to find one) was that in order to effectively use the select loop, I had to read from the socket 1 byte at a time, glue these bytes to a string until I ran across a "\n", after which I sent the whole line out for processing. Using threading, you can simply have the thread do it's own little thing, reading in data using <> or readline(), and if for some reason the socket dies, be it by accident or intentionally, you can take appropriate action within the actual thread without disturbing the functionality of the main thread where the actual action happens. At some point during testing I had 10 irc connections up and running, all being fed regular data and CPU usage was, well, negligible. Memory use was a different story, but there are certain modules to bring this down a little on CPAN. Take a look at some of the modules starting with Thread:: if you feel you're willing to take a shot at it :)

        I did end up using threads actually.

        In the case that I can't open a non-blocking listener, I make a thread and share the messages in an array. It worked great and I can get the IP from the recv().

        Really, the best part is, this is the first time I've every needed to use a thread and now that I know how, it's like there's a whole new world out there.

        And for posterity, here is my original non-blocking loop along with my new magical threaded version.

        sub listen { my $this = shift; if( $this->{blocking} ) { if( not $this->{blocking_thread} ) { $this->{blocking_thread} = new threads( \&_blocking_listen +, $this ); } my @msgs = map([ split /^G^G/, $_ ], @global_shared_messages); @global_shared_messages = (); return @msgs; } my @msgs = (); my $msg = ""; while( my $portaddr = recv($this->{in}, $msg, 1024*1024, 0) ) { my ($portno, $ipaddr) = sockaddr_in($portaddr); my $ip = inet_ntoa($ipaddr); push @msgs, [$ip, $this->_transform($msg)]; } return @msgs; } sub _blocking_listen { my $this = shift; { my $msg = ""; while( my $portaddr = recv($this->{in}, $msg, 1024*1024, 0) ) +{ my ($portno, $ipaddr) = sockaddr_in($portaddr); my $ip = inet_ntoa($ipaddr); push @global_shared_messages, "$ip^G^G" . $this->_transfor +m($msg); } redo; # we stay in here forever, btw. } }
Re: IO::Socket::INET -- Jettero verses non-blocking in windows
by tachyon (Chancellor) on Jul 30, 2004 at 13:43 UTC
      If I read that right, it's not even a bug, it's just something you have to live with in windows?

      Is there a work around?

Re: IO::Socket::INET -- Jettero verses non-blocking in windows
by Anonymous Monk on Aug 02, 2004 at 08:52 UTC
    Hello, I've worked around this using:
    my $true=1; ioctl($ioioio, 0x8004667e, \$true);
    And then reading data with
    sysread()
    rather than <>. I haven't messed around with select() because this seems to work, although your mileage may vary.(Also, for the record, the IO::Handle::blocking() method suggested in #292856 fails but returns an empty $!.) Hope this helps, -jc

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://378675]
Approved by jdporter
Front-paged by BrowserUk
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others contemplating the Monastery: (4)
As of 2024-04-25 13:11 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found