我可能对这里的绑定术语有一个基本的误解,但我对MulticastSocket及其构造函数的用法感到困惑。他们不做我理解他们应该做的事情,任何能帮助我消除误解的人都将不胜感激。
首先是我想要达到的目标。我试图编写一个简短的程序来创建一个 MulticastSocket 将它绑定(即侦听)到特定的网络适配器上,然后加入特定的 Multicast 组。我已经尝试了以下(客户端)代码,它可以正常工作,我可以向它多播一个数据包,而多播套接字不会超时。
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 套接字绑定到将用作发送到组的数据报的目标端口号的 prot 号。这两个操作是不同的,两者都是必需的。加入组会告诉主机的 IP 层和数据链路层接收发送到该组的多播数据报。绑定端口是应用程序向 UDP 指定它想要接收发送到该端口的数据报的方式。除了端口之外,一些应用程序还将多播地址绑定到套接字。这可以防止为该端口接收到其他单播、广播或多播地址的任何其他数据报传递到套接字。
我认为这说明了一切。
**更新 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"));*/