Netty(一)--java NIO详解

我们都知道Netty是一个基于NIO的客户、服务器端编程框架,使用Netty可以大大简化网络应用的编程过程。那么首先第一步就是要了解什么是NIO?
一.我们一般将I/O模型分为以下五种类型。

  • 阻塞式I/O
  • 非阻塞式I/O
  • I/O复用
  • 信号驱动
  • 异步I/O
那么这些类型是按照什么来定义的呢?阻塞和非阻塞是针对什么来划分的?复用又是对什么复用,异步又是怎么回事?
这里面介绍下liunx I/O流程,下面是liunx I/O的架构图
Netty(一)--java NIO详解
文章图片

这张架构图可以在一定的层面上展示I/O流程。但是我们针对的是下面这张图
Netty(一)--java NIO详解
文章图片

liunx服务当有请求来访问的时候,最终都是从磁盘中读取数据然后数据读取到内核空间成为内核态,再从内核态复制到用户态。
从这张图可以简单的认为客户端请求I/O流程是(1)等待内核态数据准备好(2)从内核向进程复制数据
那么模式的划分就是从application的用户态和内核层的内核态之间的交互类型来划分的。
首先看下阻塞式I/O模型
Netty(一)--java NIO详解
文章图片

当有请求的时候,内核态准备数据,数据准备完毕后再拷贝到用户态。当数据拷贝到用户态完成的时候返回。数据没有返回之前进程会卡住。整个流程上都是阻塞的称之为阻塞式I/O

非阻塞式I/O模型
Netty(一)--java NIO详解
文章图片

首先在数据准备阶段程序不断轮训数据有没有准备完毕,当程序在轮训到数据准备好的时候实际上还是要等到内核到用户拷贝完毕才能返回。所以在整个的流程上还是阻塞的。但是我们把这种模型称为非阻塞式I/O
也就是阻塞和非阻塞是针对内核态数据准备阶段而言的。
I/O复用模型
Netty(一)--java NIO详解
文章图片

多路I/O共用一个同步阻塞接口,任意IO可操作都可激活IO操作,这是对阻塞IO的改进(主要是select和poll、epoll,关键是能实现同时对多个IO端口进行监听)。此时阻塞发生在select/poll的系统调用上,而不是阻塞在实际的I/O系统调用上
信号驱动式I/O模型
Netty(一)--java NIO详解
文章图片

内核在数据准备完成的时候发送SIGIO信号通知,此时程序再做数据请求 。称之为信号驱动I/O
异步I/O模型
Netty(一)--java NIO详解
文章图片

当数据请求的时候先返回等数据准备完毕并且从内核态copy到用户态完成的时候通知客户端
以下是各种I/O模型之间的比较
Netty(一)--java NIO详解
文章图片
Netty是NIO框架,那么NIO是什么?NIO=New I/O,是采用I/O复用模式的一种面向缓冲区的非阻塞I/O技术
NIO 1:jdk1.4引入
NIO 2:jdk7
NIO
  • Buffers
  • Channels
  • Selectors
NIO 2.0
  • Update
  • New File System API
  • Asynchronous I/O
这里面说的是NIO 1.0。NIO和I/O之间的区别
Netty(一)--java NIO详解
文章图片

NIO中三大部件 Buffer,Channel,Selector
  • Buffer一个 Buffer 本质上是内存中的一块, 可以将数据写入这块内存, 从这块内存获取数据。java.nio 定义了以下几个Buffer 的实现:CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer,ByteBuffer,MappedByteBuffer
Buffer三大核心概念:position、limit、capacity。最好理解的当然是 capacity,它代表这个缓冲区的容量,一旦设定 就不可以更改。比如 capacity 为 1024 的 IntBuffer,代表其一次可以存放 1024 个 int 类型的值。一旦 Buffer 的容量达到 capacity,需要清空 Buffer,才能重新写入值。从写操作模式到读操作模式切换的时候(flip),position 都会归零,这样就可以从头开始读写了。写操作模式下,limit 代表的是最大能写入的数据,这个时候 limit 等于 capacity。写结束后,切换到读模式,此时的 limit 等于 Buffer 中实际的数据大小,因为Buffer 不一定被写满了。
Netty(一)--java NIO详解
文章图片

Buffer的分类:Non-direct ByteBuffer和Direct ByteBuffer

DirectByteBuffer
HeapByteBuffer
创建开销



存储位置
Native heap
Java heap

数据拷贝
无需临时缓冲区做拷贝
拷贝到临时DirectByteBuffer,但临时缓冲区使用缓存。
聚集写/发散读时没有缓存临时缓冲区。
GC影响
每次创建或者释放的时候
都调用一次System.gc()

另外就是一些API的使用,创建,读取,复制(都是浅复制)
  • Channel所有的 NIO 操作始于通道,通道是数据来源或数据写入的目的地,主要地, java.nio 包中主要实现的以下几个Channel:FileChannel,SocketChannel,DatagramChannle,ServerSocketChannel。FileChannel:文件通道,用于文件的读和写,DatagramChannel:用于 UDP 连接的接收和发送,SocketChannel:把它理解为 TCP 连接通道,简单理解就是 TCP 客户端,ServerSocketChannel:TCP 对应的服务端,用于监听某个端口进来的请求。NIO读操作就是从Channel读到Buffer,写操作就是把Buffer写到Channel中
  • SelectorSelector是Java NIO中的一个组件,用于检查一个或多个NIO Channel的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接
Netty(一)--java NIO详解
文章图片

selectors是支持IO多路复用的抽象实体,SelectionKey —— 表示Selector和被注册的channel之间关系,一份凭证,SelectionKey 保存channel感兴趣的事件。Selector.select 更新所有就绪的SelectionKey的状态,并返回就绪的channel个数,迭代Selected Key集合并处理就绪channel
Netty(一)--java NIO详解
文章图片

创建Selector(Creating a Selector):
Selector selector = Selector.open();
注册Channel到Selector上(Registering Channels with the Selector):
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
register的第二个参数,这个参数是一个“关注集合”,代表关注的channel状态, 有四种基础类型可供监听, 用SelectionKey中的常量表示如下:SelectionKey.OP_CONNECT SelectionKey.OP_ACCEPT SelectionKey.OP_READ SelectionKey.OP_WRITE
从Selector中选择channel(Selecting Channels via a Selector) 一旦向Selector注册了一个或多个channel后,就可以调用select来获取channel select方法会返回所有处于就绪状态的channel select方法具体如下:int select() int select(long timeout) int selectNow()。select()方法的返回值是一个int,代表有多少channel处于就绪了。也就是自上一次select后有多少channel进入就绪。selectedKeys() 在调用select并返回了有channel就绪之后,可以通过选中的key集合来获取channel,这个操作通过调用selectedKeys()方法:Set selectedKeys = selector.selectedKeys();

NIO带来了什么?
事件驱动模型 避免多线程 单线程处理多任务 非阻塞IO,IO读写不再阻塞,而是返回0 基于block的传输,通常比基于流的传输更高效 更高级的IO函数,zero-copy IO多路复用大大提高了java网络应用的可伸缩性和实用性
NIO Tips
使用NIO = 高性能
NIO不一定更快的场景:客户端应用 连接数<1000 并发程度不高 局域网环境下
NIO完全屏蔽了平台差异(Linux poll/select/epoll, FreeBSD Kqueue) NIO仍然是基于各个OS平台的IO系统实现的,差异仍然存在
使用NIO做网络编程很容易 离散的事件驱动模型
编程困难 陷阱重重

【Netty(一)--java NIO详解】谢谢指正。谢谢!!

    推荐阅读