浅谈NIO

一、NIO与BIO比较

同步非阻塞IO
与BIO比较:
1.在BIO中,每次建立一次连接,就会创建一个线程处理该连接,虽然可以使用线程池来管理,
但是每次建立TCP连接,销毁,消耗资源和性能,对应的线程也是如此。

2.与BIO相比,IO操作读写数据时,如果当前没有数据读写,线程会一直阻塞,直到有数据读写,不能做别的事情

对于第一点,NIO中采取channel来读写数据,提升性能
对于第二点,NIO中采取Reactor模式解决

二、NIO核心API

  • Channels
  1. 通道是对原 I/O 包中的流的模拟,可以使用它对数据进行读写,这里读写是按块进行,而不像传统流式读写按照字节进行
  2. 通道既可以读取也可以写入,不像流只能单向读或者写
  3. 通道读写都是与缓冲区交互。
  • Buffers
    缓冲区 三个变量
    1.position 写数据表示当前位置 读数据 向前移动至下一个可读数据位置
    2.limit 当前缓冲区空间,写模式下,limit等于Buffer的capacity,读模式时, limit表示你最多能读到多少数据
    3.capacity 缓冲区容量

  • Selectors
    选择器
    通道向Selectors注册事件,Selector监听Channel所感兴趣的事件
    事件通常使用SelectorKey表示:

  1. SelectionKey.OP_CONNECT 连接就绪
  2. SelectionKey.OP_ACCEPT 接收就绪
  3. SelectionKey.OP_READ 可读
  4. SelectionKey.OP_WRITE 可写

在Reactor模式中,通过事件轮询,不断遍历Selector,针对不同事件做不同处理,而这只需要一个线程就可以管理多个socket,不会让线程白白等待读写数据浪费时间。

在Proactor模式中,当检测到有事件发生时,会新起一个异步操作,然后交由内核线程去处理,当内核线程完成IO操作之后,发送一个通知告知操作已完成,异步IO模型采用的就是Proactor模式,也就是AIO。

三、NIO代码

客户端:

public class Client {
    private static String DEFAULT_HOST = "127.0.0.1";

    private static int DEFAULT_PORT = 6666;

    private static ClientHandle clientHandle;

    public static void start(){
        start(DEFAULT_HOST,DEFAULT_PORT);
    }

    public static synchronized void start(String ip,int port){
        if(clientHandle!=null)
            clientHandle.stop();
        clientHandle = new ClientHandle(ip,port);
        new Thread(clientHandle,"Server").start();
    }

    public static boolean sendMsg(String msg) throws Exception{
        if(msg.equals("q"))
            return false;
        clientHandle.sendMsg(msg);
        return true;
    }

    public static void main(String[] args){
        start();
    }
}

public class ClientHandle implements Runnable {
    private String host;
    private int port;
    private Selector selector;
    private SocketChannel socketChannel;
    private volatile boolean started;
    private MsgUtil msgUtil = new MsgUtil();


