Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling
 
PerlMonks  

Re: IO::Socket::INET -- Jettero verses non-blocking in windows

by tachyon (Chancellor)
on Jul 30, 2004 at 15:07 UTC ( [id://378711]=note: print w/replies, xml ) Need Help??


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

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

Replies are listed 'Best First'.
Re^2: IO::Socket::INET -- Jettero verses non-blocking in windows
by jettero (Monsignor) on Jul 30, 2004 at 15:27 UTC
    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

        Your method really does work, so I'm closer. I just don't know how to find the sender IP. Thanks a lot BTW.

        But, what I was actually asking is about how to find the sender IP. Check out the recv() I posted above.

        UDP doesn't really connect, so the source IP on each packet can be different. recv() returns some packet header info from which I can get the ip addr of the sender.

        Sysread doesn't do that at all. And the worse part is, the perldoc page for select() tells me that mixing your vec/select call with recv() is stupid and I'll get burnt.

        So I'm not sure what to do ...

Re^2: IO::Socket::INET -- Jettero verses non-blocking in windows
by Forsaken (Friar) on Jul 31, 2004 at 07:36 UTC
    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. } }

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://378711]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others avoiding work at the Monastery: (5)
As of 2024-04-19 22:50 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found