NIO简介

2021-12-13  本文已影响0人  jhon_11

BIO与NIO的区别

NIO 和 BIO 的区别主要体现在三个方面:

NIO BIO
基于缓冲区( Buffer ) 基于流( Stream )
非阻塞 IO 阻塞 IO
选择器( Selector )
/**
 * 模拟一个客户端
 */
public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket=null;
        try {
            socket=new Socket("127.0.0.1",8080);
            socket.getOutputStream().write("111".getBytes());
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            socket.close();
        }
    }
}
/**
 * 通过Socket模拟一个客户端
 */
public class QQServer {
    /**
     * 定义一个字节数组用于接收客户端内容
     */
    static byte[] bytes=new byte[1024];

    public static void main(String[] args) {
        try {
            ServerSocket serverSocket=new ServerSocket();
            serverSocket.bind(new InetSocketAddress(8080));
            //这是一个阻塞方法,直到有客户端进行通信
            Socket socket=serverSocket.accept();
            //这是一个阻塞方法
            socket.getInputStream().read(bytes);
            System.out.println("读到内容打印");
            String content=new String(bytes);
            System.out.println(content);

        }catch (IOException ex){

        }
    }
}

NIO代码层原理:逻辑代码

List<Socket> list=new ArrayList<>();
public void main() throws IOException {
    ServerSocket serverSocket=new ServerSocket();
    serverSocket.bind(new InetSocketAddress(8080));
    /**
     * 设置服务器的accept方法为非阻塞方法,防止没人连接不会一直等待
     */
    serverSocket.setConfig(false);
    while (true){
        //接受一个连接
        Socket socket=serverSocket.accept();
        //如果没有连接
        if(socket==null){
            System.out.println("没人来连接");
            //查看前面的socket有没有客户端发送消息
            for(Socket socket1:list){
                int read=socket1.getInputStream().read(bytes);
                //如果前面的客户端有发送消息,则处理消息
                if(read!=0){
                    //logic
                }
            }

        }else {//如果有人来连接
            //设置socket为非阻塞方法
            socket.setConfig(false);
            //将当前连接的socket加入List集合
            list.add(socket);
            //同样处理前面逻辑,看前面的socket是否发送消息以及现在的socket是否发送消息。
            for(Socket socket1:list){
                int read=socket1.getInputStream().read(bytes);
                //如果前面的客户端有发送消息,则处理消息
                if(read!=0){
                    //logic
                }
            }
        }
    }
}

实现代码

public class QQNIOServer {
    /**
     * 字节数组存储读取的数据
     */
    static byte[] bytes=new byte[1024];
    /**
     * 非阻塞Socket对象集合,用于存储所有的socket
     */
    static List<SocketChannel> list=new ArrayList<>();
    /**
     * 申请一个堆外内存,在虚拟机中也称为直接内存
     */
    static ByteBuffer byteBuffer=ByteBuffer.allocate(512);

    public static void main(String[] args) {
        try {
            //监听开始
            //打开服务端非阻塞Socket
            ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
            //绑定监听端口号
            serverSocketChannel.bind(new InetSocketAddress(8080));
            //设置为非阻塞,默认为阻塞
            serverSocketChannel.configureBlocking(false);
            while (true){
                //拿到客户端的socket流
                SocketChannel socketChannel=serverSocketChannel.accept();
                //如果没有客户端连接服务器
                if (socketChannel==null){
                    //处理业务逻辑
                    Thread.sleep(500);
                    System.out.println("no conn");
                    //查看前面连接的socket是否有发送消息
                    for(SocketChannel client:list){
                        int read=client.read(byteBuffer);
                        if(read>0){
                            //该方法表明将从缓存的头开始读,并且读取所读数据的长度,不读取整个缓存,该方法必有!!!
                            byteBuffer.flip();
                            System.out.println(byteBuffer.toString());
                        }
                    }

                }else {//当得到一个连接
                    System.out.println("has a conn");
                    socketChannel.configureBlocking(false);
                    list.add(socketChannel);
                    //查看前面连接的socket是否有发送消息
                    for(SocketChannel client:list){
                        int read=client.read(byteBuffer);
                        if(read>0){
                            //该方法表明将从缓存的头开始读,并且读取所读数据的长度,不读取整个缓存,该方法必有!!!
                            byteBuffer.flip();
                            System.out.println(byteBuffer.toString());
                        }
                    }
                }
            }
        }catch (Exception e){

        }
    }
}

问题:List集合大部分连接没用,做了没必要的轮询,故此,解决方案是轮询不能交给我的系统,要主动感知有数据的socket,在linux上有epoll函数,它会告诉你当前来的是什么socket,但在windows上使用的select函数,同样在内核做轮询。所以Socket的性能与操作系统有关

基于 Buffer 与基于 Stream

BIO 是面向字节流或者字符流的,而在 NIO 中,它摒弃了传统的 IO 流,而是引入 Channel 和 Buffer 的概念:从 Channel 中读取数据到 Buffer 中,或者将数据从 Buffer 中写到 Channel 中。

① 那么什么是基于 Stream呢?

在一般的 Java IO 操作中,我们以流式的方式,顺序的从一个 Stream 中读取一个或者多个字节,直至读取所有字节。因为它没有缓存区,所以我们就不能随意改变读取指针的位置。

② 那么什么是基于 Buffer 呢?参考http://ifeve.com/buffers/

基于 Buffer 就显得有点不同了。我们在从 Channel 中读取数据到 Buffer 中,这样 Buffer 中就有了数据后,我们就可以对这些数据进行操作了。并且不同于一般的 Java IO 操作那样是顺序操作,NIO 中我们可以随意的读取任意位置的数据,这样大大增加了处理过程中的灵活性。

阻塞与非阻塞 IO

Java IO 的各种流是阻塞的 IO 操作。这就意味着,当一个线程执行读或写 IO 操作时,该线程会被阻塞,直到有一些数据被读取,或者数据完全写入。

Java NIO 可以让我们非阻塞的使用 IO 操作。例如:
当一个线程执行从 Channel 执行读取 IO 操作时,当此时有数据,则读取数据并返回;当此时无数据,则直接返回而不会阻塞当前线程。
当一个线程执行向 Channel 执行写入 IO 操作时,不需要阻塞等待它完全写入,这个线程同时可以做别的事情。
也就是说,线程可以将非阻塞 IO 的空闲时间用于在其他 Channel 上执行 IO 操作。所以,一个单独的线程,可以管理多个 Channel 的读取和写入 IO 操作。

Selector

Java NIO 引入 Selector ( 选择器 )的概念,它是 Java NIO 得以实现非阻塞 IO 操作的最最最关键。
我们可以注册多个 Channel 到一个 Selector 中。而 Selector 内部的机制,就可以自动的为我们不断的执行查询( select )操作,判断这些注册的 Channel 是否有已就绪的 IO 事件( 例如可读,可写,网络连接已完成 )。
通过这样的机制,一个线程通过使用一个 Selector ,就可以非常简单且高效的来管理多个 Channel 了。

上一篇 下一篇

猜你喜欢

热点阅读