NIO初探

  • 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();
                }*/
            }

        }
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值