NIO初探

来源:互联网 发布:java开发界面 编辑:程序博客网 时间:2024/06/05 14:40
  • NIO和旧IO的区别

  • NIO的涉及到的类

  • 例子


一点学习NIO的心得,抛砖引玉,若有错误请高手指正。

NIO和旧IO的区别

NIO是传输是基于字节块的,而原来的IO是基于字节流的。NIO的优势不在于传输速度上,而是在其处理方式上。NIO使用selector来轮询可以使用的channel。selector的时下由底层jvm提供支持的。在连接(channel)很多个情况下,selector可以用来关心IO可用性,而线程高效的处理业务逻辑。

NIO相关类

  1. Channel:对底层I/O接口的一个抽象,可以打开底层I/O,比如socket,文件,磁盘等等。可以在在多个线程中共享
  2. Selector: 对可以选择的channel的一个多路选择器,可以从多个channel中选择出可以使用channel,所以selector和channel是一对多。
  3. Buffer:缓存块。其实普通IO我们也有Buffer封装类来加速IO处理。NIO定义了普通类型的buffer,比如ByteBuffer,IntBuffer,Longbuffer等。

例子

参考java网络编程中的Chargen协议,以socket为例,演示NIO的用法。CharGen主要是实现对访问的客户端返回顺序的字符串序列。
客户端socket相对简单些,步骤如下:
创建读取Channel 和ByteBuffer
创建写入Channel ,将读取的内容写入
package nio;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.Channels;import java.nio.channels.SocketChannel;import java.nio.channels.WritableByteChannel;public class CharGenClient {    public static void main(String[] args) {        // TODO Auto-generated method stub        SocketChannel channel = null;        WritableByteChannel writeChannel = null;        try {            //使用工厂方法声明一个Channel,指定网络链接            channel = SocketChannel.open(new InetSocketAddress("127.0.0.1",5536));            //将Channel绑定到指定端口(客户客户端的socket,让系统默认绑定一个本地端口)            //channel.bind();            //分配一个缓冲区,以便接收服务端送来的数据块            ByteBuffer buffer = ByteBuffer.allocate(80);            //创建一个写channel,将接收到到数据写到console             writeChannel = Channels.newChannel(System.out);            //轮询读取服务其的数据            while(channel.read(buffer)!=-1) {                //限定Buffer的实际数据大小                buffer.flip();                //写入到输出channel                writeChannel.write(buffer);                //清空buffer,以便下次读取                buffer.clear();            }        } catch (IOException e) {            // TODO Auto-generated catch block            e.printStackTrace();        } finally {            if(channel != null) {                try {                    //关闭channel                    writeChannel.close();                    channel.close();                } catch (IOException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }            }        }    }}

接着实现服务端。步骤如下:

  1. 定义服务端的Channel,并绑定到指定端口。并声明该channel为非阻塞的。
  2. 定义selector,将服务端的channel注册到selector,并指定关注事件类型为selectionkey.OP_ACCEPT。
  3. 简单的定义的轮询selector.select(); 这个事件会阻塞,直到有相关可以处理的SelectionKey.channel唤醒。
  4. 遍历selector.selectedKey(),根据Key的事件进行相关处理
    • 如果是接收事件,则通过selectionKey获取对应的ServerSocketChannel。利用这个channel生成SocketChannel。并注册到刚刚的Selector 中,并关注写事件
    • 如果是写事件,则分配ByteBuffer供后续Channel读取。
package nio;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.util.Iterator;import java.util.Set;public class CharGenServer {    /**     * 服务端CharGen,接收客户端的访问,顺序输出字符串,直到客户端终止链接。使用NIO实现。</br>     * 1、初始化服务端的Channel,以便监听来的请求</br>     * 2、根据来的请求生成客户端Channel,返回字符串     * </br>     * 使用到的类包括:Channel、Buffer、Selector、SelectorKey等     */    public static void main(String[] args) {        // TODO Auto-generated method stub        //初始化需要输出的内容,在内存中缓存        byte[] rotation = new byte[95*2];        for(byte i= ' ';i<= '~';i++) {            rotation[i-' '] = i;            rotation[i+95-' '] = i;        }        ServerSocketChannel server = null;        SocketChannel client = null;        Selector selector =null;        try {            //获取服务端socket的channel            server = ServerSocketChannel.open();            //channel和指定的端口绑定,对外提供服务            server.bind(new InetSocketAddress(5536));            //设置服务端channel为非阻塞的,默认是阻塞的。            server.configureBlocking(false);            //工厂方法调用selector            selector = Selector.open();            //将服务端channel注册到selector,并指定关注类型为accept            server.register(selector, SelectionKey.OP_ACCEPT);        } catch (IOException e) {            // TODO Auto-generated catch block            e.printStackTrace();            return;        }        //select开始轮询        while(true) {            try {                //这是个阻塞方法,会等待该select对应的keys中至少一个Channel可以进行I/O操作。                selector.select();            } catch (IOException e) {                // TODO Auto-generated catch block                e.printStackTrace();                break;            }            //获取可以操作的keys,只允许从keyset中删除,不能直接添加。非线程安全的。            Set<SelectionKey> readyKeys = selector.selectedKeys();            //遍历seletionkey            Iterator<SelectionKey> iterator = readyKeys.iterator();            while(iterator.hasNext()) {                SelectionKey key = iterator.next();                iterator.remove();//从Set删除待处理的key                try {                    //判断key所属channel属于那类型的‘事件’                    if(key.isAcceptable()) {                        //获取key对应的channel                        ServerSocketChannel s = (ServerSocketChannel)key.channel();                        //因为服务端的channel是非阻塞的,所以稳妥点的化,需要判断返回的socketchannel是否为null。                        client = s.accept();                        System.out.println("Accepted connection from " + client);                        client.configureBlocking(false);                        //针对客户端的channel注册selector,以及关注的事件,以便提高处理效率,在链接空闲时,不占用系统资源                        SelectionKey key2 = client.register(selector, SelectionKey.OP_WRITE);                        //分配缓存,这个是NIO处理的基本单元。                        ByteBuffer buffer = ByteBuffer.allocate(74);                        buffer.put(rotation,0,72);                        buffer.put((byte)'\r');                        buffer.put((byte)'\n');                        buffer.flip();                        //将分配的缓存对象作为selectionKey的附件,以便后续的channel可以使用                        key2.attach(buffer);                    }else if (key.isWritable()) {                        client = (SocketChannel)key.channel();                        //从附件中获取待处理的缓存                        ByteBuffer buffer = (ByteBuffer)key.attachment();                        if(!buffer.hasRemaining()) {                            buffer.rewind();//重置buffer的position到0                            byte first = buffer.get();//获取第一个字符                            buffer.rewind();//重置buffer的position到0                            int position = first - ' ' + 1;                            buffer.put(rotation, position, 72);                            buffer.put((byte)'\r');                            buffer.put((byte)'\n');                            buffer.flip();//设置缓存中有效值的截止位置                        }                        client.write(buffer);//将数据写入channel                    }                } catch (IOException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                    key.cancel();                    try {                        key.channel().close();                    } catch (IOException e1) {                        // TODO Auto-generated catch block                        e1.printStackTrace();                    }                }/*finally {//不能放在finally中close channel,因为服务端的channel还不能关闭                    key.channel().close();                }*/            }        }    }}

    • NIO和旧IO的区别
    • NIO相关类
    • 例子

原创粉丝点击