简体   繁体   中英

Java: Receiving an UDP datgram packet with multiple DatagramSockets

I am trying to implement a method that sends an UDP packet to multiple receivers. I thought that this should be doable setting setReuseAddress(true) on the receiving DatagramSocket instances.

My problem is that in certain conditions I need to limit the communication to the local computer - hence the localhost interface (useLocalhost=true in the demo code below). In such a case suddenly only the first receiver socket gets the incoming packet, the two other don't see anything.

I tested this on Windows (oracle 64bit) and Linux (OpenJDK 64bit), therefore I only see three possibilities:

  1. This is an intended and known behavior (and I don't understand the whole mechanism - aka "bug in my brain")
  2. There is a bug in the Java JRE
  3. There is a bug in my code.

Does somebody have any experience on that topic and can me help to identify where the problem is located?

See below a minimal working example that demonstrates this. Note that I am using the broadcast address for simulating network packets that come from a real external host.

If everything goes right you should see three lines at the end (in this or a different order):

Thread-0 - packet received
Thread-1 - packet received
Thread-2 - packet received

public static void main(String[] args) throws Exception {

    boolean useLocalhost = true;

    InetSocketAddress addr;
    String sendPacketTo = "192.168.1.255"; // we use broadcast so that packet comes from an real external address
    if (useLocalhost)
        sendPacketTo = "localhost"; // does not work (only listener 1 received packet)

    addr = new InetSocketAddress(15002);

    new MyThread(addr).start(); // Datagram socket listener 1
    new MyThread(addr).start(); // Datagram socket listener 2
    new MyThread(addr).start(); // Datagram socket listener 3

    DatagramSocket so = new DatagramSocket();
    so.setBroadcast(true); // does not change anything
    so.connect(new InetSocketAddress(sendPacketTo, 15002));
    so.send(new DatagramPacket("test".getBytes(), 4));
    Thread.sleep(1000);
    System.exit(0);
}

public static class MyThread extends Thread {

    DatagramSocket socket;

    public MyThread(InetSocketAddress addr) throws SocketException {
        super();
        setDaemon(true);
        socket = new DatagramSocket(null);
        socket.setReuseAddress(true);
        socket.setBroadcast(true); // does not change anything
        socket.bind(addr);
        System.out.println("Listener started: " + socket.getLocalAddress());
    }

    public void run() {
        byte[] buf = new byte[10];
        DatagramPacket p = new DatagramPacket(buf, buf.length);
        try {
            socket.receive(p);
            System.out.println(Thread.currentThread().getName() + " - packet received");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

192.168.1.255 is a broadcast address, so the datagram is broadcast, under the rules for UDP broadcast. 127.0.0.1 is a unicast address, so the packet is unicast. So you get different behaviour.

As @DavidSchwartz commented, your code is a mixture. Connecting to a broadcast address for example doesn't have a lot of meaning, and neither does binding to it. I think what you are looking for is multicast.

You can use multicast on localhost However, there are several things you need to be careful of to make it work.

example: lo0 (127.0.0.1) en0 (192.168.0.111) en1 (10.1.0.111)

  1. for each interface 2 separate sockets, one for receiving, one for sending. In the above example this means creating a total of 6 sockets.
  2. Never bind() a socket that will send multicast UDP packets.
  3. Always bind() a socket that will receive multicast UDP packets. Never try to setsockopt() or reconfigure multicast sockets after you call bind() Instead, when machine's interfaces change due to cables being unplugged/plugged, destroy all send/receive multicast sockets and recreate them.

sample code: iMulticastSocketInterfaceIPAddress would be one of the three interfaces

     /* use setsockopt() to request that the kernel join a multicast group */
     struct ip_mreq mreq;
     mreq.imr_multiaddr.s_addr=inet_addr( "239.192.0.133" );
     myAddress.sin_addr.s_addr = mreq.imr_multiaddr.s_addr;         
     mreq.imr_interface.s_addr=( htonl(iMulticastSocketInterfaceIPAddress) );
     theErr = setsockopt( CFSocketGetNative( mSocketBroadcast ) ,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM