4

I recently was implementing multicast into my multiplayer game to locate games running on the player's network. I created two classes, Heart and Listener. The problem I am having is that the listener only hears the heart beat through localhost, not if I'm running one part on another computer.

Heart:

package net.jibini.networking.udp;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import net.jibini.networking.packets.Packet;

public class Heart implements Runnable 
{
    private String groupName = "229.5.38.17";
    private int port = 4567;
    MulticastSocket multicastSocket;
    DatagramPacket datagramPacket;
    public boolean beating = true;
    public Packet toSend;

    public Heart(int connectionListenerPort, Packet toSend) 
    {
        try 
        {
            this.toSend = toSend;
            multicastSocket = new MulticastSocket();
            multicastSocket.setReuseAddress(true);
            InetAddress group = InetAddress.getByName(groupName);
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(toSend);
            objectOutputStream.flush();
            objectOutputStream.close();
            byte[] buf = byteArrayOutputStream.toByteArray();
            datagramPacket = new DatagramPacket(buf, buf.length, group, port);
            new Thread(this).start();
        } 
        catch (IOException e) 
        {
            e.printStackTrace();
        }
    }

    @Override
    public void run() 
    {
        while (beating) 
        {
            beat();
            try 
            {
                Thread.sleep(1000);
            }
            catch (InterruptedException e) 
            {
                e.printStackTrace();
            }
        }
    }

    private void beat() {
        try 
        {
            multicastSocket.send(datagramPacket);
        } 
        catch (IOException e) 
        {
            e.printStackTrace();
        }
    }
}

Listener:

package net.jibini.networking.udp;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import net.jibini.networking.packets.Packet;

public class Listener implements Runnable 
{
    private boolean run = true;
    private String groupName = "229.5.38.17";
    MulticastSocket multicastSocket;
    public OnFound onFound;

    public Listener(OnFound onFound) 
    {
        try 
        {
            multicastSocket = new MulticastSocket(4567);
            multicastSocket.setReuseAddress(true);
            multicastSocket.joinGroup(InetAddress.getByName(groupName));
            this.onFound = onFound;
            new Thread(this).start();
        } 
        catch (IOException e) 
        {
            e.printStackTrace();
        }
    }

    @Override
    public void run() 
    {
        while (run) 
        {
            DatagramPacket datagramPacket = new DatagramPacket(new byte[1500], 1500);
            try 
            {
                multicastSocket.receive(datagramPacket);
                Packet beat = getBeat(datagramPacket);
                if (beat != null) 
                {
                    onFound.onFound(datagramPacket.getAddress(), beat);
                }
            } 
            catch (IOException e) 
            {
                e.printStackTrace();
            }
        }
    }

    public void stop() 
    {
        run = false;
    }

    /*private boolean isLocalhost(String hostAddress) 
    {
        boolean isLocalhost = false;
        Enumeration<NetworkInterface> networkInterfaces;
        try 
        {
            networkInterfaces = NetworkInterface.getNetworkInterfaces();
            if (networkInterfaces != null) 
            {
                OUTER:
                while (networkInterfaces.hasMoreElements()) 
                {
                    NetworkInterface networkInterface = networkInterfaces.nextElement();
                    Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
                    if (inetAddresses != null) 
                    {
                        while (inetAddresses.hasMoreElements()) 
                        {
                            InetAddress inetAddress = inetAddresses.nextElement();
                            if (hostAddress.equals(inetAddress.getHostAddress())) 
                            {
                                isLocalhost = true;
                                break OUTER;
                            }
                        }
                    }
                }
            }
        } 
        catch (SocketException e) 
        {
            e.printStackTrace();
        }
        return isLocalhost;
    }*/

    private Packet getBeat(DatagramPacket datagramPacket) 
    {
        Packet beat = null;
        byte[] data = datagramPacket.getData();
        if (data != null) 
        {
            try 
            {
                ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(data));
                beat = (Packet) objectInputStream.readObject();
            } 
            catch (IOException e) 
            {
                e.printStackTrace();
            } 
            catch (ClassNotFoundException e) 
            {
                e.printStackTrace();
            }
        }
        return beat;
    }

    public static class OnFound 
    {
        public void onFound(InetAddress inet, Packet beat) {}
    }
}

How can I make it so that the listener hears the beat even from another computer?

EDIT: Finds local IPv4 address.

public static InetAddress localAddress()
{
    String ip;
    try {
        Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
        while (interfaces.hasMoreElements()) {
            NetworkInterface iface = interfaces.nextElement();
            if (iface.isLoopback() || !iface.isUp())
                continue;

            Enumeration<InetAddress> addresses = iface.getInetAddresses();
            while(addresses.hasMoreElements()) {
                InetAddress addr = addresses.nextElement();
                ip = addr.getHostAddress();
                if (ip.startsWith("10.") || ip.startsWith("172.31.") || ip.startsWith("192.168"))
                    return addr;
            }
        }
    } catch (SocketException e) {
        throw new RuntimeException(e);
    }
    return null;
}
Zach Goethel
  • 131
  • 2
  • 10
  • Some routers won't pass multicast through. Are you testing two computers on the same hub/switch? – WillShackleford Aug 10 '15 at 21:53
  • You can try capturing the data with wireshark. If wireshark can see it from one and not the other maybe there is an issue with the router. If it can see the data on both computers it must be your listener code. https://www.wireshark.org/ – WillShackleford Aug 10 '15 at 22:03

