java (多网卡环境下)发送组播广播(multicast/broadcast)失败问题

java发送组播或广播包并不复杂网上有很多文章,比如下面的两篇:
《Java实现组播(multicast)简单例子》
《Java 网络编程案例:使用 MulticastSocket 实现多点广播》
这些例子都大同小异,拿来就可以用,我刚开始使用组播/广播时就是这样抄个例子,编译,运行,收到消息—完美

但是,当我们的项目在开始运行时,发现问题来了:有时会收不到某台主机发送的组播包,开始以为是路由器或交换阻止组播包通过,就改为广播.改为广播后,发现问题依然存在.
经过反复测试,发现了规律,当电脑上有多块网卡(虚拟网卡也算)时,就有可能无法发出组播包数据,但自己可以收到自己发送的组播/广播包。
哇哦,原来与多网卡环境有关。有了这个规律,再去百度发现不少关于多网卡环境下发送组播/广播包问题的文章,比如这个
《解决多网卡环境下使用特定网卡广播UDP消息的问题》
这篇文章开头的内容就给出了解决办法,如下图:
在这里插入图片描述
看了第一条我就明白了。这是最重要的。
一般情况下,我们向一个IP地址发送数据,我们并不需要指定用哪块网卡发送,因为目标地址明确,底层网卡驱动会帮我们选择合适的网卡发送数据,
但广播或组播就不同,广播或组播地址不是一个指向单一主机的地址,在没有明确的目标指向性的情况下,底层网卡驱动会选择找到第一块网卡做为默认网卡发送数据。如果这时这个默认网卡是一个虚拟网卡(比如我的电脑上装了虚拟机就有一块虚拟网卡),那么发送组播数据就没有真的通过物理网卡发出。而只能被自己接收到。因为这个原因,对于多网卡环境下,发送广播或组播包就必须要指定用哪块网卡发送。

所以我的解决办法就是:遍历所有物理网卡,在每一块网卡上都把组播或广播数据发送一遍,接收组播包时将要明确将每一块物理网卡加入到组播地址中。
下面是我的实现代码片段:

发送组播或广播:

	/**
	 * 向指定的组播或广播地址和端口发送组播数据
	 * @param group 组播或广播地址
	 * @param port 端口
	 * @param message 发送的数据
	 * @param ttl  time-to-live for multicast packets 
	 * @param nic 指定发送数据的网卡
	 * @throws IOException
	 */
	public static void sendMulticast(InetAddress group,int port,byte[] message, Integer ttl,NetworkInterface nic) throws IOException{
    
    
		checkArgument(null != group,"group is null");
		// 如果地址不是组播或广播地址则抛出异常
		checkArgument(group.isMulticastAddress() || isBroadcast(group),"group %s is not a multicast or broadcast address",group);
		checkArgument(message != null && message.length > 0,"message is null or empty");
		checkArgument(null != nic,"nic is null");
		// 获取网卡的绑定的所有IP地址枚举对象
		Enumeration<InetAddress> nifAddresses = nic.getInetAddresses();
		if(!nifAddresses.hasMoreElements()){
    
    
			System.out.printf("NOT ADDRESS ON %s\n",nic.toString());
			return;
		}
		// 从网卡上绑定的IP地址创建的InetSocketAddress 对象
		InetSocketAddress inetAddr= new InetSocketAddress(nifAddresses.nextElement(),0);

		DatagramSocket ds = null;
		try {
    
    
			if(group.isMulticastAddress()){
    
    
				// MulticastSocket 绑定到指定的网卡
				@SuppressWarnings("resource")
				MulticastSocket ms =  new MulticastSocket(inetAddr);
				if(ttl != null){
    
    
					ms.setTimeToLive(ttl);
				}
				ds = ms;
			}else{
    
    
				// DatagramSocket绑定到指定的网卡
				ds = new DatagramSocket(inetAddr);
				ds.setBroadcast(true);
			}
			
			ds.send(new DatagramPacket(message, message.length,group,port));
		} finally {
    
    
			if(ds != null){
    
    
				ds.close();
			}
		}
	}
	/**
	 * 向指定的组播或广播地址和端口发送组播数据
	 * @param group 组播或广播地址
	 * @param port 端口
	 * @param message 发送的数据
	 * @param ttl  time-to-live for multicast packets 
	 * @throws IOException
	 */
	public static void sendMulticast(InetAddress group,int port,byte[] message, Integer ttl) throws IOException{
    
    
		checkArgument(null != group,"group is null");
		// 如果地址不是组播或广播地址则抛出异常
		checkArgument(group.isMulticastAddress() || isBroadcast(group),"group %s is not a multicast or broadcast address",group);
		checkArgument(message != null && message.length > 0,"message is null or empty");
		// 遍历所有物理网卡,将数据在每个网卡上发送一次
		for(NetworkInterface nic:getNICs(Filter.UP,Filter.PHYICAL_ONLY)){
    
    
			sendMulticast(group,port,message,ttl,nic);
		}
	}
	/**
	 * 判断一个地址是否为广播地址(255.255.255.255)
	 * @param addr
	 * @return
	 */
	public static boolean isBroadcast(InetAddress addr){
    
    
		return addr.getHostAddress().equals("255.255.255.255");
	}

