MulticastSocket构造函数和绑定端口或SocketAddress

10

我可能对绑定术语存在基本误解,但我对MulticastSocket及其构造函数的用法感到困惑。它们没有做我理解中应该做的事情,因此希望能有人帮我澄清误解。

首先,我想要实现什么。我尝试编写一个简短的程序,创建一个MulticastSocket并将其绑定(即侦听)到特定的网络适配器,然后加入特定的多播组。我尝试了以下(客户端)代码,它可以正常工作,我可以向它多播一个数据包而不会使多播套接字超时。

public class Main {
public static final int DEFAULT_MULTICAST_PORT = 5555;
public static final String multicastGroup = "225.4.5.6";
public static final String adapterName = "eth0";
public static final int MAX_PACKET_SIZE = 65507;

CharBuffer charBuffer = null;
Charset charset = Charset.defaultCharset();
CharsetDecoder decoder = charset.newDecoder();
static ByteBuffer message = ByteBuffer.allocateDirect(MAX_PACKET_SIZE);
static boolean loop = true;

static byte[] buffer = new byte[MAX_PACKET_SIZE];

public static void main(String[] args) {

    try {
        //MulticastSocket mSocket = new MulticastSocket(new InetSocketAddress("192.168.2.23", DEFAULT_MULTICAST_PORT));
        MulticastSocket mSocket = new MulticastSocket(DEFAULT_MULTICAST_PORT);
        mSocket.setReuseAddress(true);
        mSocket.setSoTimeout(5000);
        NetworkInterface nic = NetworkInterface.getByName(adapterName);
        mSocket.joinGroup(new InetSocketAddress(multicastGroup, DEFAULT_MULTICAST_PORT),NetworkInterface.getByName(adapterName));
        DatagramPacket p = new DatagramPacket(buffer, MAX_PACKET_SIZE);
        while (loop){
            try{
                mSocket.receive(p);
                System.out.println("Packet Received.");
            } catch (SocketTimeoutException ex){
                System.out.println("Socket Timed out");
            }
        }

    } catch (IOException ex){
        System.err.println(ex);
    }

}

不幸的是,一旦我改变了MulticastSocket构造函数为MulticastSocket(SocketAddress bindaddr),它就停止工作了。似乎我只能使用绑定到端口的构造函数才能使其工作,那么当调用此构造函数时,它实际上绑定到了什么,因为在这个阶段我没有指定网络适配器。(我知道稍后会使用特定的NetworkInterface加入组,但是如何确保在构造函数调用期间它不绑定到任何适配器?)

我也可以加入一个组而不指定适配器,然后我就不知道它绑定到哪个适配器了。

有人能解释一下仅绑定到端口意味着什么,以及是否可能仅侦听特定的NetworkInterface吗?

更新#1 **

阅读到目前为止的回复并与同事讨论后,以下是我的对Java MulticastSocket的理解:

