NIO入门级学习(Netty预备知识)

一、NIO三大组件

1、Channel 通道
2、Buffer 缓冲区
3、Selector 选择器

最直白的理解就是,NIO可以做到一个线程处理多个请求(或操作)
就如HTTP2.0 使用了多路复用技术,做到一个连接并发处理多个请求并且并发请求的数量比HTTP1.1大了几个数量级

1.1三大组件关系

  1. 一个线程有一个Selector
  2. Selector对应多个channel
  3. 程序切换到哪一个channel由事件Event决定
  4. Selector会根据不同的事件在不同的Channel上面切换
  5. Buffer是一个内存块,底层是一个数组
  6. 数据的读取写入是通过Buffer,BIO中是通过输入输出流。不能双向,但是NIO的Buffer是可以读也可以写。要调用flip方法,Channel

二、缓冲区Buffer

Buffer内部有一个数组,Channel读取文件或者从网络通道读取数据必须经过Buffer
Buffer有除了布尔类型之外所有基本类型的对应子类。
如ByteBuffer,CharBuffer,…

2.1

Buffer类中定义了缓冲区具有对的四个属性

  1. Capacity 容量,创建之后就不能改变了
  2. Limit 缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写。且极限是可以修改的
  3. Position 位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变该值,为下一次读写作准备
  4. Mark 标记

三、通道Channel

常用的Channel:

  1. FileChannel
  2. DatagramChannel
  3. ServerSocketChannel
  4. SocketChannel

ServerSocketChanne 类似ServerSocket SocketChannel 类似Socket

通道是可以同时进行读写,而流只能读或者写
通道可以实现异步读写数据
通道可以从缓冲读数据,也可以写数据到缓冲

   @Test
    void contextLoads() {
    
    

        ByteBuffer buffer = ByteBuffer.allocate(64);

        //类型化方法放入数据
        buffer.putInt(100);
        buffer.putLong(9);
        buffer.putChar('张');
        buffer.putShort((short)4);

        buffer.flip();
        //取出
        System.out.println(buffer.getInt());
        System.out.println(buffer.getLong());
    }

}

buffer和channel 还有很多的一些用法,用到的时候可以再去看

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

  1. 当客户端连接时,会通过ServerSocketChannel
  2. Selector进行监听 select方法,返回有事件发生的通道的个数
  3. 将socketChannel注册到Selector上,register(Selector sel,int ops),一个Selector上可以注册多个SocketChannel
  4. 注册后返回一个SelectionKey,会和该Selector关联(集合)
  5. 进一步得到各个SelectionKey(有事件发生)
  6. 在通过SelectionKey 反向获取SocketChannel,方法channel()
  7. 可以通过得到的channel,完成业务处理

Selectionkey解释

int OP_ACCEPT:有新的网络连接可以accept,值为6
int OP_CONNECT 代表连接已经建立,值为8
int OP_READ 代表读操作,值为1
int OP_WRITE 代表写操作,值为4

通过SelectionKey得到与之关联的Selection对象
得到与之关联的通道
得到与之关联的共享数据
设置或改变监听事件

ServerSocketChannel

ServersocketChannel在监听新的客户端Socket连接后
具体负责进行读写操作。NIO把缓冲区的数据写入通道或者吧通道中的数据读到缓冲区

serversocket与serversocketchannel什么关系
服务器必须先建立ServerSocketChannel来等待客户端连接

客户端必须建立相对应的Socket或者SocketChannel来与服务器建立连接

服务器接受到客户端的连接受,再生成一个Socket或者SocketChannel与此客户端通信

SocketChannel

具体负责进行读写操作。

五、NIO与零拷贝

因为之前没接触过零拷贝,这里先说一下零拷贝
零拷贝常用方法有mmap和sendFile

5.1 引入

在现代操作系统中。引用了IO内存映射,把寄存器的值映身到主存,最设备存储器的操作,转换为对主存的操作,转换为对主存的操作,这样极大的提高了效率。

5.2 DMA

现代操作系统中引入DMA设备,设备接收到数据时,把数据放至DMA内存中,再向CPU产生中断,这样就节省了大量的CPU时间

5.3 零拷贝

零拷贝表述的是CPU不执行拷贝数据从一个存储区域到另一个存储区域的任务,这通常用于通过网络传输一个文件时以减少CPU周期和内存宽带

这是一个read方法的流程示意图
这里摘抄

作者:莫那一鲁道
链接:https://www.jianshu.com/p/275602182f39
来源:简书

