Java NIO初体验

概述

由于BIO(同步阻塞IO)对系统资源的浪费较大。Java1.4中引⼊了NIO框架,在java.nio包中提供了Channel、Selector、Buffer等抽象类,可以快速构建多路复⽤的IO程序,⽤于提供更接近操作系统底层的⾼性能数据操作⽅式。

NIO(Non Blocking IO)是同步⾮阻塞的IO,服务器可以使⽤⼀个线程来处理多个客户端请求,客户端发送的请求会注册到多路复⽤器Selector上,由多路复⽤器Selector轮询各客户端的请求并进⾏处理。

NIO与BIO的比较

  • BIO以流的方式处理数据,而NIO以块的方式处理数据,块IO的效率比流IO高得多

  • BIO是阻塞的,NIO是非阻塞的

  • BIO基于字节流和字符流进行操作,而NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区中写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求、读写请求等),因此使用单个线程就可以监听多个客户端的通道

NIO可以先将数据写入到缓冲区,然后再由缓冲区写入通道,因此可以做到同步非阻塞

BIO是面向流的,读写数据都是单向的。因此是同步阻塞。

NIO线程模型

NIO包含三⼤组件:

  • Channel通道:每个通道对应⼀个buffer缓冲区

  • Buffer缓冲区:buffer底层是数组,类似于蓄⽔池,channel就是⽔管

  • Selector选择器:selector对应⼀个或多个线程。channel会注册到selector上,由selector根据channel读写事件的发⽣交给某个空闲线程来执⾏。

  • Buffer和Channel都是既可读也可写。

Java NIO初体验_第1张图片

NIO初体验

  • 服务端程序

    package com.my.io.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;
    ​
    /**
     * @author zhupanlin
     * @version 1.0
     * @description: NIO服务端
     * @date 2024/1/24 17:23
     */
    public class NIOServer {
    ​
        public static void main(String[] args) throws IOException {
            // 创建ServerSocketChannel对象
            ServerSocketChannel ssc = ServerSocketChannel.open();
            // 设置为非阻塞的方式
            ssc.configureBlocking(false);
            // 设置服务端程序端口
            ssc.socket().bind(new InetSocketAddress(9090));
            // 创建Selector多路复用器
            Selector selector = Selector.open();
            // 把ServerSocketChannel对象注册到Selector上,标识该channel只做客户端连接服务端的事情
            // selectionKey标识唯一的channel
            SelectionKey selectionKey = ssc.register(selector, SelectionKey.OP_ACCEPT);
            
            while (true){
                System.out.println("等待事件发生");
                // 在事件发生之前一直阻塞,轮询监听所有注册到selector上的channel
                int select = selector.select();
                System.out.println("某个事件发生了");
                // 获得所有发生事件的channel
                Set selectionKeys = selector.selectedKeys();
                // 遍历所有channel
                Iterator iterator = selectionKeys.iterator();
                while (iterator.hasNext()){
                    // selectionKey==>对应着一个channel
                    SelectionKey key = iterator.next();
                    handle(key);
                    // 删除本次处理的key,防止重复处理
                    iterator.remove();
                }
            }
        }
    ​
        private static void handle(SelectionKey selectionKey) throws IOException {
            // 判断channel上发生了什么事件
            if (selectionKey.isAcceptable()){
                System.out.println("有客户端连接了");
                // 服务端处理客户端的连接,得到ServerSocketChannel,代表着服务端
                ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
                // 服务端接收客户端的连接,得到socketChannel,建立起了服务端和客户端具体的连接通道,(阻塞的)
                SocketChannel socketChannel = channel.accept();
                // 把socketChannel设置成非阻塞
                socketChannel.configureBlocking(false);
                // 把socketChannel注册到selector,只处理读事件的发生
                socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ);
            }else if (selectionKey.isReadable()){
                System.out.println("有客户端向服务端写数据");
                // 获得服务端和客户端之间的通道SocketChannel
                SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                // 创建Buffer,可用空间为1024B
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                // 通过socketChannel把数据读到buffer中,返回的是这一次读的字节个数
                // NIO非阻塞的体现,read本身就是非阻塞的,read方法在执行的时刻一定客户端写的操作发生了。
                int len = socketChannel.read(buffer);
                if (len != -1){
                    System.out.println("读到客户端的数据:" + new String(buffer.array(), 0, len));
                }
                // 服务端返回数据给客户端
                ByteBuffer byteBuffer = ByteBuffer.wrap("hello nio".getBytes());
                // 由通道channel去写数据
                socketChannel.write(byteBuffer);
                // 监听下一次事件,读或者写
                selectionKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
    ​
            }
        }
    }
  • 客户端程序

    package com.my.io.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.SocketChannel;
    import java.util.Iterator;
    import java.util.Set;
    ​
    /**
     * @author zhupanlin
     * @version 1.0
     * @description: NIO客户端
     * @date 2024/1/24 18:19
     */
    public class NIOClient {
    ​
        public static void main(String[] args) throws IOException {
            // 获得channel通道
            SocketChannel channel = SocketChannel.open();
            // 设置成非阻塞
            channel.configureBlocking(false);
            // 获得Selector
            Selector selector = Selector.open();
            // 客户端连接上服务端
            channel.connect(new InetSocketAddress("127.0.0.1", 9090));
            // 将channel注册到Selector上,并且监听连接事件
            // 由于是客户端主动去连服务端,所以要处理的事件便为连接事件
            channel.register(selector, SelectionKey.OP_CONNECT);
            // 轮询访问selector
            while (true){
                // 阻塞的,在客户端的角度这个方法只面向一个客户端的channel
                selector.select();
                Set selectionKeys = selector.selectedKeys();
                Iterator iterator = selectionKeys.iterator();
                while (iterator.hasNext()){
                    SelectionKey selectionKey = iterator.next();
                    // 处理任务
                    handle(selectionKey);
                    // 防止重复处理
                    iterator.remove();
                }
            }
        }
    ​
        private static void handle(SelectionKey selectionKey) throws IOException {
            SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
            if (selectionKey.isConnectable()){
                // 如果是正在连接,则完成连接
                if (socketChannel.isConnectionPending()){
                    // finishConnect这个方法执行才表示连接完成了
                    socketChannel.finishConnect();
                    // 设置成非阻塞的模式
                    socketChannel.configureBlocking(false);
                    // 给服务端发消息
                    ByteBuffer byteBuffer = ByteBuffer.wrap("hello server".getBytes());
                    socketChannel.write(byteBuffer);
                    // 获得服务端返回的数据
                    socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ);
                }
            }else if (selectionKey.isReadable()){
                // 读服务端返回的数据
                // 创建缓冲区
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                // 读缓冲区中的数据
                int len = socketChannel.read(buffer);
                if (len != -1){
                    System.out.println("服务端返回的数据:" + new String(buffer.array(), 0, len));
                }
    ​
            }
        }
    ​
    }
  • 通信逻辑

    Java NIO初体验_第2张图片

你可能感兴趣的:(java,nio)