zoukankan      html  css  js  c++  java
  • nio非阻塞缓存流

    NIO学习笔记
    
    
    一、为什么引入NIO?
    
         NIO是new IO的简称,从1.4版本后引入。传统的套接字(Socket)对于小规模的系统可以很好的运行,但是如果要同时处理上千个客户机时,服务器就需要产生上千个线程来等待用户的输入,这样就产生了严重的资源浪费,那么如何解决这个问题呢?NIO的提出正是解决了这个问题。
         NIO采用轮询的方式来查找哪个客户机需要服务,从而提供服务,这也正是NIO中的Selector和Channel抽象的关键点。一个Channel实例代表了一个“可轮询的”I/O目标。NIO另外一个重要特性是Buffer类。
    
    二、信道(Channel)与套接字(Socket)的不同点
    
          信道需要通过调用静态工厂方法来获得实例:
    
            SocketChannel sc = SocketChannel.open();
            ServerSocketChannel ssc = ServerSocketChannel.open();
    
          Channel使用的不是流,而是缓冲区来发送和读取数据。Buffer类或其任何子类的实例都可以看作是一个定长度的JAVA基本数据类型元素序列。与流不同,缓冲区具有固定的、有限的容量。还有一点需要注意,Buffer实例化是通过调用allocate()方法。
    
                ByteBuffer buffer = ByteBuffer.allocate(256);//根据实际情况来定缓冲区大小
    
         或者通过包装一个已有的数组来创建:
    
                ByteBuffer buffer = ByteBuffer.wrap(byteArray);
    
         NIO的强大功能部分来自于channel的非阻塞特性。Socket的某些操作可能会无限期的阻塞。例如,对accept()方法的调用可能会因为等待一个客户端连接而阻塞;对read()方法的调用可能会因为没有数据可读而阻塞,直到连接的另一端传来新的数据。NIO的channel抽象的一个特征就是可以通过配置它的阻塞行为,以实现非阻塞的信道。
    
                 cc.configureBlocking(false);
    
         在非阻塞式信道上调用一个方法总是会立即返回。这种调用的返回值指示了所请求的操作完成的程度。例如,在一个非阻塞式ServerSockerChannel上调用accept()方法,如果有连接请求在等待,则返回客户端SocketChannel,否则返回null。
    
     
    三、Selector介绍
    
         Selector类可用于避免使用非阻塞式客户端中很浪费资源的“忙等”方法。例如,考虑一个即时消息发送器。可能有上千个客户端同时连接到了服务器,但在任何时刻都只有非常少量的消息需要读取和分发。这就需要一种方法阻塞等待,直到至少有一个信道可以进行I/O操作,并指出是哪个信道。NIO的Selector就实现了这个功能。一个Selector实例可以同时检查一组信道的I/O状态。
         那么如何使用Selector来监听呢?首先需要创建一个Selector实例(使用静态工厂方法open())并将其注册(一个信道可以注册多个Selector实例)到想要监听的信道上。如下:
    
               Selector selector = Selector.open();
              DatagramChannel channel = DatagramChannel.open();
              channel.configureBlocking(false);
              channel.socket().bind(new InetSocketAddress(servPort));//绑定端口
               channel.register(selector, SelectionKey.OP_READ);//注册
    
    最后,调用选择器上的select方法。
    int num = selector.select();//获取

    获取可进行I/O操作的信道数量。如果在一个单独的线程中,通过调用sleect()方法就能检查多个信道是否准备I/O操作。如果经过一段时间后任然没有信道准备好,则返回0,并允许程序继续执行其它任务。 那么如何在信道上对“感兴趣的”I/O操作进行监听呢?Selector与Channel之间的关联由一个SelectionKey实例表示。SelectionKey维护了一个信道上感兴趣的操作类型信息,并将这些信息存放在一个int型的位图(bitmap)中,该int型数据的每一位都有相应的含义。 SelectionKey类中的常量定义了信道上可能感兴趣的操作类型,每个这种常量都是只有一位设置为1的位掩码。在API文档中,我们查知: OP_ACCEPT 16 10000 OP_CONNECT 8 01000 OP_WRITE 4 00100 OP_READ 1 00001 通过对OP_ACCEPT,OP_CONNECT,OP_READ以及OP_WRITE中适当的常量进行按位OR,我们可以构造一个位向量来指定一组操作。例如,一个包含了读和写的操作集合可由表达式(OP_READ|OP_WRITE)来指定。 通过Channel类中的validOps()方法,我们可以知道该信道可以监听哪些I/O操作。如果定义了OP_READ|OP_WRITE,则validOps()方法的返回值为5(00101);定义了上述四种操作,则其值应该为29(11101)。 下面笔者将实际使用兴趣集中常见的错误进行下汇总(一般使用OP_WRITE和OP_READ是不会发生错误的): Error1: Exception in thread "main" java.lang.IllegalArgumentException at java.nio.channels.spi.AbstractSelectableChannel.register(Unknown Source) at java.nio.channels.SelectableChannel.register(Unknown Source) at UdpServer.main(UdpServer.java:20) 错误使用OP_CONNECT,其正确使用方法应该是先建立连接。正确的一个例子如下(参考自:http://blog.csdn.net/zhouhl_cn/article/details/6582420) TCP的NIO实现网络上很多,笔者在网络发现很少有关于UDP的NIO实现。下面给出笔者亲测的NIO版本的UDP实现。 /** * 服务器的实现 */ import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.DatagramChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.util.Iterator; public class UdpServer { public static void main(String args[]) throws IOException { int servPort = 999; Selector selector = Selector.open(); DatagramChannel channel = DatagramChannel.open(); channel.configureBlocking(false); channel.socket().bind(new InetSocketAddress(servPort)); channel.register(selector, SelectionKey.OP_READ); //channel.register(selector, 1);与上句子同效果 while (true) { int num = selector.select(); if (num == 0) { continue; } Iterator<SelectionKey> Keys = selector.selectedKeys().iterator(); while (Keys.hasNext()) { SelectionKey k = Keys.next(); if ((k.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) { DatagramChannel cc = (DatagramChannel) k.channel(); // 非阻塞 cc.configureBlocking(false); ByteBuffer buffer = ByteBuffer.allocateDirect(255); // 接收数据并读到buffer中 buffer.clear(); channel.receive( buffer ) ; buffer.flip(); byte b[] = new byte[buffer.remaining()]; for (int i = 0; i < buffer.remaining(); i++) { b[i] = buffer.get(i); } Charset charset = Charset.forName("UTF-8"); CharsetDecoder decoder = charset.newDecoder(); CharBuffer charBuffer = decoder.decode(buffer); System.out.println("The imformation recevied:"+charBuffer.toString()); Keys.remove(); //一定要remove } } } } } /** * 客户端的实现 */ import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.DatagramChannel; import java.util.Scanner; public class UdpClient { @SuppressWarnings("resource") public static void main(String agrs[]) throws IOException { String s = null; while ((s = new Scanner(System.in).nextLine()) != null) { DatagramChannel dc = null; dc = DatagramChannel.open(); SocketAddress address = new InetSocketAddress("localhost", 999); ByteBuffer bb = ByteBuffer.allocate(255); byte[] b = new byte[130]; b = s.getBytes(); bb.clear(); bb.put(b); bb.flip(); dc.send(bb, address); } } }
  • 相关阅读:
    从0系统学Android-2.6Activity间数据传递
    观察者模式详解
    从0系统学Android-2.5更多隐式Intent用法
    从 View 的四个构造方法说起
    ListView详细介绍与使用
    推荐一个程序员系统学习网址
    从 http协议角度解析okhttp
    从0系统学Android-2.4隐式Intent
    菜单布局记录篇
    轮播图记录篇
  • 原文地址:https://www.cnblogs.com/xiaolei2017/p/9554094.html
Copyright © 2011-2022 走看看