Netty-NIO三大组件

本文最后更新于:2 年前

channel:双向数据传输通道

filechannel:文件

datagramchannel:

socketchannel:

serversocketchannel:

buffer:内存缓冲区

bytebuffer:

selector

选择器?

服务器端早期–多个客户端连接==多线程:

缺点:

内存占用高。

线程上下文切换成本高。

只适合连接数较少。

线程池版本设计:

缺点:

线程同一时间只能处理一个 socket。

仅适合短连接(长连接–一直保持连接)场景。

selector 设计:

一个线程管理多个 channel。

image-20210804200403644

调用 selector 的 select() 会阻塞直到 channel 发生了读写就绪事件,这些事件发生,select 方法就会返回这些事件交给 thread 来处理。

基本使用

  1. 向 buffer 写入数据,例如调用 channel.read(buffer)

  2. 调用 flip() 切换至读模式

  3. 从 buffer 读取数据,例如调用 buffer.get()

  4. 调用 clear() 或 compact() 切换至写模式

  5. 重复 1~4 步骤

image-20210804200504999

分配空间:

bytebuffer.allocate().==堆内存=读写效率低=受到 GC 影响

Bytebuffer.allocatedirect()==直接内存=读写效率高=分配内存的效率低

写入:write()

读取:get()

get 方法会让 position 读指针向后走,如果想重复读取数据

可以调用 rewind 方法将 position 重新置为 0

调用 get(int i) 方法获取索引 i 的内容,它不会移动读指针

mark 和 reset

mark 是在读取时,做一个标记,即使 position 改变,只要调用 reset 就能回到 mark 的位置

rewind 和 flip 都会清除 mark 位置

概念

阻塞 IO(同步):

非阻塞 IO(同步):read 为例,在等待数据阶段用户线程不阻塞(多次内存空间的切换)。复制数据阶段阻塞。

多路复用(同步):select()为例,两个阶段都是阻塞的。好处在于:可以一次性的处理多个 channel 上的事件。

信号驱动:不常用

异步 IO:
异步(线程自己不去获得结果,而是由其他的线程送来结果)

异步情况下一定是非阻塞的。

异步意味着:在进行读写操作时,线程不必等待结果,而是通过回调的方式由另外的线程来获取。

linux 在 2.6 底层通过多路复用模拟了异步 IO。

windows 通过 IOCP 真正实现了异步 IO。

netty 废弃了异步 IO。

零拷贝

image-20210804200720036

4 次数据拷贝
用户态内核态切换三次

NIO 优化

通过 DirectByteBuf

image-20210804200821683

  • ByteBuffer.allocate(10) HeapByteBuffer 使用的还是 java 内存

  • ByteBuffer.allocateDirect(10) DirectByteBuffer 使用的是操作系统内存
    java 可以使用 DirectByteBuf 将堆外内存映射到 jvm 内存中来直接访问使用

  • 这块内存不受 jvm 垃圾回收的影响,因此内存地址固定,有助于 IO 读写

  • java 中的 DirectByteBuf 对象仅维护了此内存的虚引用,内存回收分成两步

    • DirectByteBuf 对象被垃圾回收,将虚引用加入引用队列
    • 通过专门线程访问引用队列,根据虚引用释放堆外内存
  • 减少了一次数据拷贝,用户态与内核态的切换次数没有减少

进一步(linux2.1 之后的 sendfile 方法)

image-20210804200844338

java 中对应着两个 channel 调用 transferTo/transferFrom 方法拷贝数据

  1. java 调用 transferTo 方法后,要从 java 程序的用户态切换至内核态,使用 DMA将数据读入内核缓冲区,不会使用 cpu
  2. 数据从内核缓冲区传输到 socket 缓冲区,cpu 会参与拷贝
  3. 最后使用 DMA 将 socket 缓冲区的数据写入网卡,不会使用 cpu
  • 只发生了一次用户态与内核态的切换

  • 数据拷贝了 3 次

进一步(linux2.4 之后的 sendfile 方法)

image-20210804200932305

  1. java 调用 transferTo 方法后,要从 java 程序的用户态切换至内核态,使用 DMA 将数据读入内核缓冲区,不会使用 cpu
  2. 只会将一些 offset 和 length 信息拷入 socket 缓冲区,几乎无消耗
  3. 使用 DMA 将内核缓冲区的数据写入网卡,不会使用 cpu
    整个过程仅只发生了一次用户态与内核态的切换,数据拷贝了 2 次。
    所谓的【零拷贝】=linux=sendfile 方法,并不是真正无拷贝,而是在不会拷贝重复数据到 jvm 内存中。

零拷贝的优点有

  • 更少的用户态与内核态的切换
  • 不利用 cpu 计算,减少 cpu 缓存伪共享(使用 DMA 硬件)
  • 零拷贝适合小文件传输=大文件没有缓冲的作用。(缓冲区比较小)