1 Answers1

3

You need to make sure your listener has joined the multicast group on the proper interface(s), and that your sender is sending on the proper interface.

In both cases, you can do this via the setInterface or setNetworkInterface methods.

Suppose your sender has IP addresses 192.168.1.1 and 192.168.2.1, and your receiver has addresses 192.168.1.2 and 192.168.2.2. If you want your sender to send from 192.168.1.1, you would call:

multicastSocket.setInterface(InetAddress.getByName("192.168.1.1"));

Your receiver would then need to receive on 192.168.1.2:

multicastSocket.setInterface(InetAddress.getByName("192.168.1.2"));
multicastSocket.joinGroup(InetAddress.getByName(groupName));

If you want your receiver to receive on multiple interfaces, call joinGroup multiple times, first calling setInterface:

multicastSocket.setInterface(InetAddress.getByName("192.168.1.2"));
multicastSocket.joinGroup(InetAddress.getByName(groupName));
multicastSocket.setInterface(InetAddress.getByName("192.168.2.2"));
multicastSocket.joinGroup(InetAddress.getByName(groupName));

Edit:

If you don't know your local IP address, you can use InetAddress.getLocalHost() to get the IP associated with the computer's host name. If you have more than one IP on your system, you can call NetworkInterface.getNetworkInterfaces() to get a list of network interfaces, then call getInetAddresses() on each one to get the IP addresses:

for (NetworkInterface intf: NetworkInterface.getNetworkInterfaces()) {
    for (InetAddress addr: intf.getInetAddresses()) {
        System.out.println("interface " + intf + ": address " + addr);
    }
}

Edit 2:

To send on multiple interfaces: In Heart.beat():

    Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
    while (interfaces.hasMoreElements()) {
        NetworkInterface iface = interfaces.nextElement();
        if (iface.isLoopback() || !iface.isUp())
            continue;

        Enumeration<InetAddress> addresses = iface.getInetAddresses();
        while(addresses.hasMoreElements()) {
            InetAddress addr = addresses.nextElement();
            multicastSocket.setInterface(addr);
            multicastSocket.send(datagramPacket);
        }
    }

To receive on multiple interfaces: In the constructor for Listener:

    Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
    while (interfaces.hasMoreElements()) {
        NetworkInterface iface = interfaces.nextElement();
        if (iface.isLoopback() || !iface.isUp())
            continue;

        Enumeration<InetAddress> addresses = iface.getInetAddresses();
        while(addresses.hasMoreElements()) {
            InetAddress addr = addresses.nextElement();
            multicastSocket.setInterface(addr);
            multicastSocket.joinGroup(InetAddress.getByName(groupName));
        }
    }
dbush
  • 205,898
  • 23
  • 218
  • 273
  • So do `multicastSocket.setInterface(InetAddress.getByName("IP of current computer"));` on both heart and listener? – Zach Goethel Aug 12 '15 at 17:11
  • @JimmyJohnson Correct. On the sender, it controls which interface to use when you call `send`. On the listener, it controls which interface to join the multicast group on when you call `joinGroup`. – dbush Aug 12 '15 at 17:14
  • How do I determine the IP of the computer? I've done some research but mostly it says you'd have to do parsing of a system command. – Zach Goethel Aug 12 '15 at 17:16
  • @JimmyJohnson Assuming you only have one external IP, that should work. If you have multiple, the sender needs to pick one to send from, and the listener probably wants to join the group on all. In the latter case, you'll also want to check that `supportsMulticast()` returns true. If you know you only have one, then `InetAddress.getLocalHost()` should work. Calling `ifconfig -a` (Linux) or `ipconfig /all` (Windows) will tell you all available interfaces / IPs. – dbush Aug 12 '15 at 17:35
  • My computer has two, one for router and another for my virtual machine. `InetAddress.getLocalHost()` returns the virtual machine IP. I also realized that I never called `.joinGroup` on the heart. – Zach Goethel Aug 12 '15 at 17:41
  • The heart doesn't have to call `joinGroup` unless it intends on receiving from the multicast group. If it's only sending, it's not necessary. – dbush Aug 12 '15 at 17:45
  • @JimmyJohnson What you could for heart is send on multiple interfaces. Loop through the interface list, call `setInterface()` on your multicast socket, then call `send()` for each interface. And your receiver should definitely join on all interfaces. – dbush Aug 12 '15 at 17:47
  • You can't iterate like that on an Enumerator, but I can rewrite it. – Zach Goethel Aug 12 '15 at 18:04
  • @JimmyJohnson Good catch. I'll fix that in my answer. – dbush Aug 12 '15 at 18:08