NIOサンプル

##NIO介绍
###同步非阻塞IO （non-blocking IO / new io）是指JDK 1.4 及以上版本。
服务器实现模式为一个请求一个通道，即客户端发送的连接请求都会注册到多路复用器上，多路复用器轮询到连接
有IO请求时才启动一个线程进行处理。
###通道（Channels）
NIO 新引入的最重要的抽象是通道的概念。Channel 数据连接的通道。 数据可以从Channel读到Bu􀁷er中，也可以从
Bu􀁷er 写到Channel中 .
###缓冲区（Bu􀁷ers）
通道channel可以向缓冲区Bu􀁷er中写数据，也可以像bu􀁷er中存数据。
###选择器（Selector）
使用选择器，借助单一线程，就可对数量庞大的活动 I/O 通道实时监控和维护。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3783/c134affd-1318-018f-5f21-3fb4a0d90399.png)
###特点
当一个连接创建后，不会需要对应一个线程，这个连接会被注册到多路复用器，所以一个连接只需要一个线程即
可，所有的连接需要一个线程就可以操作，该线程的多路复用器会轮训，发现连接有请求时，才开启一个线程处
理。

```NIOServer.java
package com.lagou.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Scanner;

public class NIOServer extends Thread{

    //1.声明多路复用器
    private Selector selector;

    //2.定义读写缓冲区
    private ByteBuffer readBuffer = ByteBuffer.allocate(1024);
    private ByteBuffer writeBufer = ByteBuffer.allocate(1024);

    //3.定义构造方法初始化端口

    public NIOServer(int port) {
        init(port);
    }

    //4.main方法启动线程
    public static void main(String[] args) {
        new Thread(new NIOServer(8888)).start();
    }

    //5.初始化
    private void init(int port){
        try {
            System.out.println("服务器正在启动……");
            //1)开启多路复用器
            this.selector = Selector.open();
            //2)开启服务通道
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

            //3)设置为非阻塞
            serverSocketChannel.configureBlocking(false);

            //4)绑定端口
            serverSocketChannel.bind(new InetSocketAddress(port));

            //5)注册，标记服务连接状态为ACCEPT状态
            serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT);
            System.out.println("服务器启动完毕……");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true){
            try {
                //1.当有至少一个通道被选中，执行此方法
                this.selector.select();
                //2.获取选中的通道编号集合
                Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator();
                //3.遍历keys
                while(keys.hasNext()){
                    SelectionKey key = keys.next();
                    //4.当前key需要从通道集合中移出，下次循环会执行对应的逻辑，造成业务错乱
                    keys.remove();
                    //5.判断通道是否有效
                    if(key.isValid()){
                        try {
                            //6.判断是否可以连接
                            if (key.isAcceptable()) {
                                accept(key);
                            }
                        } catch (CancelledKeyException e){
                            //出现异常断开连接
                            key.cancel();
                        }
                        try{
                            //7.判断是否可读
                            if(key.isReadable()){
                                read(key);
                            }
                        }catch (CancelledKeyException e){
                            //出现异常断开连接
                            key.cancel();
                        }
                        try{
                            //8.判断是否可写
                            if(key.isWritable()){
                                //出现异常断开连接
                                write(key);
                            }
                        }catch (CancelledKeyException e){
                            key.cancel();
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //给同道中人写操作
    private void write(SelectionKey key) {
        //清空缓存
        this.readBuffer.clear();
        //获取当前通道对象
        SocketChannel channel = (SocketChannel) key.channel();
        //录入数据
        Scanner scanner = new Scanner(System.in);

        try{
            System.out.println("即将发送数据到客户端……");
            String line = scanner.nextLine();
            //把录入的数据写到Buffer中
            writeBufer.put(line.getBytes("UTF-8"));
            //重置缓存游标
            writeBufer.flip();
            channel.write(writeBufer);
            channel.register(this.selector,SelectionKey.OP_READ);

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //使用通道读取数据
    private void read(SelectionKey key) {
        try{
            //清空缓存
            this.readBuffer.clear();
            //获取当前通道对象
            SocketChannel channel = (SocketChannel) key.channel();
            //江通道的数据（客户端发送的data）读取到缓存中
            int readLen = channel.read(readBuffer);
            //如果通道中没有数据
            if(readLen == -1){
                //关闭通道
                key.channel().close();
                //关闭连接
                key.cancel();
                return;
            }
            //Buffer中有游标，游标不会重置，需要我们调用flip重置，否则读取不一致
            this.readBuffer.flip();
            //创建有效字节长度数组
            byte[] bytes = new byte[readBuffer.remaining()];
            //读取buffer中数据保存在字节数组
            readBuffer.get(bytes);
            System.out.println("收到了客户端"+channel.getRemoteAddress() +": " +new String(bytes,"UTF-8"));
            //注册通道，标记为写操作
            channel.register(this.selector,SelectionKey.OP_WRITE);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void accept(SelectionKey key) {
        try {
            //1.当前通道在init方法注册了selector中的ServerSocketChannel
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel();
            //2.阻塞方法，客户端发起后请求返回
            SocketChannel channel = serverSocketChannel.accept();
            //3.serverSocketChannel设置为非阻塞
            channel.configureBlocking(false);
            //4.设置对应客户端的通道标记，设置此通道为可读时适用
            channel.register(this.selector,SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

```

```NIOClient.java
package com.lagou.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class NIOClient {
    public static void main(String[] args) {
        //创建远程地址
        InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
        SocketChannel channel = null;
        //定义缓存
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        try {
            //开启通道
            channel = SocketChannel.open();
            //连接远程服务器
            channel.connect(address);
            Scanner sc = new Scanner(System.in);
            while (true){
                System.out.println("客户端即将给 服务器发送数据……");
                String line = sc.nextLine();
                if(line.equalsIgnoreCase("exit")){
                    break;
                }
                //控制台输入写数据到缓存
                buffer.put(line.getBytes("UTF-8"));
                //重置buffer游标
                buffer.flip();
                //数据发送
                channel.write(buffer);
                //清空缓存数据
                buffer.clear();

                //读取服务器返回的数据
                int readLen = channel.read(buffer);
                if(readLen == -1){
                    break;
                }
                //重置buffer游标
                buffer.flip();
                byte[] bytes = new byte[buffer.remaining()];
                //读取数据到字节数组
                buffer.get(bytes);
                System.out.println("收到了服务器发送的数据:" + new String(bytes, "UTF-8"));
                buffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(null != channel){
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

```