• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Netty四、NIO基本、和BIO的区别、三大核心组件

武飞扬头像
开拖拉机的小许
帮助1

基本介绍:

  1. 全称non-blocking IO jdk1.4开始java提供了一系列改进的输入/输出的新特性 ,也叫new IO
  2. 同步非阻塞
  3. 相关类在java.io 包及其子包下,并且对原java.io 包中的很多类进行了改写

    学新通

  4. NIO是面向缓冲区,或者面向块编程的。数据读取到一个他稍后处理的缓冲区,需要时可以在缓冲区中前后移动,这就增加了数据处理的灵活性,使用它可以提供非阻塞式的高伸缩性网络。
  5. 读非阻塞:线程请求读取数据,但是他只能得到目前可用的数据,如果没有可用的数据,就什么都不会获取,而不是保持线程阻塞,当数据变得可用之前,该线程可以去处理其他的事情。 
  6. 写非阻塞:线程请求写入一些数据到某通道,但是不需要等待它完全写入,这个线程同时可以去做别的事情。县城通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出Channel。
  7. Http2.0使用了多路复用技术,可以同一个连接并发处理多个请求,而且并发请求的数量比Http1.0大了好几个数量级

 NIO与BIO的比较

  BIO NIO
处理数据的方式 流       块(效率更高)
是否阻塞 是       
操作单位 基于字节流、字符流

基于Channel和Buffer

三大核心组件 Channel(数据通道) Buffer(缓冲区) Selector(选择器)

        关系图:

学新通

        关系说明

  1.  程序切换到哪个channel是由事件决定的。
  2. Selector会根据不同的事件,在各个通道上切换。
  3. Buffer就是一个内存块,底层是又一个数组。
  4. 数据的读写时通过Buffer,是可以双向的,而BIO要么是输入流,或者是输出流,不能双向。

一、 Buffer类及其子类

      1.结构列表:

学新通

        2. 属性说明:

  1.  
    /**
  2.  
    * abstract class IntBuffer // 真实数局是存放在hb数组中的
  3.  
    * final int[] hb; // Non-null only for heap buffers
  4.  
    *
  5.  
    * abstract class Buffer
  6.  
    * private int mark = -1; // 标记
  7.  
    * private int position = 0; // 位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变该值,为下次读写做准备
  8.  
    * private int limit; // 缓冲区的当前终点,不能对超过极限位置进行读写操作。且极限是可以修改的。
  9.  
    * private int capacity; // 可以容纳的最大的数据量,在缓冲区创建时被设定并且不能改变。
  10.  
    * @param args
  11.  
    */

        3. flip读写切换说明:

  1.  
    public final Buffer flip() {
  2.  
    limit = position;
  3.  
    position = 0;
  4.  
    mark = -1;
  5.  
    return this;
  6.  
    }

         4. 常用的方法

学新通

         5. 自定义块while循环读取

  1.  
    public static void main(String[] args) {
  2.  
    // 创建一个Buffer 内存大小是5
  3.  
    IntBuffer intBuffer = IntBuffer.allocate(5);
  4.  
    // 向Buffer块中写数据
  5.  
    for (int i = 0; i < intBuffer.capacity(); i ) {
  6.  
    intBuffer.put(i*2);
  7.  
    }
  8.  
    // 切换操作
  9.  
    intBuffer.flip();
  10.  
    // 读取Buffer中的数据
  11.  
    System.out.println("for循环");
  12.  
    for (int i = 0; i < intBuffer.capacity(); i ) {
  13.  
    System.out.println(intBuffer.get(i));
  14.  
    }
  15.  
    // for循环不够灵活,使用while 自定义 position和limit 读取指定的数据
  16.  
    intBuffer.position(1);
  17.  
    intBuffer.limit(4);
  18.  
    System.out.println("while循环");
  19.  
    while(intBuffer.hasRemaining()){
  20.  
    System.out.println(intBuffer.get());
  21.  
    }
  22.  
    }
学新通

         6. 最常用的类ByteBuffer

学新通

