NIO(同步、非阻塞IO)

(补充)关键词

  • 同步和异步

        同步是一种可靠的有序运行机制,当我们进行同步操作时,后续的任务是等待当前调用返回,才会进行下一步;

        而异步则相反,其他任务不需要等待当前调用返回,通常依靠事件、回调等机制来实现任务间次序关系

  • 阻塞与非阻塞

        在进行阻塞操作时,当前线程会处于阻塞状态,无法从事其他任务,只有当条件就绪才能继续,比如ServerSocket新连接建立完毕,或者数据读取、写入操作完成;

         非阻塞则是不管IO操作是否结束,直接返回,相应操作在后台继续处理

  • BIO/NIO/AIO
    package cn.tedu;
    
    import java.nio.ByteBuffer;
    
    public class ByteBufferDemo {
        public static void main(String[] args) {
            // 创建ByteBuffer
            // 创建的时候需要指定容量,实际上就是给底层的数组来指定容量
            ByteBuffer buffer = ByteBuffer.allocate(10);
    
            // 添加数据
            buffer.put("abc".getBytes());
            buffer.put("def".getBytes());
            buffer.flip();
            byte[] bs = buffer.array();
            System.out.println(new String(bs, 0, buffer.limit()));
            // 获取数据
            // byte b = buffer.get();
            // System.out.println(b);
    
            // 如果要遍历这个缓冲区,需要先挪动limit,然后挪动position
            // 翻转缓冲区
            buffer.flip();
            /*
            buffer.flip();执行这个方法等同于执行下面的一段代码
                 先将limit挪到position上
                 buffer.limit(buffer.position());
                 将position归零
                buffer.position(0);
                如果position和limit重合,就表示所有的元素已经取完
                while (buffer.position() < buffer.limit())
             */
            // while (buffer.position() < buffer.limit()) {
            while (buffer.hasRemaining()) {
                System.out.println(buffer.get());
            }
        }
    
    }

    BIO - BlockingIO - 同步阻塞式IO

               BIO的缺点:

               a. 阻塞:相比非阻塞而言,阻塞的效率是相对较低的

               b. 一对一的连接:每过来一个客户端,就需要在服务器端去创建一个线程去处理这个请求。这个过程中伴随着大量的             线程的创建和销毁浪费CPU;如果同一时间内,大量客户端产生连接,那么这个时候服务器就得创建大量线程去处理,导           致服务器卡顿甚至崩溃

              c. 当客户端连接到服务器之后,即使客户端不发生任何操作,连接依然会保持,这就会导致服务器端的线程会一直被占用

        NIO - NewIO - NonBlockingIO - 同步非阻塞式IO

        (目前用的比较少) AIO - AsynchronousIO - 异步非阻塞式IO - JDK1.8 - AIO是在NIO的基础上进行了改进,所以AIO又称为NIO.2

NIO

一:NIO的特点

  1. 一对多的连接方式:利用一个或者少量线程处理大量的连接请求,降低服务器端的压力
  2. 非阻塞:在线程不能进行read或者write方法的时候,立即返回0,等待下一次操作
  3. 双向传输:利用通道可以实现数据的双向

二:NIO的缺点

  1. 在请求量比较大的情况下会出现部分请求的响应时间比较长的现象
  2. 不适用于长任务场景,不然会导致其他的请求无法处理

三:NIO和BIO的比较

BIO

NIO

同步阻塞

同步非阻塞

单向传输数据

可以双向传输数据

一对一的连接方式

一对多的连接方式

面向流操作

面向缓冲区操作

适合于请求少、长连接场景

适合于大量请求、短连接的场景

NIO(同步、非阻塞IO)_第1张图片

NIO(同步、非阻塞IO)_第2张图片

 

四:NIO的三大组件

 

NIO(同步、非阻塞IO)_第3张图片