    public ClientHandle(String ip, int port) {
        this.host = ip;
        this.port = port;
        try {
            //创建选择器
            selector = Selector.open();
            //打开监听通道
            socketChannel = SocketChannel.open();
            //如果为 true,则此通道将被置于阻塞模式;如果为 false,则此通道将被置于非阻塞模式
            socketChannel.configureBlocking(false);//开启非阻塞模式
            started = true;
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    public void stop() {
        started = false;
    }

    @Override
    public void run() {

        try {
            doConnect();
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }

        msgUtil.dealWithSelector(started, selector, null, this);

        //selector关闭后会自动释放里面管理的资源
        if (selector != null)
            try {
                selector.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
    }

    public void handleInput(SelectionKey key) throws IOException {
        if (key.isValid()) {
            SocketChannel sc = (SocketChannel) key.channel();
            if (key.isConnectable()) {
                if (sc.finishConnect()) ;
                else System.exit(1);
            }
            //读消息
            if (key.isReadable()) {
                //创建ByteBuffer,并开辟一个1M的缓冲区
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                //读取请求码流,返回读取到的字节数
                int readBytes = sc.read(buffer);
                //读取到字节,对字节进行编解码
                if (readBytes > 0) {
                    //将缓冲区当前的limit设置为position=0,用于后续对缓冲区的读取操作
                    buffer.flip();
                    //根据缓冲区可读字节数创建字节数组
                    byte[] bytes = new byte[buffer.remaining()];
                    //将缓冲区可读字节数组复制到新建的数组中
                    buffer.get(bytes);
                    String result = new String(bytes, "UTF-8");
                    System.out.println("客户端收到消息:" + result);
                }
                //释放资源
                else if (readBytes < 0) {
                    key.cancel();
                    sc.close();
                }
            }
        }
    }

    private void doConnect() throws IOException {
        if (socketChannel.connect(new InetSocketAddress(host, port))) ;
        else socketChannel.register(selector, SelectionKey.OP_CONNECT);
    }

    //发送消息
    public void sendMsg(String msg) throws Exception {
        MsgUtil msgUtil = new MsgUtil();
        socketChannel.register(selector, SelectionKey.OP_READ);
        msgUtil.doWrite(socketChannel, msg);
    }
}

服务端

public class Server {
    private static int DEFAULT_PORT = 6666;
    private static ServerHandle serverHandle;

    public static void start(){
        start(DEFAULT_PORT);
    }
    public static synchronized void start(int port){
        if(serverHandle!=null)
            serverHandle.stop();
        serverHandle = new ServerHandle(port);

        new Thread(serverHandle,"Server").start();
    }
    public static void main(String[] args){
        start();
    }
}
public class ServerHandle implements Runnable {
    //选择器
    private Selector selector;
    //通道
    private ServerSocketChannel serverChannel;

    //是否遍
    private volatile boolean started;

    private MsgUtil msgUtil = new MsgUtil();

    /**
     * 构造方法
     *
     * @param port 指定要监听的端口号
     */
    public ServerHandle(int port) {
        try {
            //创建选择器
            selector = Selector.open();
            //打开监听通道
            serverChannel = ServerSocketChannel.open();
            //如果为 true,则此通道将被置于阻塞模式;如果为 false,则此通道将被置于非阻塞模式
            serverChannel.configureBlocking(false);//开启非阻塞模式
            //绑定端口 backlog设为1024
            serverChannel.socket().bind(new InetSocketAddress(port), 1024);
            //监听客户端连接请求
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            //标记服务器已开启
            started = true;
            System.out.println("服务器已启动,端口号:" + port);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    public void stop() {
        started = false;
    }

    @Override
    public void run() {
        //循环遍历selector
        msgUtil.dealWithSelector(started, selector, this, null);
        //selector关闭后会自动释放里面管理的资源
        if (selector != null)
            try {
                selector.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
    }

    public void handleInput(SelectionKey key) throws IOException {
        if (key.isValid()) {
            //处理新接入的请求消息
            if (key.isAcceptable()) {
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                //通过ServerSocketChannel的accept创建SocketChannel实例
                //完成该操作意味着完成TCP三次握手,TCP物理链路正式建立
                SocketChannel sc = ssc.accept();
                //设置为非阻塞的
                sc.configureBlocking(false);
                //注册为读
                sc.register(selector, SelectionKey.OP_READ);
            }
            //读消息
            if (key.isReadable()) {
                SocketChannel sc = (SocketChannel) key.channel();
                //创建ByteBuffer,并开辟一个1M的缓冲区
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                //读取请求码流,返回读取到的字节数
                int readBytes = sc.read(buffer);
                //读取到字节,对字节进行编解码
                if (readBytes > 0) {
                    //将缓冲区当前的limit设置为position=0,用于后续对缓冲区的读取操作
                    buffer.flip();
                    //根据缓冲区可读字节数创建字节数组
                    byte[] bytes = new byte[buffer.remaining()];
                    //将缓冲区可读字节数组复制到新建的数组中
                    buffer.get(bytes);
                    String expression = new String(bytes, "UTF-8");
                    String result = "服务端处理数据: " + expression;
                    System.out.println("服务器收到消息:" + expression);
                    msgUtil.doWrite(sc, result);
                }
                //释放资源
                else if (readBytes < 0) {
                    key.cancel();
                    sc.close();
                }
            }
        }
    }

}

工具类:

public class MsgUtil {

    //异步发送消息
    public void doWrite(SocketChannel channel, String request) throws IOException {
        //将消息编码为字节数组
        byte[] bytes = request.getBytes();
        //根据数组容量创建ByteBuffer
        ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
        //将字节数组复制到缓冲区
        writeBuffer.put(bytes);
        //flip操作
        writeBuffer.flip();
        //发送缓冲区的字节数组
        channel.write(writeBuffer);
    }

    public void dealWithSelector(boolean started, Selector selector, ServerHandle serverHandle, ClientHandle clientHandle) {
        while (started) {
            try {
                //无论是否有读写事件发生,selector每隔1s被唤醒一次
                selector.select(1000);
                //阻塞,只有当至少一个注册的事件发生的时候才会继续.
//              selector.select();
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> it = keys.iterator();
                SelectionKey key;
                //循环注册器事件.
                while (it.hasNext()) {
                    key = it.next();
                    it.remove();
                    try {
                        if (serverHandle != null)
                            serverHandle.handleInput(key);
                        if (clientHandle != null)
                            clientHandle.handleInput(key);
                    } catch (Exception e) {
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null) {
                                key.channel().close();
                            }
                        }
                    }
                }
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }
    }
}

public class Test {
    //测试主方法
    @SuppressWarnings("resource")
    public static void main(String[] args) throws Exception {
        //运行服务器
        Server.start();
        //避免客户端先于服务器启动前执行代码
        Thread.sleep(100);
        //运行客户端
        Client.start();
        while (Client.sendMsg(new Scanner(System.in).nextLine())) ;
        System.out.println("发送消息结束");
    }
}

注:
NIO要想真正说清楚,可能需要写一本书,很多细节我也不懂,本文哪里有问题请指出,本文属于新手入门级别。

猜你喜欢

转载自blog.csdn.net/LJJZJ/article/details/88067166
NIO
今日推荐