  1. MulticastSocket()创建一个多播套接字,该套接字绑定到由主机的底层操作系统随机选择的端口(绑定到通配符地址0.0.0.0,即所有网络卡)。但是,调用此构造函数并传入null时会创建一个未绑定的MulticastSocket。在这种情况下,调用`bind(SocketAddress)`方法将绑定到IP和端口。
  2. MulticastSocket(int port)创建一个多播套接字,该套接字绑定到特定端口但在每个IP地址上。
  3. MulticastSocket(SocketAddress sa)创建一个多播套接字,该套接字绑定到指定的IP地址(可以是任何IP地址,甚至是无效的多播地址)和端口。

使用选项2,这意味着潜在地发送到指定端口的任何数据包都将传递给MulticastSocket。我说“潜在地”是因为只有加入了组(但非组播地址的其他数据包将到达只要端口号匹配?)才能到达多播数据包。

使用选项3,我可以绑定到IP地址,只有目标与之匹配的数据包才会到达套接字。完全可以使用此选项将其绑定到特定网络接口的IP,但是将不会接收任何多播数据包,因为它们不会被定向到网络卡的特定IP地址(这就是为什么我从未在代码示例中看到它们到达的原因)。也可以绑定到有效的多播地址,但在这种情况下只有与绑定的多播地址匹配的特定数据包才会到达套接字,无论调用何时使用 joinGroup()

现在对joinGroup()的调用对套接字本身不会产生任何影响,而是向底层网络系统发出IGMP请求,以确保路由器、操作系统等实际上开始将指定的多播数据包通过硬件和网络堆栈传递到Java MulticastSocket本身。

** 更新2 ** 引用自 "UNIX 网络编程",作者:Stevens、Fenner、Rudoff:

为了接收组播数据报,进程必须加入多播组,并且还必须将 UDP 套接字 绑定 到用作分组发送目标端口号的协议编号上。这两个操作是不同的,而且都是必需的。加入组告诉主机的 IP 层和数据链路层接收发往该组的多播数据报。绑定端口是应用程序指定要接收发往该端口的数据报的方式。一些应用程序还会将多播地址与套接字一起绑定,以防止任何其他数据报通过单播、广播或多播地址发送到该端口而递交给套接字。

我认为这就解释了一切。

** 更新3 ** 只是想发布一下我测试过的代码及注释,解释了每个操作。

/**
         * This first creates an UNBOUND Multicast Socket and then binds to
         * a port (but accepting the wildcard IP 0.0.0.0.
         * The Following WORKS:
         */

        /*MulticastSocket mSocket = new MulticastSocket(null);
        mSocket.bind(new InetSocketAddress(DEFAULT_MULTICAST_PORT));
        mSocket.setReuseAddress(true);
        mSocket.setSoTimeout(5000);
        NetworkInterface nic = NetworkInterface.getByName(adapterName);
        mSocket.joinGroup(InetAddress.getByName(multicastGroup));
        */

        /**
         * The following creates a a network socket and binds in the constructor
         * to a local adapter and port. Consequently it DOES not work because
         * it only allows destination ips that match the bound address & port
         * even though the desired group is joined.
         */

        /*MulticastSocket mSocket = new MulticastSocket(new InetSocketAddress("192.168.2.23", DEFAULT_MULTICAST_PORT));
        mSocket.setReuseAddress(true);
        mSocket.setSoTimeout(5000);
        NetworkInterface nic = NetworkInterface.getByName(adapterName);
        mSocket.joinGroup(InetAddress.getByName(multicastGroup));*/


        /**
         * The following binds to the same multicast group this is 'joined' later
         * and this works correctly. However if the join() is NOT called, no packets 
         * arrive at the socket, as expected.
         */

        /*MulticastSocket mSocket = new MulticastSocket(new InetSocketAddress(multicastGroup, DEFAULT_MULTICAST_PORT));

        mSocket.setSoTimeout(5000);
        NetworkInterface nic = NetworkInterface.getByName(adapterName);
        // Comment out the following line and it no longer workds correctly.
        mSocket.joinGroup(InetAddress.getByName(multicastGroup));*/

        /**
         * The following binds to a a specified port on 0.0.0.0 and joins 
         * a specific Multicast group on a specific adapter. This must mean that the IGMP must occur 
         * on the specified adapter.
         * 
         * ** This will ALSO receive packets addressed DIRECTLY to the ip 192.168.2.23 with the same
         * port as DEFAULT_MULTICAST_POR ***ONLY!!***
         */
        MulticastSocket mSocket = new MulticastSocket(DEFAULT_MULTICAST_PORT);
        mSocket.setReuseAddress(true);
        mSocket.setSoTimeout(5000);
        NetworkInterface nic = NetworkInterface.getByInetAddress(InetAddress.getByName("192.168.2.23"));
        mSocket.joinGroup(new InetSocketAddress(multicastGroup, DEFAULT_MULTICAST_PORT),NetworkInterface.getByName(adapterName));