二、通道(Channel)

  1.  结构列表(子接口和实现类)

    学新通

  2. Channel接口继承Closeable接口
    1.  
      public interface Channel extends Closeable {
    2.  
      public boolean isOpen();
    3.  
      public void close() throws IOException;
    4.  
      }
  3. FileChannel类
    1.  
      // 从通道读写数据并放到缓冲区
    2.  
      public int read(ByteBuffer var1) throws IOException {}
    3.  
       
    4.  
      // 将缓冲区的数据写到通道1
    5.  
      public int write(ByteBuffer var1) {}
    6.  
       
    7.  
      // 把缓冲区的数据写到通道2
    8.  
      public long write(ByteBuffer[] var1, int var2, int var3) throws IOException{}
    9.  
       
    10.  
      // 从目标通道中复制数据到当前通道
    11.  
      public long transferFrom(ReadableByteChannel var1, long var2, long var4){}
    12.  
       
    13.  
      // 把数据从当前通道复制给目标通道
    14.  
      public long transferTo(long position,long count,WritableByteChannel target){}
    15.  
       
    学新通
  4. 本地文件写数据:ByteBuffer 搭配 FileChannel,将 Hello World写入到 file01.txt  

    学新通

    学新通
    1.  
      public class ChannelTest {
    2.  
      public static void main(String[] args) throws IOException {
    3.  
      String str = "Hello World!";
    4.  
      // 文件输出流
    5.  
      FileOutputStream fileOutputStream = new FileOutputStream("e:\\file01.txt");
    6.  
      // 通过 文件输出流 获取一个对应的 FileChannel 真实类型是FileChannelImpl
    7.  
      FileChannel fileChannel = fileOutputStream.getChannel();
    8.  
      // 创建一个缓冲区
    9.  
      ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    10.  
      // 将字符串以字节数组的方式 加入缓冲区
    11.  
      byteBuffer.put(str.getBytes());
    12.  
      // 对byteBuffer进行flip()
    13.  
      byteBuffer.flip();
    14.  
      // 将缓冲区数据写入到通道中 (因为要先从byteBuffer中读取出来,所以要进行flip)
    15.  
      fileChannel.write(byteBuffer);
    16.  
      fileOutputStream.close();
    17.  
      }
    18.  
      }
    学新通
  5. 读取本地文件 学新通

    1.  
      public class ChannelTest02 {
    2.  
      public static void main(String[] args) throws IOException {
    3.  
      File file = new File("e:\\file01.txt");
    4.  
      FileInputStream fileInputStream = new FileInputStream(file);
    5.  
      FileChannel fileChannel = fileInputStream.getChannel();
    6.  
      ByteBuffer buffer = ByteBuffer.allocate((int) file.length());
    7.  
      // 将通道的数据读取到Buffer中
    8.  
      int read = fileChannel.read(buffer);
    9.  
      buffer.flip(); // 这里 是将通道的数据读取到buffer中(应该是写的操作),没有先读的操作,所以不需要flip切换
    10.  
      //将字节转成String
    11.  
      System.out.println(new String(buffer.array()));
    12.  
      }
    13.  
      }
  6. 使用一个Buffer完成文件的拷贝学新通

    1.  
      public class ChannelTest03 {
    2.  
      public static void main(String[] args) throws Exception{
    3.  
      FileInputStream fileInputStream = new FileInputStream("1.txt");
    4.  
      FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
    5.  
       
    6.  
      FileChannel fileInputStreamChannel = fileInputStream.getChannel();
    7.  
      FileChannel fileOutputStreamChannel = fileOutputStream.getChannel();
    8.  
       
    9.  
      ByteBuffer byteBuffer = ByteBuffer.allocate(512);
    10.  
       
    11.  
      while(true){
    12.  
      byteBuffer.clear();
    13.  
      // 清空buffer 如果不清空的话第一次读取写入完毕之后
    14.  
      // limit 和 position 都为38 ,read返回0 陷入死循环
    15.  
       
    16.  
      int read = fileInputStreamChannel.read(byteBuffer);
    17.  
      System.out.println("read = " read);
    18.  
      if(read == -1){
    19.  
      break;
    20.  
      }
    21.  
      byteBuffer.flip();
    22.  
      fileOutputStreamChannel.write(byteBuffer);
    23.  
      }
    24.  
       
    25.  
      fileInputStream.close();
    26.  
      fileOutputStream.close();
    27.  
       
    28.  
      }
    29.  
      }
    学新通
  7. 使用TransferFrom完成文件拷贝

    1.  
      public class ChannelTest04 {
    2.  
      /**
    3.  
      * 从目标通道复制数据到当前通道
    4.  
      * @param args
    5.  
      */
    6.  
      public static void main(String[] args) throws IOException {
    7.  
      FileInputStream fileInputStream = new FileInputStream("row.jpg");
    8.  
      FileChannel channel01 = fileInputStream.getChannel();
    9.  
       
    10.  
      FileOutputStream fileOutputStream = new FileOutputStream("new.jpg");
    11.  
      FileChannel channel02 = fileOutputStream.getChannel();
    12.  
       
    13.  
      /**
    14.  
      * 使用transFrom来将 在通道层面 执行拷贝 没使用Buffer
    15.  
      */
    16.  
      channel02.transferFrom(channel01,0,channel01.size());
    17.  
      channel02.close();
    18.  
      channel01.close();
    19.  
      fileOutputStream.close();
    20.  
      fileInputStream.close();
    21.  
       
    22.  
      }
    23.  
      }
    学新通
  8. Buffer 和 Channel 注意细节

    1 buffer的 put类型和get类型要求要对应否则出现BufferUnderFlowException异常
    2 readOnlyBuffer 只能get,调用 put方法会出现ReadOnlyBufferException异常
    3

    NIO提供了MappedByteBuffer,可以让文件直接在内存(堆外内存)中进行修改,而如何同步到文件由NIO来完成

    4 前面的读写都是通过一个Buffer来完成的,NIO还支持 通过多个Buffer(Buffer[] 数组)完成读写操作,即Scattering和Gatering
  9. MappedByteBuffer堆外内存修改文件

    学新通

    1.  
      public class MappedByteBufferTest01 {
    2.  
      /**
    3.  
      * MappedByteBuffer 可以让文件直接在内存【堆外内存】中进行修改
    4.  
      * 好处 :操作系统级别的修改 不需要拷贝一次
    5.  
      * @param args
    6.  
      */
    7.  
      public static void main(String[] args) throws IOException {
    8.  
      RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt", "rw");
    9.  
      FileChannel channel = randomAccessFile.getChannel();
    10.  
       
    11.  
      /**
    12.  
      * 映射模式【读/写】
    13.  
      * 起始位置
    14.  
      * 内存大小
    15.  
      * map(MapMode mode,long position, long size)
    16.  
      */
    17.  
      MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
    18.  
      // 修改 position 位置的元素
    19.  
      mappedByteBuffer.put(0,(byte) 'H');
    20.  
      mappedByteBuffer.put(3, (byte) 'L');
    21.  
      // mappedByteBuffer.put(5, (byte) '_');//java.lang.IndexOutOfBoundsException
    22.  
       
    23.  
      randomAccessFile.close();
    24.  
      System.out.println("修改成功!");
    25.  
      }
    26.  
      }
    学新通
  10. Scattering 分散 和Gathering 聚合 

    1.  
      public class ScatteringAndGatheringTest01 {
    2.  
      /**
    3.  
      * 分散和聚合
    4.  
      * Scattering 写入的时候 使用buffer数组 分散写入
    5.  
      * Gathering 读取的时候使用buffer数组 依次读取
    6.  
      */
    7.  
      public static void main(String[] args) throws Exception{
    8.  
      /**
    9.  
      * ServerSocketChannel 和 SocketChannel 【网络】
    10.  
      *
    11.  
      */
    12.  
      // 服务端
    13.  
      ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    14.  
      InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
    15.  
      serverSocketChannel.bind(inetSocketAddress);
    16.  
       
    17.  
      // buffer数组分散写入
    18.  
      ByteBuffer[] buffers = new ByteBuffer[2];
    19.  
      buffers[0] = ByteBuffer.allocate(5);
    20.  
      buffers[1] = ByteBuffer.allocate(3);
    21.  
       
    22.  
      //等待客户端连接
    23.  
      SocketChannel socketChannel = serverSocketChannel.accept();
    24.  
       
    25.  
      // 消息长度
    26.  
      int messageLength = 8;
    27.  
       
    28.  
      while(true){
    29.  
      // read 从通道 读取数据 并放到缓冲区
    30.  
      int readByte = 0;
    31.  
      while(readByte<messageLength){
    32.  
      long read = socketChannel.read(buffers);
    33.  
      readByte = read;
    34.  
      System.out.println("readByte = " readByte);
    35.  
      // 输出 buffer的 position 和 limit
    36.  
      Arrays.asList(buffers)
    37.  
      .stream()
    38.  
      .map(buffer -> "position = " buffer.position() ",limit = " buffer.limit())
    39.  
      .forEach(System.out::println);
    40.  
      }
    41.  
      // 读写切换
    42.  
      Arrays.asList(buffers)
    43.  
      .forEach(buffer -> buffer.flip());
    44.  
      // 数据显示到客户端
    45.  
      long byteWrite = 0;
    46.  
      while(byteWrite<messageLength){
    47.  
      long write = socketChannel.write(buffers);
    48.  
      byteWrite = write;
    49.  
      }
    50.  
      // 将所有的buffer clear
    51.  
      Arrays.asList(buffers)
    52.  
      .forEach(buffer -> buffer.clear());
    53.  
      System.out.println("readByte = " readByte "writeByte = " byteWrite);
    54.  
      }
    55.  
       
    56.  
       
    57.  
      }
    58.  
      }
    学新通

