Java NIO系列之[前世今生]

这节重点探讨两个概念性的问题:什么是NIO?为什么要用NIO?

在引入NIO之前,有必要聊下BIO,因为NIO是相对于BIO所提出的新的Java IO api,但这里不会深入,每本java书籍都会介绍javaIO的。

BIO:blocking IO,即阻塞IO,是java的传统IO api,以流的方式处理数据,一般可分为文件IO(处理文件)和网络IO(Socket网络编程),这里重点探讨网络IO,通过一个小的例子,让大家了解阻塞IO的具体含义。
服务端代码:(代码中用到java8中的try-with-resources写法,可以自动关闭流和socket连接,jdk1.7就有了try-with-resources)

public class BIOServer {
    public static void main(String[] args) {

        while (true){
            try(ServerSocket serverSocket = new ServerSocket(9000); //创建ServerSocket对象
                //监听客户端的连接请求
                final Socket socket= serverSocket.accept();  //当没有客户端连接时,就会阻塞到这里,一直等待客户端的连接请求,下面的代码不会执行
                //从连接中取出输入流来接收客户端发来的消息
                InputStream is = socket.getInputStream(); //阻塞
                //从连接中取出输出流回应客户端
                OutputStream os = socket.getOutputStream()) {
                System.out.println("我是风清扬"); //在客户端启动之前,不会打印输出,因为前面的accept()方法已经使线程阻塞,等待客户端连接,客户端启动后,该语句执行
                //读取消息
                byte[] b = new byte[1024];
                int len;
                String clientIP = socket.getInetAddress().getHostAddress();
                while ((len = is.read(b)) != -1){ //当没有读取到客户端发送的消息时,就会阻塞到这里,InputStream.read()方法下面的代码不会执行
                    System.out.println(clientIP + "说:" + new String(b,0,len));
                }
                System.out.println("我是令狐冲");   //该语句在客户端没有发送消息前不会执行,只有上面的read()方法接收到服务端发送的消息时才会执行该语句
                //发送消息
                Scanner scanner = new Scanner(System.in);
                String msg = scanner.nextLine();
                os.write(msg.getBytes());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

客户端代码:

public class BIOClient {
    public static void main(String[] args) {
        while (true){
            try(Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9000); // 创建Socket对象
                //从连接中取出输入流读消息
                final InputStream is = socket.getInputStream();  //阻塞
                //从连接中取出输出流发消息
                final OutputStream os = socket.getOutputStream()) {

                System.out.println("请输入:");
                Scanner scanner = new Scanner(System.in);
                String msg = scanner.nextLine();
                os.write(msg.getBytes());
                socket.shutdownOutput();
                byte[] b = new byte[1024];
                int len;
                while ((len = is.read(b)) != -1){ //当没有读取到服务端发送的消息时,就会阻塞到这里,InputStream.read()方法下面的代码不会执行
                    System.out.println("服务端说:" + new String(b));
                }
                System.out.println("我是逍遥子"); //该语句在服务端没有发送消息前不会执行,只有上面的read()方法接收到服务端发送的消息时才会执行该语句
            } catch (UnknownHostException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果分析:
BIOServer控制台输出:
Java NIO系列之[前世今生]_第1张图片
BIOClient控制台输出:
Java NIO系列之[前世今生]_第2张图片
一、什么是NIO:

百度百科对NIO的解释如下:
在这里插入图片描述
NIO:non-blocking IO,即非阻塞IO,以块的方式处理数据,相比于BIO用流的方式处理数据,块IO的效率要比流IO高很多,有的书籍也叫做新IO(New IO),这里我们与BIO进行比较,non-blocking IO更加语义化,能够突出对比性。JDK1.4中,新增了许多新的用于处理输入输出的类,这些类放在java.nio包下(java11是放在java.base模块下的java.nio包中):
Java NIO系列之[前世今生]_第3张图片
NIO提出了三大核心的概念:Channel(通道)、Buffer(缓冲区)、Selector(选择器),后面的内容也是主要围绕这三个核心概念展开,再适当加一些charset字符集的东西。相对于BIO基于 字节流和字符流 对数据进行操作,NIO是基于Channel和Buffer对数据进行操作。读数据的时候先将数据从通道读取到缓冲区,写数据的时候要将数据从缓冲区写如到通道,Selector用于监听多个通道的事件,从而使单个线程就可以监听多个客户端。下面通过安倍和特朗普通电话的事例代码(本人属于段子手类型,平时扫地,偶尔开车,如果例子举的有不当的地方,还请大家包涵),让大家了解NIO相对于BIO的非阻塞的特性,涉及到的api不了解也没关系,后面会详细介绍,可以把代码运行一遍,体会下NIO的非阻塞特性。
客户端代码:

public class NioClient {
    public static void main(String[] args) throws IOException {

         //1.得到一个通道
        final SocketChannel channel = SocketChannel.open();

        //2.设置非阻塞方式,默认是阻塞方式,所以需要手动设置为非阻塞方式
        channel.configureBlocking(false);

        //3.设置服务端的ip和端口号
        InetSocketAddress address = new InetSocketAddress("127.0.0.1",9000);

        //4.连接服务器端
        if(!channel.connect(address)){ //当connect连接没有成功时,尝试再次连接服务端,但不能再用connect方法连接,此方法只能连接一次,再次连接需要用finishConnect()方法
            while (!channel.finishConnect()){ //当连接服务端时,客户端不会处于阻塞状态,还可以继续执行下面的代码
                System.out.println("安倍:在特朗普老兄的电话还未接通之前,小弟我可以干些其他的事情:哎呀,我大日本的网络有点令人担忧啊,半天了," +
                        "特朗普老兄的电话还是没有接通,这老家伙,不会在做些羞羞的事情吧,不行我还是先看个苍老师或是波多老师的剧情片吧,真有意思,先看再说!");
            }
        }

        //5.提供一个缓冲区并存入数据
        String msg = "您好,特朗普兄长,我是安倍,可否赏脸陪小弟喝个便茶,小弟最近有点迷茫啊,突然感觉没有了高潮!";
        final ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());

        //6..发送数据
        channel.write(buffer);

        //7.当程序执行到这里,已经执行完,channel就会关闭,服务器端就会抛出异常,所以让程序阻塞到这里
        System.in.read();
    }
}

服务端代码:

public class NioServer {
    public static void main(String[] args) throws IOException {

        //1.得到一个通道
        final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //2.得到一个Selector对象
        final Selector selector = Selector.open();

        //3.绑定端口号
        serverSocketChannel.bind(new InetSocketAddress(9000));

        //4.设置非阻塞方式
        serverSocketChannel.configureBlocking(false);

        //5.把ServerSocketChannel对象注册给Selector对象
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        //6.实现业务
        while (true){

            //6,1 一直监控客户端,直到有客户端连接上来
            if(selector.select(2000) == 0){
                System.out.println("特朗普:安倍这小子好几天就越好今天和我通话,现在也没来,不会干火星上去了吧,不管他了,我还是干别的事情吧:82年拉菲," +
                        "加拿大总理特鲁多小老弟送我的,我去找我的merry女秘书喝两杯,cheers,大爷我要嫖了!");
                continue;
            }

            //6.2 得到SelectionKey
            final Iterator selectionKeyIterator = selector.selectedKeys().iterator();
            while (selectionKeyIterator.hasNext()){
                final SelectionKey selectionKey = selectionKeyIterator.next();

                if(selectionKey.isAcceptable()){ //客户端连接事件
                    System.out.println("连接...............................................");
                    final SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }

                if(selectionKey.isReadable()){ //读取客户端数据事件
                    final SocketChannel channel = (SocketChannel) selectionKey.channel();
                    final ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();
                    channel.read(buffer);
                    System.out.println("客户端发来数据:" +new String(buffer.array()));
                }

                //6.3 手动从当前集合中移除key,防止重复处理
                selectionKeyIterator.remove();
            }

        }
    }
}

运行结果分析:
1.只启动服务端,不启动客户端
Java NIO系列之[前世今生]_第4张图片
2.只启动客户端,不启动服务端
Java NIO系列之[前世今生]_第5张图片
3.启动服务端和客户端
Java NIO系列之[前世今生]_第6张图片
通过以上的代码举例,想必大家对NIO的非阻塞特性应该有所理解。下面聊下NIO的今生,AIO。

AIO: asynchronous IO,即异步IO,并且是非阻塞的,也被称作NIO2,算是对NIO的增强和补充吧,在JDK1.7中新加入的,AIO最大的特性就是异步能力,jdk1.7新增三个异步通道,用于对异步IO的支持:

1.AsynchronousFileChannel: 用于文件异步读写;
2.AsynchronousSocketChannel: 客户端异步socket;
3.AsynchronousServerSocketChannel: 服务器异步socket。

二、为什么要用NIO:
NIO比普通的BIO提供了功能更加强大,处理数据更快的解决方案,大大提升IO的吞吐量,常用在高性能服务器上,在大多数涉及java高性能应用软件中,NIO是必不可少的技术之一,例如Netty就是封装了NIO,而互联网微服务架构中常用的Dubbo,Elasticsearch等中间件底层网络通信都用Netty实现,可见NIO对于高性能网络通信的作用。

三、总结:
这个小节由BIO引出了NIO,通过将NIO与BIO相比较,突出NIO非阻塞IO的优势,简单介绍了什么是NIO,回答了为什么要用NIO的疑问。

你可能感兴趣的:(Java,NIO)