1. Buffer 

  • Buffer又称之为缓冲区,是用于存储数据的容器

  • 本质上是一个数组,在内存中占用了一块连续的空间

  • Java中针对不同的基本类型提供了不同的缓冲区(但需注意的是没有boolean类型对应的缓冲区),其中最基本的字节缓冲区ByteBuffer

    ByteBuffer

  •  概述
  1. 字节缓冲区,继承了Buffer类
  2. 底层是依靠字节数组来存储数据
  3. 本身是一个抽象类,需要利用其子类创建对象或者是利用其提供的allocate或者是wrap方法来创建ByteBuffer对象
  4. 重要位置:capacity >= limit >= position >= mark
  •  重要位置
  1. capacity:容量位。用于标记该缓冲区的容量,在缓冲区创建好之后就不再改变
  2. limit:限制位。用于限制操作位position所能达到的最大位置。在缓冲区刚创建的时候指向容量位
  3. position:操作位。用于指向要操作的位置,实际意义类似于数组中的下标。在缓冲区刚创建的时候指向0
  4. mark:标记位。用于进行标记。在缓冲区刚创建的时候指向-1,默认不启用
  5. 可以看下面图,allocate()、put()、fip()

NIO(同步、非阻塞IO)_第4张图片

package cn.tedu;

import java.nio.ByteBuffer;

public class ByteBufferDemo {
    public static void main(String[] args) {
        // 创建ByteBuffer
        // 创建的时候需要指定容量,实际上就是给底层的数组来指定容量
        ByteBuffer buffer = ByteBuffer.allocate(10);

        // 添加数据
        buffer.put("abc".getBytes());
        buffer.put("def".getBytes());
        buffer.flip();
        byte[] bs = buffer.array();
        System.out.println(new String(bs, 0, buffer.limit()));
        // 获取数据
        // byte b = buffer.get();
        // System.out.println(b);

        // 如果要遍历这个缓冲区,需要先挪动limit,然后挪动position
        // 翻转缓冲区
        buffer.flip();
        /*
        buffer.flip();执行这个方法等同于执行下面的一段代码
             先将limit挪到position上
             buffer.limit(buffer.position());
             将position归零
            buffer.position(0);
            如果position和limit重合,就表示所有的元素已经取完
            while (buffer.position() < buffer.limit())
         */
        // while (buffer.position() < buffer.limit()) {
        while (buffer.hasRemaining()) {
            System.out.println(buffer.get());
        }
    }

}

//执行结果
//abcdef

 NIO(同步、非阻塞IO)_第5张图片

Channel

  •     概念
  1. Channel,称之为通道,在NIO中用于完成数据的传输
  2. 在操作的时候是面向缓冲区进行的
  3. 可以实现数据的双向传输
  4. Channel默认是阻塞的,可以手动设置为非阻塞(默认是true,可以改为false)  
  5. 三种通道:FileChannel、TCP、UDP

                        FileChannel:从文件读取数据的

                       DatagramChannel:读写UDP网络协议数据

                       SocketChannel:读写TCP网络协议数据

                       ServerSocketChannel:可以监听TCP连接

  •   TCP通道
  1. 用于进行TCP通信的通道
  2. 需要进行连接的网络协议
  3. 提供了连接、接收、读取、写入操作
  4. 客户端通道是SocketChannel,服务器端通道是ServerSocketChannel
package cn.tedu.channel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class SocketServer {

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

        // 开启服务器端的通道
        ServerSocketChannel ssc = ServerSocketChannel.open();
        // 绑定端口
        ssc.bind(new InetSocketAddress(8090));
        ssc.configureBlocking(false);
        // 接受连接
        SocketChannel sc = ssc.accept();
        while (sc == null)
            sc = ssc.accept();
        // 读取数据
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        sc.read(buffer);
        System.out.println(new String(buffer.array(), 0, buffer.position()));

        sc.write(ByteBuffer.wrap("hello wyf".getBytes()));
        // 关流
        ssc.close();
    }

}