        /**
         * The following binds to a specific address and port (i.e. adapter address)
         * and then ONLY accepts UDP packets with destination equal to that IP.
         */
        /*MulticastSocket mSocket = new MulticastSocket(new InetSocketAddress("192.168.2.23", DEFAULT_MULTICAST_PORT));
        mSocket.setReuseAddress(true);
        mSocket.setSoTimeout(5000);
        NetworkInterface nic = NetworkInterface.getByInetAddress(InetAddress.getByName("192.168.2.23"));*/

使用SocketAddress创建多播套接字意味着我们将套接字绑定到SocketAddress,对吗?那么如果我们加入一个多播组,为什么套接字无法接收多播数据报? - Sivanandham
好的,这里有一个完全没有意义的句子:“This will ALSO receive packets addressed DIRECTLY to the ip 192.168.2.23 with the same port as DEFAULT_MULTICAST_POR ***ONLY!!***”...它怎么可能同时是“ALSO”和“ONLY”。这在逻辑上是不可能的,所以我不知道你在说什么,但我想知道,因为我也想更多地了解这个。 - searchengine27
也许如果你在回复中更加礼貌,我会倾向于提供帮助。 - D-Dᴙum
这毫无意义。你只是在为一条陈述事实的评论寻找争吵的机会吗?它既不礼貌也不不礼貌 - 它只是一个事实。现在你变得不礼貌了 :) 谢谢你证明了你无法成为社区受尊敬的成员 :) - searchengine27
2个回答

2
如果您在创建或绑定时不指定本地IP地址,则会绑定到0.0.0.0,这意味着“通过任何NIC接受输入”。这通常是您想要的。
可以绑定到特定的IP地址,这隐含地意味着相应的NIC,但某些系统(如Linux)似乎希望如果绑定了多播套接字,则将其绑定到多播组本身。我觉得这毫无意义:如果您想加入另一个组怎么办?
我认为最好、最可移植的方法是在0.0.0.0上监听,并逐个通过特定的NIC或所有NIC加入。在多主机中,除非您确信默认路由到多播组是您想要发送加入请求的路由,否则后者是必需的,因为如果您不指定加入接口,就会发生这种情况。

经过进一步的思考,我已经在问题中添加了更多信息,并希望听取您的想法。 - D-Dᴙum
如果您使用选项3,则应通过该接口加入,正如我上面所说的那样。但仍然可能遇到接收非多播的问题。您可以尝试绑定到多播地址本身,但它可能在您的平台上无法工作,就像我之前也说过的那样。 - user207421
EJP,我认为我们基本上对发生的事情达成了一致意见,但我应该指出,在我所做的测试中,可以将多播套接字绑定到非多播地址,这实际上会阻止接收任何多播数据包。我现在意识到join()bind()是两个不同的东西。还可以使用此绑定到非多播地址的MulticastSocket接收发送到该非多播地址的数据报。这是有道理的,因为MulticaseSocket只是DatagramSocket的一个特殊化。我使用允许绑定的Linux。我不关心Windows。 - D-Dᴙum
我很快会发布我的测试摘要,以供将来参考,如果有其他人对此深入思考的话... - D-Dᴙum
请查看此链接:https://dev59.com/Q2gv5IYBdhLWcg3wTvLL#10739443 - haelix
显示剩余10条评论

0

现在不明白重点了吗?乍一看,他的多播地址看起来没问题。 - user207421
Germann,谢谢您的回复。您能否对我添加的额外信息发表评论? - D-Dᴙum
我没有检查流量是否实际到达了所有地址 - 这可能很有趣,不幸的是我目前没有足够的计算机来自行检查。如果您可以设置并报告您的发现。 - Germann Arlington
据我所知,多播基本上与广播相同(即数据包将被发送到所有可用地址),不同之处在于您必须订阅(加入)以接收多播。您在哪里找到的joinGroup()实际上影响网络而不是接收器?我预计选项3 - MulticastSocket绑定到无效地址将立即失败,或者至少在您尝试开始使用它时会失败。 - Germann Arlington
1
Germann,我之前没有意识到的是bindingjoining是两个完全不同的概念。你可以绑定到任何IP地址,但只能加入多播IP地址。 - D-Dᴙum
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接