在这里插入图片描述
上图中,上半部分表示用户态和内核态的上下文切换。下半部分表示数据复制操作。下面说说他们的步骤:

read 调用导致用户态到内核态的一次变化,同时,第一次复制开始:DMA(Direct Memory Access,直接内存存取,即不使用 CPU 拷贝数据到内存,而是 DMA 引擎传输数据到内存,用于解放 CPU) 引擎从磁盘读取 index.html 文件,并将数据放入到内核缓冲区。

发生第二次数据拷贝,即:将内核缓冲区的数据拷贝到用户缓冲区,同时,发生了一次用内核态到用户态的上下文切换。

发生第三次数据拷贝,我们调用 write 方法,系统将用户缓冲区的数据拷贝到 Socket 缓冲区。此时,又发生了一次用户态到内核态的上下文切换。

第四次拷贝,数据异步的从 Socket 缓冲区,使用 DMA 引擎拷贝到网络协议引擎。这一段,不需要进行上下文切换。

write 方法返回,再次从内核态切换到用户态。

如你所见,复制拷贝操作太多了。如何优化这些流程?

mmap 优化
mmap 通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据。这样,在进行网络传输时,就可以减少内核空间到用户控件的拷贝次数。如下图:
在这里插入图片描述
如上图,user buffer 和 kernel buffer 共享 index.html。如果你想把硬盘的 index.html 传输到网络中,再也不用拷贝到用户空间,再从用户空间拷贝到 Socket 缓冲区。

现在,你只需要从内核缓冲区拷贝到 Socket 缓冲区即可,这将减少一次内存拷贝(从 4 次变成了 3 次),但不减少上下文切换次数。

sendFile
那么,我们还能继续优化吗? Linux 2.1 版本 提供了 sendFile 函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到 Socket Buffer,同时,由于和用户态完全无关,就减少了一次上下文切换。
在这里插入图片描述

如上图,我们进行 sendFile 系统调用时,数据被 DMA 引擎从文件复制到内核缓冲区,然后调用,然后掉一共 write 方法时,从内核缓冲区进入到 Socket,这时,是没有上下文切换的,因为在一个用户空间。

最后,数据从 Socket 缓冲区进入到协议栈。

此时,数据经过了 3 次拷贝,3 次上下文切换。

那么,还能不能再继续优化呢? 例如直接从内核缓冲区拷贝到网络协议栈?

实际上,Linux 在 2.4 版本中,做了一些修改,避免了从内核缓冲区拷贝到 Socket buffer 的操作,直接拷贝到协议栈,从而再一次减少了数据拷贝。具体如下图:
在这里插入图片描述
现在,index.html 要从文件进入到网络协议栈,只需 2 次拷贝:第一次使用 DMA 引擎从文件拷贝到内核缓冲区,第二次从内核缓冲区将数据拷贝到网络协议栈;内核缓存区只会拷贝一些 offset 和 length 信息到 SocketBuffer,基本无消耗。

等一下,不是说零拷贝吗?为什么还是要 2 次拷贝?

答:首先我们说零拷贝,是从操作系统的角度来说的。因为内核缓冲区之间,没有数据是重复的(只有 kernel buffer 有一份数据,sendFile 2.1 版本实际上有 2 份数据,算不上零拷贝)。例如我们刚开始的例子,内核缓存区和 Socket 缓冲区的数据就是重复的。

而零拷贝不仅仅带来更少的数据复制,还能带来其他的性能优势,例如更少的上下文切换,更少的 CPU 缓存伪共享以及无 CPU 校验和计算。

再稍微讲讲 mmap 和 sendFile 的区别。

mmap 适合小数据量读写,sendFile 适合大文件传输。
mmap 需要 4 次上下文切换,3 次数据拷贝;sendFile 需要 3 次上下文切换,最少 2 次数据拷贝。
sendFile 可以利用 DMA 方式,减少 CPU 拷贝,mmap 则不能(必须从内核拷贝到 Socket 缓冲区)。
在这个选择上:rocketMQ 在消费消息时,使用了 mmap。kafka 使用了 sendFile。

摘抄结束

六、AIO

AIO就是用来两种模式:Reactor和Proactor
Java的NIO就是Reactor,当有事件触发时,服务器端得到通知,进行相应的处理

AIO即NIO2.0 叫做异步不阻塞的IO,引入异步通道的概念,采用了Proator模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用

六、三种IO对比

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_43263481/article/details/108456064