Netty从入门到源码2_NIO三大组件简单介绍

1.NIO三大组件

1.1Channel & Buffer介绍

channel类似于stream,区别在于:

  • channel它是读写数据的双向通道可以从channel将数据输出到buffer中,也可以将buffer中的数据输出到channel中

在这里插入图片描述

  • 而stream是单向的 (BIO是面向stream的),所以分为了输入流(InputStream)输出流(OutputStream)关于Java的IO流参考

常见的Channel

  • FileChannel(操作文件)
  • DatagramChannel(UDP协议连接)
  • SocketChannel(TCP协议连接)
  • ServerSocketChannel专门处理TCP协议Accept事件

常见的buffer

buffer用作缓冲读写数据Buffer本质上是一个内存块,可以向里面写入数据,或者从里面读取数据,在Java中它被包装成了Buffer对象,并提供了一系列的方法用于操作这个内存块

常见的buffer有

ByteBuffer

  • MappedByteBuffer
    MappedByteBuffer是一种特殊的ByteBuffer,它使用内存映射的方式加载物理文件,并不会耗费同等大小的物理内存,是一种直接操作堆外内存的方式,读写性能比较高。
  • DirectByteBuffer
  • HeapByteBuffer

ShortBuffer/IntBuffer/LongBuffer/FloatBuffer/DoubleBuffer/ChaBuffer/

1.2Channel与Buffer使用

1.2.1简单实例

使用channel/buffer读取data.txt的内容(123456789@xxx)

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/*
 *  说明:这里为例演示代码清晰,所以没有使用try-catch去处理资源
 * */
public class TestByteBuffer {

    private static final String PATH = "data.txt";

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

        //获取channel 调用getChannel()方法
        FileChannel channel = new FileInputStream(PATH).getChannel();

        //准备一个缓冲区buffer 大小为10个字节
        ByteBuffer buffer = ByteBuffer.allocate(10);

        while (true) {
            /*
             *  read()方法,将channel的数据输出到buffer中,
             *  返回值就是此次输出buffer中数据的字节数
             *  返回-1,表示channel中的数据全部输出到了buffer中
             * */
            int len = channel.read(buffer);

            if (len == -1) break;

            /*
            *  切换缓冲区至输出模式,本质就是position指针移动到第一个
            *  未读数据的位置。
            * */
            buffer.flip();
            
            /*
             *  hasRemaining表示buffer中是否还有未读数据
             * */
            while (buffer.hasRemaining()) {
                byte b = buffer.get(); //读取数据
                System.out.println((char) b);
            }
            
            /*
            * 清空buffer中的数据,本质就是移动position指针(后续详细讲)到开头
            * 下次从channel中输入数据的时候从头开始输入
            * */
            buffer.clear();
        }
    }
}

运行结果

读到的字节数 = 10
1
2
3
4
5
6
7
8
9
@
读到的字节数 = 3
x
x
x
读到的字节数 = -1 //结束

1.2.2使用流程

  • 1-获取channel() 可以通过网络或者stream
  • 2-通过ByteBuffer.allocate(n)分配一个Buffer(缓冲)
  • 3-将channel中的数据输出到buffer中 channer.read(buffer)
  • 4-开启buffer的输出模式,buffer.flip()
  • 5-从buffer中获取数据,buffer.get()
  • 6-清空buffer(切换为写(输入)模式) 继续从channel中读数据,然后继续循环1-5

1.2.3buffer内部结构详解

ByteBuffer有下面三个重要属性

  • capacity(buffer总容量)

    Buffer作为一个存储块,是有固定大小的,这个固定大小我们称作“容量”。

    当Buffer写满之后,需要先清空或者读取数据,才能继续写入新的数据。

  • position(当前位置)
    -写模式下,position从0开始,每写入一个单位的数据,position前进一位,position最大 可到达(capacity-1)的位置。
    -当Buffer从写模式切换为读模式时,position将重置为0。读取数据时,同样地,position每读取一个单位,前进一位,此时,position最大可到达limit的位置 实际最大可读取的位置是(limit-1)

  • limit(限制长度)

    写模式下,limit最大值等于capacity。

    读模式下,limit最大值等于切换为读模式时position

总结