接收广播或组播包代码片段

	/**
	 * socket初始化
	 * @return 当前对象
	 * @throws IOException 创建组播对象({@link MulticastSocket})时出错
	 */
	public MultiCastDispatcher init() throws IOException{
    
    
		if(null == datagramSocket){
    
    
			if(NetworkUtil.isBroadcast(group)){
    
    
				datagramSocket = new DatagramSocket(port);
				datagramSocket.setBroadcast(true);
			}else{
    
    
				MulticastSocket multicastSocket = new MulticastSocket(port);
				// 遍历所有物理网卡,对每块网卡执行joinGroup
				for(NetworkInterface nic:NetworkUtil.getNICs(Filter.UP,Filter.PHYICAL_ONLY)){
    
    
					InetSocketAddress inetAddr= new InetSocketAddress(group,0);
					multicastSocket.joinGroup(inetAddr,nic);
				}
				this.datagramSocket = multicastSocket;
			}
		}
		return this;
	}
	/**
	 * 循环接收group,port指定的组播地址发送的数据并交给{@link #processor}处理
	 */
	@Override
	public void run() {
    
    
		stopListener = Boolean.FALSE;
		DatagramPacket packet = new DatagramPacket(message, message.length);
		try {
    
    
			while(!Boolean.TRUE .equals(stopListener)){
    
    
				try {
    
    
					checkArgument(datagramSocket != null,"multicastSocket is uninitizlied");
					datagramSocket.receive(packet);
					byte[] recevied = new byte[packet.getLength()];
					System.arraycopy(message, 0, recevied, 0, packet.getLength());
					// 处理收到的广播/组播数据
					if(!processor.apply(recevied)){
    
    
						break;
					}
				} catch (Exception e) {
    
    
					if(!onerr.apply(e)){
    
    
						break;
					}
				}
			}
		} finally {
    
    
			try {
    
    
				if(datagramSocket instanceof MulticastSocket){
    
    
					// 遍历所有物理网卡,对每块网卡执行leaveGroup
					for(NetworkInterface nic:NetworkUtil.getNICs(Filter.UP,Filter.PHYICAL_ONLY)){
    
    
						InetSocketAddress inetAddr= new InetSocketAddress(group,0);
						((MulticastSocket) datagramSocket).leaveGroup(inetAddr,nic);
					}
				}
			} catch (IOException e) {
    
    
				e.printStackTrace();
			}
			datagramSocket.close();
			datagramSocket = null;
			stopListener = null;
		}				
	}

上面代码都只是示例片段,完整代码参见我的码云GIT仓库:
发送组播/广播的完整实现方法 net.gdface.utils.NetworkUtil.sendMulticast(InetAddress group, int port, byte[] message, Integer ttl)
https://gitee.com/l0km/common-java/blob/master/common-base2/src/main/java/net/gdface/utils/NetworkUtil.java
接收组播/广播的完整实现类 MultiCastDispatcher
https://gitee.com/l0km/common-java/blob/master/common-base2/src/main/java/net/gdface/utils/MultiCastDispatcher.java

Guess you like

Origin blog.csdn.net/10km/article/details/108375600