JDK7新特性:MulticastChannel实现非阻塞式组播通信


         一般情况下,我们可以结合利用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的控制台

去掉了下载分限制对于UDP组播的一些认识 利用UDP组播能在intarnet,internet上也数据报的形进行数据的组播(在internet上进行组播,要求路由器支持IGMP(internet网关管理协议,这个协议是在IP出现以后,为了支持组播而出现的)).相对于极度消耗网络带宽的广播来说(广播只能在intranet内广播),UDP组播有了很大的优化,只有终端加入到了一个广播组,UDP组播的数据才能被他接受到. UDP组播是采用的无连接,数据报的连接方,所以是不可靠的.也就是数据能不能到达接受端和数据到达的顺序都是不能保证的.但是由于UDP不用保证数据的可靠性,所有数据的传送速度是很快的.1. 组播的“根” 组播从概念上来讲分为两部分:控制部分和数据部分。控制部分决定着组播的对象的组织方。而数据部分决定了数据的传输方。 控制层有“有根”,“无根”两种情况。对于有根的控制层,存在着一个root和若干个leaf. root负责管理这个组播组,只有他能邀请一个leaf加入一个组播组(ATM就是有根控制的一个典型的例子)。对于无根的控制层,没有root,只有若干的leaf. 每一个leaf都能自己加入一个组播组(IP就是无根控制的典型例子) 数据层也有“有根”,“无根”两种情况。对于有根数据层,从root发出的数据能到达每一个leaf,而从leaf发出的数据只能到达root.对于无根数据层,每一个leaf发出的数据能到达组播组中的每一个leaf(甚至包括他自己)。每一个leaf也能接受组播组里的任何数据包。二.IP组播地址 IP组播通信需要一个特殊的组播地址.IP组播地址是一组D类IP地址,范围从224.0.0.0 到 239.255.255.255。其中还有很多地址是为特殊的目的保留的。224.0.0.0到224.0.0.255的地址最好不要用,因为他们大多是为了特殊的目的保持的(比如IGMP协议)三.IGMP协议 IGMP(internet网关管理协议)是IP组播的基础.在IP协议出现以后,为了加入对组播的支持,IGMP产生了。IGMP所做的实际上就是告诉路由器,在这个路由器所在的子网内有人对发送到某一个组播组的数据感兴趣,这样当这个组播组的数据到达后面,路由器就不会抛弃它,而是把他转送给所有感兴趣的客户。假如不同子网内的A,B要进行组播通信,那么,位与A,B之间的所有路由器必须都要支持IGMP协议,否则A,B之间不能进行通信。 当一个应用加入一个组播组后,就会向这个子网的所有路由器发送一个IGMP加入命令,告诉他子网内有人对发送到某一个组播组的数据感兴趣.路由器也会定时向子网内的所有终端发送一条查询消息,用于询问是否还有人对某个组播组的数据感兴趣。如果有的话,终端就会回应一条IGMP消息,路由器则继续转发这个组播组的数据。如果没有人回应这条消息,那么路由器就认为已经没有终端对这个组播组的数据感兴趣,就不会在转发关于这个组播组的数据了。在IGMP第二版中,一个终端推出组播组以后,会向路由器发送一个推出消息,路由器也会通过这个消息来判断是否还要继续转发关于这个组播组的数据了(IGMP第一版中没有这个功能)[这些事情都是底层的系统做的,你只要坐享其成就好了] 四. winsock 1组播 winsock 1的组播主要有以下几个步骤:1. 建立支持数据报的scoket2. 把socket和本地的一个端口绑定(以后会通过这个端口进行数据的收发)3. 通过setsockopt IP_ADD_MEMBERSHIP加入一个组播组4. 然后就能通过sendto / recvfrom进行数据的收法5. 通过 setsockopt IP_DROP_MEMBERSHIP离开一个组播组6. 关闭socket如果你仅仅是想向一个组播组发送数据,而不要接受数据,那么可不用加入组播组,而直接通过sendto向组播组发送数据五.winsock 2组播 winsock 2组播主要是通过WSAJoinLeaf来实现的(WSAJoinLeaf的行为,返回值根据socket的模式组播实现构架有很大的关系) winsock 2组播的主要有以下几个步骤1. 建立支持数据报的socket(用WSASocket建立socket,同2. 时设置组播的一些属性)3. 把socket和本地的一个端口绑定(以后会通过这个端口进行数据的收发)4. 通过WSAJoinLeaf加入一个组播组5. 通过sendto / recvfrom进行数据的收发6. 直接关闭socket,7. 退出组播
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值