Netty2 netty与nio

2.1 Java NIO 三件套
在NIO 中有几个核心对象需要掌握:缓冲区(Buffer)、选择器(Selector)、通道(Channel)。
2.1.1 缓冲区Buffer
1.Buffer 操作基本API
缓冲区实际上是一个容器对象,更直接的说,其实就是一个数组,在NIO 库中,所有数据都是用缓冲区处理的。在读
取数据时,它是直接读到缓冲区中的; 在写入数据时,它也是写入到缓冲区中的;任何时候访问NIO 中的数据,都
是将它放到缓冲区中。而在面向流I/O 系统中,所有数据都是直接写入或者直接将数据读取到Stream 对象中。
在NIO 中,所有的缓冲区类型都继承于抽象类Buffer,最常用的就是ByteBuffer,对于Java 中的基本类型,基本都有
一个具体Buffer 类型与之相对应,它们之间的继承关系如下图所示:

Buffer使用demo如下所示: 

 在谈到缓冲区时,我们说缓冲区对象本质上是一个数组,但它其实是一个特殊的数组,缓冲区对象内置了一些机制,
能够跟踪和记录缓冲区的状态变化情况,如果我们使用get()方法从缓冲区获取数据或者使用put()方法把数据写入缓冲
区,都会引起缓冲区状态的变化。
在缓冲区中,最重要的属性有下面三个,它们一起合作完成对缓冲区内部状态的变化跟踪: 

position:指定下一个将要被写入或者读取的元素索引,它的值由get()/put()方法自动更新,在新创建一个Buffer 对象
时,position 被初始化为0。
limit:指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
capacity:指定了可以存储在缓冲区中的最大数据容量,实际上,它指定了底层数组的大小,或者至少是指定了准许我
们使用的底层数组的容量。

以上三个属性值之间有一些相对大小的关系:0 <= position <= limit <= capacity

在NIO 中,除了可以分配或者包装一个缓冲区对象外,还可以根据现有的缓冲区对象来创建一个子缓冲区,即在现有缓冲区上切
出一片来作为一个新的缓冲区,但现有的缓冲区与创建的子缓冲区在底层数组层面上是数据共享的,也就是说,子缓冲区相当于是
现有缓冲区的一个视图窗口。调用slice()方法可以创建一个子缓冲区,

 

在该示例中,分配了一个容量大小为10 的缓冲区,并在其中放入了数据0-9,而在该缓冲区基础之上又创建了一个子缓冲区,并
改变子缓冲区中的内容,从最后输出的结果来看,只有子缓冲区“可见的”那部分数据发生了变化,并且说明子缓冲区与原缓冲区是数据共享的 


2.1.2 选择器Selector
传统的Server/Client 模式会基于TPR(Thread per Request),服务器会为每个客户端请求建立一个线程,由该线程单独负责处理
一个客户请求。这种模式带来的一个问题就是线程数量的剧增,大量的线程会增大服务器的开销。大多数的实现为了避免这个问题,都采用了线程池模型,并设置线程池线程的最大数量,这又带来了新的问题,如果线程池中有200 个线程,而有200 个用户都在进行大文件下载,会导致第201个用户的请求无法及时处理,即便第201 个用户只想请求一个几KB 大小的页面。传统的Server/Client模式如下图所示: 

 NIO 中非阻塞I/O 采用了基于Reactor 模式的工作方式,I/O 调用不会被阻塞,相反是注册感兴趣的特定I/O 事件,如可读数据到
达,新的套接字连接等等,在发生特定事件时,系统再通知我们。NIO 中实现非阻塞I/O 的核心对象就是Selector,Selector 就是
注册各种I/O 事件地方,而且当那些事件发生时,就是这个对象告诉我们所发生的事件,如下图所示:

当有读或写等任何注册的事件发生时,可以从Selector 中获得相应的SelectionKey,同时从SelectionKey 中可
以找到发生的事件和该事件所发生的具体的SelectableChannel,以获得客户端发送过来的数据。
使用NIO 中非阻塞I/O 编写服务器处理程序,大体上可以分为下面三个步骤:
1. 向Selector 对象注册感兴趣的事件。
2. 从Selector 中获取感兴趣的事件。
3. 根据不同的事件进行相应的处理。

2.3.3 通道Channel
通道是一个对象,通过它可以读取和写入数据,当然了所有数据都通过Buffer 对象来处理。我们永远不会将字节直接 写入通道中,相反是将数据写入包含一个或者多个字节的缓冲区。同样不会直接从通道中读取字节,而是将数据从通
道读入缓冲区,再从缓冲区获取这个字节。
在NIO 中,提供了多种通道对象,而所有的通道对象都实现了Channel 接口。它们之间的继承关系如下图所示:

 

1.使用NIO 读取数据
在前面我们说过,任何时候读取数据,都不是直接从通道读取,而是从通道读取到缓冲区。所以使用NIO 读取数据可
以分为下面三个步骤:
1. 从FileInputStream 获取Channel
2. 创建Buffer
3. 将数据从Channel 读取到Buffer 中 

2.使用NIO 写入数据
使用NIO 写入数据与读取数据的过程类似,同样数据不是直接写入通道,而是写入缓冲区,可以分为下面三个步骤:
1. 从FileInputStream 获取Channel。

2. 创建Buffer。
3. 将数据从Channel 写入到Buffer 中。

 

3、Reactor

根据阻塞I/O 通信模型,我总结了它的两点缺点:
1. 当客户端多时,会创建大量的处理线程。且每个线程都要占用栈空间和一些CPU 时间
2. 阻塞可能带来频繁的上下文切换,且大部分上下文切换可能是无意义的。在这种情况下非阻塞式I/O 就有了它的应
用前景。
Java NIO 是在jdk1.4 开始使用的,它既可以说成“新I/O”,也可以说成非阻塞式I/O。下面是Java NIO 的工作原理:
1. 由一个专门的线程来处理所有的IO 事件,并负责分发。
2. 事件驱动机制:事件到的时候触发,而不是同步的去监视事件。
3. 线程通讯:线程之间通过wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。 

Java NIO 反应堆的工作原理图:


版权声明:本文为u014755700原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。