这里可能有点绕,position类似于数组的下标,是从0开始的,limit表示最大可以读取或者写入的长度,capacity表示最大的容量,limit和capacity不是下标,类似于数组的长度,所以跟position比较需要-1。在写模式下,position指向的是下一个待写入的位置;在读模式下,position指向的是下一个待读取的位置

内部变化

Buffer刚创建时

在这里插入图片描述

向Buffer中输入了四个字节的数据
在这里插入图片描述

调用flip()进入Buffer的输出模式

在这里插入图片描述

输出了四个字节的数据
在这里插入图片描述

调用clear()进入Buffer输入模式
在这里插入图片描述

1.2.4buffer常用方法

获取buffer

可以使用allocate()方法为ByteBuffer分配空间,其他buffer类也有该方法

ByteBuffer buffer = ByteBuffer.allocate(16) //创建了一个大小为16字节的buffer

向buffer中写入数据

  • 第一种方法

    通过channel将数据输入到buffer中

    int readBytes = channel.read(buffer);
    
  • 第二种方法

    调用buffer的put()方法,自己为自己输入数据

    buffer.put(2)
    

    put()有很多不同的类型,比如在特定位置写入,写入不同类型的数据等等

从buffer中输出数据

  • 输出到channel中,调用channel的write()方法
channel.write(buffer)
  • 调用buffer自己的get()方法
byte b = buffer.get();

注意:get()方法会让position读指针向后移动,如果想重复读取数据

1.可以调用rewind()方法将position重新置为0

2.或者调用get(int i)方法获取索引为i的数据,不会移动position指针

rewind()

rewind()方法会重置position为0但limit保持不变,因此可以用来重新读取数据。通常是在重新读取数据之前调用

clear()

clear()方法用于清空整个Buffer,并将Buffer从读模式切换回写模式且position归位到0位置

compact()

compact()方法用于清空已读取的数据,并将未读取的数据移至Buffer的头部,position的位置移动到从头开始计算的未读取的数据的下一个位置,它也会将Buffer从读模式切换回写模式

mark() 和 reset()

mark()方法用于标记给定位置,然后可以在之后通过reset()方法重新回到mark的位置,示例如下:

buffer.mark();

//多次调用buffer.get(),例如在解析过程中。

buffer.reset(); //将位置重新设置为标记。 

1.2.5分散读取与聚合

分散输出:将channel中的数据分散输出至多个buffer

//伪代码 假设现在有三个buffer
bufferA
bufferB
bufferC

//将channel中的数据分散读取到三个buffer中 (根据buffer的容量依次输入)
channel.read(new ByteBuffer[]{bufferA, bufferB, bufferC});

聚合输入: 将多个buffer的内容出入到一个channel中

//伪代码 假设现在有三个buffer
bufferA
bufferB
bufferC

//分别开启三个buffer的输出模式 即调用flip()    
    
//将三个buffer中的内容输出到channel中
channel.write(new ByteBuffer[]{bufferA, bufferB, bufferC})

1.3Selector介绍

Selector详细参考

selector但从字面意思不好理解,需要结合服务器的设计演化来理解其用途。

多线程版与线程池版模拟服务器处理连接代码 码云

多线程版设计

服务器建立连接,每获取一个连接(Socket)后开启一个线程去处理任务.

缺点:连接数多的话开启过多的线程会使系统崩掉。

基于这一点,就可以使用线程池优化
在这里插入图片描述

线程池版

缺点:

  • 阻塞模式下,线程仅能处理一个socket连接
  • 仅适用于短连接场景
    在这里插入图片描述

selector版设计

  • selector的作用就是配合一个线程来管理多个channel(在这里可以认为就是一个网络连接),获取这些channel上发生的时间,这些channel工作在非阻塞模式下,不会让线程吊死在一个channel上,适合连接数特别多,但流量低的场景
  • 调用selector的select()会阻塞知道channel发生了读/写就绪时间,这些时间发生,select()方法就会返回这些时间交给thread去处理(就是IO多路复用)
  • 通过调用selector的select()方法会阻塞到某个或者某些channel发生了读写就绪事件,这些事件发生,select()方法就会返回这些时间交给thread去处理

在这里插入图片描述

更多参考

IO多路复用

彤哥Netty系列之NIO核心组件Channel

彤哥Netty系列之NIO核心组件Buffer

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shstart7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值