http://www.perlmonks.org?node_id=622075

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

I'm stuck with some code which creates a UDP datagram socket and broadcasts a message, a server process picks up the broadcast and attempts to send a reply but the client which sent the broadcast does not see it. Any ideas? The client is something like this:
my $socket = IO::Socket::INET->new( Broadcast => 1, Blocking => 1, ReuseAddr => 1, Type => SOCK_DGRAM, Proto => 'udp', PeerPort => 9999, LocalPort => 0, PeerAddr => inet_ntoa(INADDR_BROADCAST)) || confess "error: failed to create broadcast udp socket - $!"; $socket->send('Q', 0); my ($srcaddr, $data); $srcaddr = $socket->recv($data, 100, 0); die "recv: $!" if !defined($srcaddr); close $socket;
and the server is like this:
my $socket = IO::Socket::INET->new( Proto => 'udp', Type => SOCK_DGRAM, LocalPort => 9999) || confess "error: failed to create broadcast udp socket - $!"; # server now does select etc to wait for msg my $ip = $socket->recv(my $data, 100); my ($port, $ipaddr) = sockaddr_in($socket->peername); my $hishost = gethostbyaddr($ipaddr, AF_INET); print "Client $hishost/$port said $data\n"; print "sent " . $scoket->send('Refresh', 0, $ip) . "\n";
and my server outputs: Client xxx.yyy.local/47917 said Q but the client just hangs and receives nothing. This is Linux if it makes any difference and perl 5.8.8.

Replies are listed 'Best First'.
Re: How to answer a UDP broadcast
by Trizor (Pilgrim) on Jun 19, 2007 at 19:32 UTC

    Datagram sockets aren't two way. Are you sure you're stuck with UDP? Its not good for 2 way traffic, because to do the opposite of what you just did (sending or recieving) you need to open a new Socket. You also cannot listen on a broadcast socket.

    My advise would be to use TCP unless there are multipule things that need to listen to the broadcast. TCP was meant for 2 way confirmed communication, and you will find that your life of network communication will be made much easier.

    Also as a matter of style mixing confess and die and ways of checking for success (using an inline or vs assigning and then checking if it's not defined) generally makes the code more difficult to read.

      OK, further explanation required. In actual fact there are multiple servers running and they have different purposes depending on how they started. Each server is both a server and a client. The idea behind the code is that when a server starts it broadcasts to find other servers on the localnet and locates the servers serving different purposes - some of these could be on the same machine. A server processing type A requests may decide to process it itself or pass it on to a dedicated type A server (hence each server is both a server and a client wrt to the code posted). All the code is trying to do is locate services on the localnet - the only two-way communication is to broadcast to find servers and receive their primary purpose. So there are multiple "things" that need to listen for the broadcast and respond. After the servers are located TCP/IP commns is used to queue jobs on the relevant servers i.e. the broadcast is only being used to locate services - after that jobs are queued via tcpip to listening servers. The actual code is not exactly what is posted - it was a cut down version to demonstrate the problem.

        Well then part of my post stands, and that is you cannot listen on a Broadcast UDP socket, you need to open another one. My advise is to add to your protocol specification a dedicated "hello" reply port and always listen to broadcast responses on that port instead of the current set up (reply on the source port).

        Another alternative which gets my vote is to have a central tracking server that new servers register with and recieve back a list of all currently live servers. This tracker would also periodically have to ping the other servers to make sure that they are still functioning and handle goodbyes, but would prevent the issues that udp packetloss can cause (always use TCP for talking with this tracker, heck the 'ping' could just be a "hi/hello" going down the inital socket).

        I'm not 100% clear on what you want but I've got a thought that might work. If the broadcast includes the senders address, then the recieving server could initiate a TCP connection to the broadcaster in response. The sender would then only accept the first connection and refuse the others so that if multiple servers replied you would just deny their connections.

        I'm picturing 1 computer deciding it needs job A done. So it broadcasts out saying it needs a type A server. Each type A server then replys with a TCP connection. Each type A server could have a built in delay before replying, so the primary would reply immediatly and backups a random number of seconds later. In this way the client just accepts the first connection and proceeds with its needs.


        ___________
        Eric Hodges

        You might want to consider using Net::UPnP for this task. Universal Plug and Play devices broadcast their existence and capabilities in UDP, advertising existing services (like web servers) and sending status messages. It seems exactly like what you are trying to do.

        Oh, and each node broadcasts and listens on the same socket. Sorry, Trizor.


        The intelligent reader will judge for himself. Without examining the facts fully and fairly, there is no way of knowing whether vox populi is really vox dei, or merely vox asinorum. — Cyrus H. Gordon
Re: How to answer a UDP broadcast
by ikegami (Patriarch) on Jun 19, 2007 at 20:23 UTC

    A couple of notes:

    $socket->peername makes no sense when using UPD. UPD sockets aren't connected, so they don't have a peer. The address of the sender is packet-specific, which is why recv returns it. $socket->peername should be replaced with $ip.

    Proto => 'udp' and Type => SOCK_DGRAM are redundant. They both mean the same thing. Either one will do. Currently, one of them is silently being ignored.

      Point 1 taken - this was a result of experimentation and not the original code. Redundant does not make wrong but perhaps the other poster has hit the nail on the head in that you cannot listen on a broadcast udp socket - that was the info I was after.
Re: How to answer a UDP broadcast
by mje (Curate) on Jun 20, 2007 at 10:12 UTC
    Somehow I did not believe you cannot use a UDP socket to broadcast and receive responses. When I looked I had C code which does precisely this. The problem appears to be my use of IO::Socket or IO::Socket as if the client code above is replaced with the following it all works fine. The PeerAddr causes IO::Socket to do a connect so the only way I can make IO::Socket do it was to remove PeerAddr (and hence PeerPort) and change the send to use INADDR_BROADCAST.
    socket(my $socket, AF_INET, SOCK_DGRAM, getprotobyname('udp')); setsockopt($socket, SOL_SOCKET, SO_BROADCAST, 1); my $destpaddr = sockaddr_in(9990, INADDR_BROADCAST); send($socket, 'Q', 0, $destpaddr); my $wait = IO::Select->new($socket); while (my ($found) = $wait->can_read(10)) { my $srcpaddr = recv($socket, my $data, 100, 0); my ($port, $ipaddr) = sockaddr_in($srcpaddr); print "Read " . (gethostbyaddr($ipaddr, AF_INET) || "UNKNOWN") . ", + " . inet_ntoa($ipaddr) . ", ", $data, "\n"; } close $socket;
    i.e. you can open a udp socket, broadcast on it and then receive data on it sent from the processes seeing the broadcast.