【问题标题】:Java: Receiving an UDP datgram packet with multiple DatagramSocketsJava:接收具有多个 DatagramSockets 的 UDP 数据包
【发布时间】:2013-02-03 03:26:28
【问题描述】:

我正在尝试实现一种将 UDP 数据包发送到多个接收器的方法。我认为在接收 DatagramSocket 实例上设置 setReuseAddress(true) 应该是可行的。

我的问题是,在某些情况下,我需要限制与本地计算机的通信 - 因此是 localhost 接口(下面的演示代码中的 useLocalhost=true)。在这种情况下,突然只有第一个接收器套接字获得传入的数据包,另外两个什么都看不到。

我在 Windows (oracle 64bit) 和 Linux (OpenJDK 64bit) 上对此进行了测试,因此我只看到了三种可能性:

  1. 这是一种预期且已知的行为(我不了解整个机制 - 也就是“我的大脑中的错误”)
  2. Java JRE 中存在错误
  3. 我的代码中有一个错误。

有人对此主题有任何经验吗?我可以帮助确定问题所在吗?

请参阅下面的最小工作示例来演示这一点。 请注意,我使用广播地址来模拟来自真实外部主机的网络数据包。

如果一切顺利,您应该会在末尾看到三行(按此顺序或不同顺序):

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();
        }
    }
}

【问题讨论】:

  • 你的代码有点尝试做广播,有点尝试做多播,有点尝试做单播。选择一个模型并坚持下去!例如,如果您尝试进行广播,请使用广播地址。
  • @David:请阅读我的 cmets。广播地址仅用于模拟传入流量。否则我的例子会复杂得多,因为第二个程序需要在第二台 PC 上运行。
  • @Robert 所以如果这不是你的真实代码为什么要发布它?
  • 这是一个演示问题和我的问题的最小工作示例。

标签: java udp datagram


【解决方案1】:

192.168.1.255是广播地址,所以数据报是广播的,按照UDP广播的规则。 127.0.0.1 是单播地址,所以数据包是单播的。所以你会得到不同的行为。

正如@DavidSchwartz 评论的那样,您的代码是混合的。例如,连接到广播地址没有太多意义,绑定也没有。我认为您正在寻找的是多播。

【讨论】:

  • 我的问题的目的是了解为什么重用数据报套接字确实适用于传入的外部流量,但对于本地主机上的流量却失败了。因此,像你这样的答案对我没有多大帮助。此外,在这种情况下,组播根本不是一种选择,因为它不能仅限于本地主机 AFAIK。
  • 我回答的目的是指出,当您在一种情况下使用广播而在另一种情况下使用单播时,您不一定有权期望相同的行为,并且将不同的行为归因于使用 localhost 而不是切换到单播是不合理的。很遗憾你第一次没有得到它。您断言多播不能仅限于本地主机也是不正确的。这就是 TTL 的用途,还有主机本地多播地址。
  • 你把我的注意力吸引到了多播上,所以我做了一些实验。但是,我找不到有关“主机本地多播地址”的任何参考资料。将 ttl 设置为 0 仅适用于本地主机,但使用公共 IP 地址。根据这个question,不可能在本地主机上使用多播。
  • 那个问题的答案是错误的。您可以使用 TTL 字段来限制多播的范围。详细信息可以在 W.R. Stevens 中找到,正如我对该问题的新回答中所引用的那样。
【解决方案2】:

您可以在本地主机上使用多播 但是,您需要注意几件事才能使其正常工作。

示例: lo0 (127.0.0.1) en0 (192.168.0.111) zh1 (10.1.0.111)

  1. 每个接口有 2 个独立的插座,一个用于接收,一个用于 发送。在上面的示例中,这意味着总共创建 6 个 插座。
  2. 永远不要 bind() 将发送多播 UDP 数据包的套接字。
  3. 始终 bind() 一个将接收多播 UDP 数据包的套接字。 调用 bind() 后,切勿尝试设置或重新配置多播套接字 相反,当机器的接口由于电缆被拔出/插入而发生变化时, 销毁所有发送/接收多播套接字并重新创建它们。

示例代码: iMulticastSocketInterfaceIPAddress 将是三个接口之一

     /* 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));

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-12-29
    • 2011-03-30
    • 2012-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多