我可能对绑定术语存在基本误解,但我对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的理解:
- MulticastSocket()创建一个多播套接字,该套接字绑定到由主机的底层操作系统随机选择的端口(绑定到通配符地址0.0.0.0,即所有网络卡)。但是,调用此构造函数并传入null时会创建一个未绑定的MulticastSocket。在这种情况下,调用`bind(SocketAddress)`方法将绑定到IP和端口。
- MulticastSocket(int port)创建一个多播套接字,该套接字绑定到特定端口但在每个IP地址上。
- 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"));*/