一般情况下,我们可以结合利用java.net.MulticastSocket和java.net.DatagramPacket对象来实现组播通信功能。但这在要求满足实时通信的情况下时,则显然有问题。主要体现在:如果没有数据报达到时,MulticastSocke对象调用receive()和send()方法进行收发数据报时,将一直处于阻塞状态,严重影响了后续操作。
在此之前,解决上述问题的一个方案是利用多线程技术,将接收和发送操作放在不同的线程对象中进行,但这在高交互的场景下时会带来线程开销问题。
不过利用java.nio.channels.MulticastChannel为我们解决了后顾之忧,这是在JDK1.7中提供的新特点,它可以使组播通信像单播UDP以及面向TCP连接的Socket通信那样,利用通道机制实现无阻塞式的交互环境。
java.nio.channels.MulticastChannel 只是一个接口,它不具备任何实现细节。不过JDK1.7利用这个接口扩展了java.nio.channels.DatagramChannel 的职责范围,使这个抽象类保留了实现非阻塞式的单播UDP通信基础上,具备了非阻塞式组播UDP通信的能力。
下面的实例将模拟用户群聊天的场景。
/** * <p>非阻塞模式下的组播用户终端,模拟用户群聊天</p> * @author <a href="mailto:code727@gmail.com">Daniele</a> * @version 1.0.0, 2013-4-15 * @see * @since AppDemo1.0.0 */ public class MulticastUserTerminal { static CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder(); static Charset charset = Charset.forName("UTF-8"); static CharsetDecoder decoder = charset.newDecoder(); public static void main(String[] args) { // 组播通道 DatagramChannel channel = null; // 组播组 InetAddress group = null; try { /* * 创建指定协议的组播通道, * 1.INET:IPV4 * 2.INET6:IPV6 */ channel = DatagramChannel.open(StandardProtocolFamily.INET); /* * setOption(StandardSocketOptions.SO_REUSEADDR, true) * 表示允许组播成员绑定到相同的端口上,它必须在绑定bind()前调用。 * 由于bind()方法内的参数绑定的是当前组成员用于接收数据报的本地端口, * 因此如果此终端只用于发送,则将bind()方法的参数设置为null或直接去掉此方法。 */ channel.setOption(StandardSocketOptions.SO_REUSEADDR, true).bind(new InetSocketAddress(9527)); // 允许接收自己发送出去的数据报 channel.setOption(StandardSocketOptions.IP_MULTICAST_LOOP, true); // 关键点,设置组播通道为非阻塞模式 channel.configureBlocking(false); /* * 如果组播通道是IPV4协议的,则这里创建的本地网络接口也应该具有此协议,否则为IPV6。 * 如果通道协议与网络接口的协议不一致,则当通道加入组播组时就会抛出java.lang.IllegalArgumentException * 与MulticastSocket类似,在接收数据报之前要将创建的本地网络接口加入到组播组。 * 如果只用于发送目的,则如下三行代码都不要 */ group = InetAddress.getByName("224.1.1.108"); NetworkInterface networkInterface = NetworkInterface.getByName("net4"); channel.join(group, networkInterface); ByteBuffer buffer = ByteBuffer.allocate(8192); InetSocketAddress member = null; MulticastPacketSenderThread senderThread = null; while (true) { /* * 由于前面已调用configureBlocking(false)方法将通道设置为非阻塞式的, * 因此这里对需要对读进行判空。与MulticastSocket类似, * 从组播通道的receive()方法的返回结果中,可以得到当前数据报是哪一个组播成员发送的。 */ if ((member = (InetSocketAddress) channel.receive(buffer)) != null) { buffer.flip(); String notice = DateUtils.dateToString(new Date(), DateUtils.DEFAULT_DATETIME_FORMAT) + " - 来自 " + member.getHostName() + "[" + member.getAddress().getHostAddress() + ":" + member.getPort() + "] 的消息:" ; System.out.println(notice); System.out.println(decoder.decode(buffer)); buffer.clear(); } /* * 由于发送数据来源于键盘输入(阻塞式),因此这里需要用线程来实现无阻塞发送。 * 如果数据不是来源于阻塞式的终端,则直接在下面判断send()方法执行后是否返回0即可。 */ if (senderThread == null) { senderThread = new MulticastPacketSenderThread(channel, group, 9527); new Thread(senderThread).start(); } else { // 当输入"exit"后,结束接收和发送数据报 if (senderThread.isStopSend()) break; } } } catch (IOException e) { e.printStackTrace(); } finally { if (channel != null) try { channel.close(); System.out.println("关闭组播通道"); } catch (IOException e) { e.printStackTrace(); } } } }
/** * <p>数据报发送线程</p> * @author <a href="mailto:code727@gmail.com">Daniele</a> * @version 1.0.0, 2013-4-15 * @see * @since AppDemo1.0.0 */ public class MulticastPacketSenderThread implements Runnable { private DatagramChannel channel; /** 发送目标组 */ private InetAddress group; /** 目标组成员端口号 */ private int groupPort; /** 标识是否结束发送操作 */ private boolean stopSend; public MulticastPacketSenderThread(DatagramChannel channel, InetAddress group, int groupPort) { this.channel = channel; this.group = group; this.groupPort = groupPort; } public boolean isStopSend() { return stopSend; } public void run() { String inputMessage = ""; BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); InetSocketAddress target = new InetSocketAddress(group, groupPort); while (!stopSend) { try { inputMessage = reader.readLine(); if (!"exit".equalsIgnoreCase(inputMessage.trim())) channel.send(ByteBuffer.wrap(inputMessage.getBytes()), target); else stopSend = true; } catch (IOException e) { e.printStackTrace(); } } } }
在Eclipse等IDE环境中,同时运行多个MulticastUserTerminal 实例后,在控制台中输入发送数据后回车,查看各实例的控制台的输出结果即可。
图1 多播用户组
图2 User1的控制台
图3 User2的控制台
相关推荐
1. **JDK7新特性<一>概述** JDK7的发布标志着对Java平台的一次重要升级。在这一部分,主要介绍了JDK7的基本情况,包括虚拟机对动态语言的支持和类文件的严格检查。动态语言支持使得Java虚拟机(JVM)能够更好地运行...
JDK8新特性课程内容包括了解Java发展史、Lambda表达式、接口的增强、函数式接口、方法引用、Stream API、Optional、新时间日期API等。 一、Java发展历史 Java的发展历史可以追溯到1991年,Sun公司成立了一个称为...
计算机后端-Java-Java核心基础-第23章 枚举类与注解 17. jdk8新特性:类型注解.avi
计算机后端-Java-Java核心基础-第23章 枚举类与注解 16. jdk8新特性:可重复注解.avi
jdk8新特性,百度云盘。jdk8新特性,百度云盘。jdk8新特性,百度云盘。
JDK1.5新特性
JDK1.8引入了许多重要的新特性,极大地提升了Java编程的效率和性能。以下是这些特性的详细介绍: 1、**Lambda表达式**: Lambda表达式是JDK1.8的一个重大改进,它允许开发者以更简洁的方式传递代码块。在上述示例中...
jdk8新特性.md
为了解决上述问题,自JDK1.4起,Java引入了非阻塞的通信机制,主要通过`java.nio`包实现,提供了更高效的数据处理方式。非阻塞式通信允许服务器程序使用较少的线程处理多个客户端的请求,显著提升了系统的响应能力和...
8. **NIO.2(New I/O 2)**:虽然NIO(非阻塞I/O)是在JDK1.4引入的,但JDK1.5对其进行了扩展,添加了文件系统操作和文件观察者等功能,使得文件操作更加高效和灵活。 9. **类型安全的异常检查(Checked Exceptions...
JDK 1.5在2004年发布,引入了许多重要的新特性和改进,其中最显著的是: 1. **泛型**:泛型增加了类型安全,允许在编译时检查类型,避免了运行时的ClassCastException。 2. **枚举类型**:这是一种新的数据类型,...
Lambda表达式是JDK1.8最显著的新特性之一,它简化了函数式编程,使得可以以更简洁的方式编写匿名函数。Lambda表达式可以替代只包含单个抽象方法的接口的实现,例如`Runnable`和`Comparator`。使用lambda,你可以这样...
视频教程地址:http://www.gulixueyuan.com/course/56
### JDK8十大新特性详解 #### 一、接口的默认方法 在JDK8之前,接口只能定义抽象方法,不允许有具体的实现。然而在JDK8中引入了一个重要的概念——**默认方法**(Default Methods)。这允许我们在接口中提供一个...
这些新特性包括函数式接口、接口的默认方法和静态方法、方法引用、Lambda表达式、重复注解、改进的类型推断以及扩展注解支持等。 首先,函数式接口是JDK 1.8中一个重要的概念。函数式接口是指那些只包含一个抽象...
jdk1.8新特性: 1.Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可。 2.新增lambda表达式 3.提供函数式接口 4.Java 8 允许你使用关键字来传递方法或者构造函数引用 5.我们可以直接在...
JDK1.8已经发布很久了,在很多企业中都已经在使用。并且Spring5、SpringBoot2.0都推荐使用JDK1.8以上版本...Java8新特性如下: Lambda表达式 函数式接口 方法引用 接口的默认方法和静态方法 Optional Streams 并行数组
jdk1.8新特性: 1.Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可。 2.新增lambda表达式 3.提供函数式接口 4.Java 8 允许你使用关键字来传递方法或者构造函数引用 5.我们可以直接在...
**JDK 15 演进历史及新特性详解** **一、JAVA现状与历史版本新特性** 自1995年诞生以来,Java已走过25个年头,成为全球最受欢迎的编程语言之一,据统计,约70%的开发者在使用Java,其应用程序广泛分布在全球510亿...
jdk5、jdk6新特性系统介绍.chm 系统、全面 本人自己整理的资料