最近有一个项目准备用多播来做心跳. 网上转了一篇ruby多播的文章.
It’s a bit hard to dig up, but I did figure out how to do UDP packet multicasting in Ruby.
I’ve been playing around with some network peer to peer discovery techniques and wanted to try some of them out in Ruby. The trick to self discovery is the ability to send network packets without knowing the address of the receiver. To do this, you either have to broadcast or multicast.
Because broadcasted packets are received by every device on the local network, it is generally considered good manners to not use broadcasted packets.
Multicasting, on the other hand, is only received by hosts that explicitly express an interest in the multicast address. Although abuse of multicasting is still bad manners, it is a better option than broadcasting.
Sending Multicast Packets
The IP addresses 224.0.0.0 through 239.255.255.255 are reserved for multicast messages. Simply sending a UDP messsage to an address in that range is sufficient.
One minor refinement is to set the TTL (Time To Live) option on the socket. My understanding is that this controls how far the packet is propagated. As polite net-citizens, we don’t want our multicast packets travelling too far abroad, so we set the TTL value to 1 (we need the ugly packing stuff because the value is passed directly to the C level setsockopt() function with no interpretation by Ruby).
So the Ruby code to send a multicast message is:
MULTICAST_ADDR = "225.4.5.6"
socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_TTL, [1].pack('i'))
socket.send(ARGV.join(' '), 0, MULTICAST_ADDR, PORT)
Receiving Multicast Messages
Receiving a multicast message is a bit tricker. Since multicast messages are generally ignored unless someone has explicitly registered an interest in a particular address, there is a bit of setup that needs to be done.
Here’s the code for receiving multicast messages:
MULTICAST_ADDR = "225.4.5.6"
ip = IPAddr.new(MULTICAST_ADDR).hton + IPAddr.new("0.0.0.0").hton
sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, ip)
sock.bind(Socket::INADDR_ANY, PORT)
msg, info = sock.recvfrom(1024)
puts "MSG: #{msg} from #{info[2]} (#{info[3]})/#{info[1]} len #{msg.size}"
The tricky part was figuring out the right setsockopt options and values needed to register interest in our multicast address. I had to do a little reading in the Unix man pages on the C level setsockopt() function call. The third option to the C function is a structure that contains two 4-byte IP addresses. The first IP address is the multicast address, and the second IP address is the address of the local host adapter that we wish to use to listen for the multicast. The 0.0.0.0 address means use any of the local network adapters. IPAddr handles parsing the human readable form of the IP address and returns a string of 4 bytes in the order needed by the C level setsockopt() function.
Save the above code in files named send.rb and rcv.rb. In one console window, type:
In another console window on the same or different machine (on the same local network), type:
ruby send.rb This is a test.
For more fun, bring up several receive windows and all will receive the messages send by the send script.
I can think of all kinds of fun things to do with this.
I added the Time To Live option on send.
To set TTL for multicast packets you need to use IP_MULTICAST_TTL not IP_TTL. Also, setting IP_MULTICAST_LOOP to 0 will prevent the sender from seeing it's own packets.