三、Selector选择器

  1. NIO中一个线程处理多个客户端连接,使用Selector管理。

  2. Selector能够检测多个注册的通道上是否有事件发生(多个Channel以事件的方法可以注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。

  3. 只有在连接/通道真正有读写事件发生时,才会进行读写,大大减少了系统开销,不必为每个连接都创建一个线程,不用去维护多个线程。

  4. 避免了多线程之间的上下文切换导致的开销。

  5. 细节说明学新通

  6.  API介绍学新通

    ①Selector和Thread关联                                                                                ②Selector.select() 方法获得活动channel的 SelectKey集合

    select()
    blocking selection operation.
    It returns only after at least one channel is selected ,this selector's method is invoked, or the current thread isinterrupted, whichever comes first.
    select(long timeout)
    This method does not offer real-time guarantees: 
    It schedules the timeout as if by invoking the wait(long) method. 
    selectNow()
    This method performs a non-blocking selection operation.  
    If no channels have become selectable since the previous selection operation then this method immediately returns zero.
    Invoking this method clears the effect of any previous invocations of the wakeup method. 

    学新通

     ③SelectKey.channel() 方法获得对应的Channel

    Returns the channel for which this key was created.  This method will continue to return the channel even after the key is cancelled.                                  

四、NIO 非阻塞 网络编程原理分析图

        NIO非阻塞相关的 SelectorSelectionKeyServerScoketChannelSocketChannel梳理

学新通

         说明:

  1. 当客户端连接时,通过ServerSocketChannel创建对应的SocketChannel。
  2. 将得到的SocketChannel,通过SocketChannel父类的 public final SelectionKey register(Selector sel, int ops)方法 将当前的Channel注册到Selector上。学新通
  3. 注册返回一个SelectionKey ,会和该Selector关联(集合)
  4. Selector进行监听 select方法,返回有事件发生的Channel的个数
  5. 进一步得到各个SelectionKey(有事件发生)
  6. 通过SelectionKey反向获取注册的SocketChannel
  7. 通过得到的Channel,完成业务的处理

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgjkgcj
系列文章
更多 icon
同类精品
更多 icon
继续加载