初识NIO

为何要使用NIO

NIO(阻塞IO),不论是磁盘IO仍是网络IO,数据在写入OutputStream或从InputStream读取的时候均可能发生阻塞,当发生阻塞,线程会失去CPU的使用权,这个在当前访问量大和有性能要求的状况下是不被容许的。若果咱们采用一个客户端对应一个线程的方式来解决这个问题,可是当须要大量的HTTP长链接的时候就会出现问题,好比淘宝的Web旺旺。为了解决上面的问题,咱们来介绍NIO。

IO和NIO的区别

IO              NIO java

面向流         面向缓冲 算法

阻塞IO        非阻塞IO 服务器

无              选择器
网络

NIO

初识NIO

首先引用iteye一个大神的回复关于NIO的总结:并发

Channel 通道 Buffer 缓冲区 Selector是NIO的三个核心部分。 dom

选择器其中Channel对应之前的流,Buffer不是什么新东西,Selector是由于nio可使用异步的非堵塞模式才加入的东西。 之前的流老是堵塞的,一个线程只要对它进行操做,其它操做就会被堵塞,也就至关于水管没有阀门,你伸手接水的时候,无论水到了没有,你就都只能耗在接水(流)上。异步

nio的Channel的加入,至关于增长了水龙头(有阀门),虽然一个时刻也只能接一个水管的水,但依赖轮换策略,在水量不大的时候,各个水管里流出来的水,均可以获得妥善接纳,这个关键之处就是增长了一个接水工,也就Selector,他负责协调,也就是看哪根水管有水了的话,在当前水管的水接到必定程度的时候,就切换一下:临时关上当前水龙头,试着打开另外一个水龙头(看看有没有水)。socket

当其余人须要用水的时候,不是直接去接水,而是事前提了一个水桶给接水工,这个水桶就是Buffer。也就是,其余人虽然也可能要等,但不会在现场等,而是回家等,能够作其它事去,水接满了,接水工会通知他们。性能

其实也是很是接近当前社会分工细化的现实,也是统分利用现有资源达到并发效果的一种很经济的手段,而不是动不动就来个串行处理,虽然那样是最简单的,但也是最浪费资源的方式。测试

NIO原理

NIO 有一个主要的类Selector,这个相似一个观察者,只要咱们把须要探知的socketchannel告诉Selector,咱们接着作别的事情,当有事件发生时,他会通知咱们,传回一组SelectionKey,咱们读取这些Key,就会得到咱们刚刚注册过的socketchannel,而后,咱们从这个Channel中读取数据,放心,包准可以读到,接着咱们能够处理这些数据。

Selector内部原理实际是在作一个对所注册的channel的轮询访问,不断的轮询(目前就这一个算法),一旦轮询到一个channel有所注册的事情发生,好比数据来了,他就会站起来报告,交出一把钥匙,让咱们经过这把钥匙来读取这个channel的内容。

NIO例

NIO例子1

RandomAccessFile aFile = new RandomAccessFile("/Users/wjk/myproject/test/IO/src/main/resources/io.txt", "rw");
        //获取接水工
        FileChannel channel = aFile.getChannel();
        //设置水桶的大小
        ByteBuffer buffer = ByteBuffer.allocate(43);
        //节水工用水桶接水
        int bytesRead = channel.read(buffer);
        if (bytesRead != -1) {
            System.out.println("read: " + bytesRead);
            //接水工准备倒出水桶里的水(能够倒出刚才接的水)
            buffer.flip();
            //水桶是否还有水
            while (buffer.hasRemaining()) {
                System.out.println((char) buffer.get());
            }
            //倒掉剩余的水
            //compact(将剩余的水留在水桶中)
            buffer.clear();
            bytesRead = channel.read(buffer);
        }
        aFile.close();

NIO例子2

/**
* 服务器端
**/
public class NIOServer {
    //通道管理器
    private Selector selector;