//输出
//hello server
package cn.tedu.channel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class SocketClient {

    public static void main(String[] args) throws IOException, InterruptedException {

        // 开启客户端通道
        SocketChannel sc = SocketChannel.open();
        // 设置为非阻塞
        sc.configureBlocking(false);
        // 发起连接
        sc.connect(new InetSocketAddress("localhost", 8090));
        while (!sc.isConnected())
            // 试图再次建立连接并且自动计数
            // 当达到底层的计数阈值之后,就会认为这个连接无法建立
            // 这个时候就会报错
            sc.finishConnect();
        // 写出数据
        sc.write(ByteBuffer.wrap("hello server".getBytes()));
        Thread.sleep(7);
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        sc.read(buffer);
        System.out.println(new String(buffer.array(), 0, buffer.position()));
        // 关流
        sc.close();
    }

}

//输出
//hello wyf

NIO(同步、非阻塞IO)_第6张图片

   Selector

     概述:

  1. Selector。称之为多路复用选择器
  2. 对通道进行选择,需要基于事件进行驱动
  3. 针对了四类事件:connect、accept、read、write,四类事件定义在SelectionKey中
  4. 可以实现利用一个或者少量线程处理大量请求
  5. 适用于大量的段任务场景,不适用于长任务场景
  6. Selector针对的必须是非阻塞的通道

    

package cn.tedu.selector;

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;

public class Server {

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

        // 开启服务器的通道
        ServerSocketChannel ssc = ServerSocketChannel.open();
        // 绑定端口
        ssc.bind(new InetSocketAddress(8070));
        // 设置为非阻塞
        ssc.configureBlocking(false);
        // 开启选择器
        Selector selc = Selector.open();
        // 将服务器注册到选择器上
        ssc.register(selc, SelectionKey.OP_ACCEPT);

        while (true) {

            // 进行选择
            selc.select();
            // 获取选择出来的事件
            Set keys = selc.selectedKeys();
            // 遍历集合,根据事件类型不同来进行对应的处理
            Iterator it = keys.iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                // 判断事件类型
                // 可接受事件
                if (key.isAcceptable()) {
                    // 从这个事件中获取通道
                    ServerSocketChannel sscx = (ServerSocketChannel) key.channel();
                    // 接受连接
                    SocketChannel sc = sscx.accept();
                    System.out.println("接到一个连接~~~");
                    // 设置非阻塞
                    sc.configureBlocking(false);
                    // 注册读/写事件
                    // 后一次的注册事件会覆盖前一次的注册事件
                    // sc.register(selc, SelectionKey.OP_READ -
                    // SelectionKey.OP_WRITE);
                    sc.register(selc, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                }
                // 可读事件
                if (key.isReadable()) {
                    // 获取到通道
                    SocketChannel sc = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    sc.read(buffer);
                    System.out.println(new String(buffer.array(), 0, buffer.position()));
                    // 移除掉可读事件
                    // 获取到这个通道身上的所有事件
                    sc.register(selc, key.interestOps() ^ SelectionKey.OP_READ);
                    // sc.register(selc, key.interestOps() -
                    // SelectionKey.OP_READ);
                }
                // 可写事件
                if (key.isWritable()) {
                    SocketChannel sc = (SocketChannel) key.channel();
                    sc.write(ByteBuffer.wrap("hello client~~~".getBytes()));
                    // 移除掉这个可写事件
                    sc.register(selc, key.interestOps() - SelectionKey.OP_WRITE);
                }

                it.remove();
            }

        }
    }

}

 

package cn.tedu.selector;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class Client {

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

        SocketChannel sc = SocketChannel.open();
        sc.connect(new InetSocketAddress("localhost", 8070));
        sc.write(ByteBuffer.wrap("hello server".getBytes()));
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        sc.read(buffer);
        System.out.println(new String(buffer.array(), 0, buffer.position()));
        sc.close();
    }

}

 

你可能感兴趣的:(NIO(同步、非阻塞IO))