【Nio】01--初始Nio组件

时间:2022-07-25
本文章向大家介绍【Nio】01--初始Nio组件,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Nio学习第一天,三大组件

BIO 朝NIO的演变过程

只能支持一次的socket服务端

此种情况下,客户端发送一次数据以后,服务器端就会停止

public class OnceSocketTcpServer {
    static byte[] buffer = new byte[1024];
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(8080))
            Socket socket = serverSocket.accept();
            int read = socket.getInputStream().read(buffer);
            String result = new String(buffer);
            System.out.println(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
支持客户端多次发送请求

现在可以支持多个请求,但是,由于单线程执行,某个方法执行时间太久会造成程序的阻塞

public class OnceSocketTcpServer {
    static byte[] buffer = new byte[1024];
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(8080));
            while(true) {
                Socket socket = serverSocket.accept();
                int read = socket.getInputStream().read(buffer);
                String result = new String(buffer);
                System.out.println(result);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
现在继续改进,支持多线程, 即伪异步方式

在伪异步的情况下,开启多线程的方式来处理。但是,并发的连接数过大,开启的线程过多。cpu资源占用太多

public class OnceSocketTcpServer {
    static byte[] buffer = new byte[1024];
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(8080));
            while(true) {
                final Socket socket = serverSocket.accept();
                int read = socket.getInputStream().read(buffer);
                new Thread(new Runnable() {
                    public void run() {
                        String result = new String(buffer);
                        System.out.println(result);
                    }
                }).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
由于线程过多,我们采用线程池来处理

即时采用线程池的情况下,频繁的切换线程也会造成cpu资源的消耗和浪费

public class OnceSocketTcpServer {
    static byte[] buffer = new byte[1024];
    public static void main(String[] args) {
        try {
            ExecutorService executorService = Executors.newCachedThreadPool();
            ServerSocket serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(8080));
            while(true) {
                final Socket socket = serverSocket.accept();
                int read = socket.getInputStream().read(buffer);
                executorService.execute(new Runnable() {
                    public void run() {
                        String result = new String(buffer);
                        System.out.println(result);
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

什么是NIO

三个概念:

  • channel管道
  • Selector选择器
  • Buffer缓冲区

这里先抛砖引玉,为什么redis是单线程,却能够支持高并发

我们需要提到一个概念,多路IO复用机制,这是什么意思呢?

多路:实际上指的就是多个Tcp连接,即上述的多个管道

IO复用:将多个Tcp连接(管道)统一交给一个Selector选择器进行管理。最后,统一使用buffer将数据写入硬盘。

buffer的作用是什么呢

每次单独写入磁盘,效率会特别底下,所以,将数据汇集的多一些以后一起写入,可以提高效率

Bio和Nio到底有什么区别

Bio

在数据没有内核空间的时候,程序会一直阻塞,这个时候cpu放弃了使用权,不能干其他事情

Nio

不管有没有拿到数据都会立即返回结果,如果返回的结果没有数据,会循环请求数据,如果拿到了数据,程序继续执行。这种情况下程序并不会阻塞

Nio、Selector、Channel、Buffer原理

  • Nio: 因为上面已经讲述了,这里就不在赘述了
  • Selector: Selector选择器,也可以叫做多路复用器,在单线程的情况下维护多个不同的channel
  • Channel:客服端传输的数据都必须经过管道,统一注册到selector中管理
  • Buffer:BIO是按照字节来写入,效率低下。 缓冲区,将数据添加到缓冲区中,一次性写入,效率更高

Nio架构流程图

Nio的实现步骤

此种实现方式还有一些缺陷,如果客户端断开连接,需要将该连接从Selector选择器中移除。

当然,此处实现还是颇有问题,当某些客户端已经连接上,即存在于Selector中,但是,一直没有发送消息,如果这样的连接过多,也会造成大量的资源浪费。

public class OnceSocketTcpServer {
    static ByteBuffer byteBuffer = ByteBuffer.allocate(512);
    static List<SocketChannel> selectors =  new ArrayList<>();
    public static void main(String[] args) {
        try {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(8080));
            // 设置为异步接收
            serverSocketChannel.configureBlocking(false);
            while(true) {
                // 接收管道内的数据
                SocketChannel socketChannel = serverSocketChannel.accept();
                // 如果数据不为空,则讲管道添加到selector选择器中
                if (socketChannel != null) {
                    socketChannel.configureBlocking(false);
                    selectors.add(socketChannel);
                }
                // 遍历选择器中的所有管道,看是否已经发送数据
                for (SocketChannel sel : selectors) {
                    int j = sel.read(byteBuffer);
                    if (j > 0) {
                        byteBuffer.flip(); 
                        byte[] bytes = Arrays.copyOf(byteBuffer.array(), byteBuffer.limit());
                        System.out.println("已经获取到了数据" + new String(bytes));
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

IO复用机制、信号驱动IO、异步IO的区别

  • IO复用机制:将多个Tcp连接加载到管道中,然后,交由选择器管理,接收到消息以后统一写入硬盘
  • 信号驱动IO:采用事件驱动的方式,有回调来通知
  • 异步IO,即AIO