    /**
     * 得到一个ServerSocket通道,并对该通道作一些初始化的工做
     *
     * @param port 绑定的端口号
     * @throws IOException
     */
    public void initServer(int port) throws IOException {
        // 得到一个ServerSocket通道
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        // 设置通道为非阻塞
        serverChannel.configureBlocking(false);
        // 将该通道对应的ServerSocket绑定到port端口
        serverChannel.socket().bind(new InetSocketAddress(port));
        // 得到一个通道管理器
        this.selector = Selector.open();
        //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,
        //当该事件到达时,selector.select()会返回,若是该事件没到达selector.select()会一直阻塞。
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    /**
     * 采用轮询的方式监听selector上是否有须要处理的事件,若是有,则进行处理
     *
     * @throws IOException
     */
    @SuppressWarnings("unchecked")
    public void listen() throws IOException {
        System.out.println("服务端启动成功!");
        // 轮询访问selector
        while (true) {
            //当注册的事件到达时,方法返回;不然,该方法会一直阻塞
            selector.select();
            // 得到selector中选中的项的迭代器,选中的项为注册的事件
            Iterator ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = (SelectionKey) ite.next();
                // 删除已选的key,以防重复处理
                ite.remove();
                // 客户端请求链接事件
                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key
                            .channel();
                    // 得到和客户端链接的通道
                    SocketChannel channel = server.accept();
                    // 设置成非阻塞
                    channel.configureBlocking(false);

                    //在这里能够给客户端发送信息哦
                    channel.write(ByteBuffer.wrap(new String("向客户端发送了一条信息").getBytes()));
                    //在和客户端链接成功以后,为了能够接收到客户端的信息,须要给通道设置读的权限。
                    channel.register(this.selector, SelectionKey.OP_READ);

                    // 得到了可读的事件
                } else if (key.isReadable()) {
                    read(key);
                }

            }

        }
    }

    /**
     * 处理读取客户端发来的信息 的事件
     *
     * @param key
     * @throws IOException
     */
    public void read(SelectionKey key) throws IOException {
        // 服务器可读取消息:获得事件发生的Socket通道
        SocketChannel channel = (SocketChannel) key.channel();
        // 建立读取的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        channel.read(buffer);
        byte[] data = buffer.array();
        String msg = new String(data).trim();
        System.out.println("服务端收到信息:" + msg);
        ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
        channel.write(outBuffer);// 将消息回送给客户端
    }

    /**
     * 启动服务端测试
     *
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        NIOServer server = new NIOServer();
        server.initServer(8001);
        server.listen();
    }
}

/**
* 客户端
**/
public class NIOClient {
    //通道管理器
    private Selector selector;

    /**
     * 得到一个Socket通道,并对该通道作一些初始化的工做
     * @param ip 链接的服务器的ip
     * @param port  链接的服务器的端口号
     * @throws IOException
     */
    public void initClient(String ip,int port) throws IOException {
        // 得到一个Socket通道
        SocketChannel channel = SocketChannel.open();
        // 设置通道为非阻塞
        channel.configureBlocking(false);
        // 得到一个通道管理器
        this.selector = Selector.open();

        // 客户端链接服务器,其实方法执行并无实现链接,须要在listen()方法中调
        //用channel.finishConnect();才能完成链接
        channel.connect(new InetSocketAddress(ip,port));
        //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。
        channel.register(selector, SelectionKey.OP_CONNECT);
    }

    /**
     * 采用轮询的方式监听selector上是否有须要处理的事件,若是有,则进行处理
     * @throws IOException
     */
    @SuppressWarnings("unchecked")
    public void listen() throws IOException {
        // 轮询访问selector
        while (true) {
            selector.select();
            // 得到selector中选中的项的迭代器
            Iterator ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = (SelectionKey) ite.next();
                // 删除已选的key,以防重复处理
                ite.remove();
                // 链接事件发生
                if (key.isConnectable()) {
                    SocketChannel channel = (SocketChannel) key
                            .channel();
                    // 若是正在链接,则完成链接
                    if(channel.isConnectionPending()){
                        channel.finishConnect();

                    }
                    // 设置成非阻塞
                    channel.configureBlocking(false);

                    //在这里能够给服务端发送信息哦
                    channel.write(ByteBuffer.wrap(new String("向服务端发送了一条信息").getBytes()));
                    //在和服务端链接成功以后,为了能够接收到服务端的信息,须要给通道设置读的权限。
                    channel.register(this.selector, SelectionKey.OP_READ);

                    // 得到了可读的事件
                } else if (key.isReadable()) {
                    read(key);
                }

            }

        }
    }
    /**
     * 处理读取服务端发来的信息 的事件
     * @param key
     * @throws IOException
     */
    public void read(SelectionKey key) throws IOException{
        //和服务端的read方法同样
    }


    /**
     * 启动客户端测试
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        NIOClient client = new NIOClient();
        client.initClient("localhost",8001);
        client.listen();
    }

}
相关文章
相关标签/搜索