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

Greetings oh wise ones. I've got a boggle that I'm hoping you can help to solve. I'm building a listener to test network ACL's (long story... 6 TCP ports, 3 UDP ports, opened across two class B... QA nightmare). The script is delivered via a software delivery system and needs to listen for 15 minutes then die. TCP's no problem; UDP's a bit more difficult.

I can't use alarm on Windows systems, and the Socket timeout param doesn't seem to work on udp recv (see below). Any idea of another way to do this so that I can kill the udp listener after timeout?

sub ListenOnUDP { # Open a port my $hSocket = IO::Socket::INET->new( LocalPort=>$intListenPort, Proto=>$strProtocol ) or die "Can't create UDP socket: $@"; my ($datagram,$flags); while ($hSocket->recv($datagram,42,$flags)) { my $ripaddr = $hSocket->peerhost; my $rport = $hSocket->peerport; my $hResponseSocket = IO::Socket::INET->new( Proto=>$strProtocol, PeerHost=>$ripaddr, PeerPort=>$intSendPort ); $hResponseSocket->send("PONG!"); close($hResponseSocket); last; } close($hSocket); }
thx much. Shunyun

Replies are listed 'Best First'.
Re: Timeout Socket recv on UDP... on windows
by BrowserUk (Patriarch) on Jan 26, 2008 at 05:06 UTC
    I can't use alarm on Windows systems,

    You can:

    print time; eval{ local $SIG{ ALRM } = sub { print "time to get up", time; die}; alarm( 10 ); sleep 20; }; alarm( 0 ); print time;; 1201323436 time to get up 1201323446 1201323446 [0] Perl> print $^O, $];; MSWin32 5.008006

    Though it probably won't interupt a socket waitstate unless you disable SAFE_SIGNALS.

    For how to set a socket non-blocking of windows, do a super search for "0x8004667e".


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      IMHO... it would be preferable to at least mention the symbolic name of the constant, FIONBIO in this case, in preference to just giving the value as 0x8004667e. Otherwise, we risk giving even more Perl newbies the idea that it's cool to write code with mysterious, arbitrary large hex constants in it.

      Perl code is scary* enough to users, even without making it look like we are trying to sneak a concealed Dark Ritual Invocation of the Name of the Beast into our code.

      * Alas, "scary" is exact word my boss used yesterday when he looked at a one-liner I dashed off for him. That's bad, very bad....

        I'm not at all sure that an arbitrary collection of 7 uppercase letters is any better.

        It would only turn up 9 of the 33 posts the search term I gave turns up. And I'm not sure that 9 are relevant. The 33 definitely are. It's very unlikely that number would turn up in any other context.

        And I'm not sure that it would do anything to help them. As that symbolic constant isn't available to them from Perl.

        And I'm not sure it would help them work out the value they need either. Even if they have the relevant C header files, FIONBIO is defined there as:

        #define FIONBIO _IOW('f', 126, u_long) /* set/clear non-blocking i +/o */

        And the relevant parts of that are defined as:

        #define _IOW(x,y,t) (IOC_IN|(((long)sizeof(t)&IOCPARM_MASK)<<16)|( +(x)<<8)|(y)) #define IOCPARM_MASK 0x7f #define IOC_IN 0x80000000 /* copy in parameters */

        Now, I think I'm pretty offay with C and bit-twiddling, but I'm not sure that I could work out what the required number is from that lot.

        And I'm not at all sure that I could recreate that math in Perl.

        That's a lot of "not sures". I am sure that if I use the hex constant, it finds the posts. And works.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
      Thanks for the reply, all. I searched through the threads you referenced and saw good examples of setting non-blocking mode. I'm not sure I'm using this right, tho. If I place the statement after establishing the socket and before the while statement (while recv), the receive evaluates false and the while statement doesn't execute.

      my $hSocket = IO::Socket::INET->new( LocalPort=>$intListenPort, Proto=>$strProtocol ) or die "Can't create UDP socket: $@"; ioctl($hSocket, 0x8004667e, pack("I", 1)); my ($datagram,$flags); # Wait for a datagram, then open a response port, respond, then close both ports. while ($hSocket->recv($datagram,42,$flags)) { ...
      So, I'm wondering if this is the right approach. I think the core of the problem is that I'm waiting for incoming data to determine if a connection has been established.

      Is there another way to watch for client connection (and corresponding technique to time-out), before establishing the recv loop? For instance, doesn't IO::Socket call an accept method on connection? So couldn't I check $hSocket for a value (undef?) until timeout, and then launch the recv loop once I have a client? Or maybe using the accept method directly, like

      until ($hSocket->accept()) { ...check for timeout ...close socket and exit if timeout } while ($hSocket->recv($datagram,42,$flags)) { ...

      I guess I'm asking if blocking is really the problem or my approach to checking for connection. I think...

        Caveat: I've had little occasion to do much with udp sockets. See also Perl Cookbook (I hope that is a sanctioned link)

        However, udp is connectionless, so you do not need an accept loop. Essentially you want to wait for upto a specified number of seconds for a datagram to arrive and do something else if not. So,

        my $hSocket = IO::Socket::INET->new( LocalPort=>$intListenPort, Proto=>$strProtocol ) or die "Can't create UDP socket: $@"; ioctl($hSocket, 0x8004667e, pack("I", 1)); my( $gotone, $datagram, $flags) = 0; my $endtime = time() + $timeout; while( time() < $endtime ) { sleep 1 and next unless $hSocket->recv($datagram,42,$flags ); ## Got a datagram. ... $gotone = 1; last; } if( $gotone ) { ## We processed a datagram within the timelimit } else { ## we didn't }

        There are probably better ways of structuring that. And you could probably use select or IO::Select to achieve a similar thing.

        Or threads.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Timeout Socket recv on UDP... on windows
by pc88mxer (Vicar) on Jan 26, 2008 at 05:49 UTC
    How about using a select based approach? Something like:

    ...(set up socket)... my $end = time + 15*60; my $rin; vec($rin, fileno($hSocket), 1) = 1; while (1) { my $timeleft = $end - time; last if ($timeleft <= 0); my ($nfound, $t) = select(my $rout = $rin, undef, undef, $timeleft +); last if ($nfound == 0); # either timeout or end of file # read from socket here }
      pc88mxer, I gotta look into this more, as I'm not sure what this is doing. Is this checking for whether $hSocket is undef (see my other reply, regarding checking if accept has been called)? I'll plug this in, read up, and try. Not necessarily in that order. Thanks
        The select call will wait $timeleft seconds or until there is data available to be read on the socket, whichever occurs first. This allows you to implement non-blocking I/O.

        This raises a good point: you have a number of tcp and udp sockets. Do you need (or want) to operate them at the same time (i.e. multiplex the I/O)? If so, you can either do that with threads or also with select.

        Final point... select can also be used to determine if a socket is ready to accept an incoming connection.