nio
1.概念
NIO代表的一个词汇叫着IO多路复用。它是由操作系统提供的系统调用,早期这个操作系统调用的名字是select,但是性能低下,后来渐渐演化成了Linux下的epoll和Mac里的kqueue。我们一般就说是epoll,因为没有人拿苹果电脑作为服务器使用对外提供服务。而Netty就是基于Java NIO技术封装的一套框架
为什么要封装,因为原生的Java NIO使用起来没那么方便,而且还有臭名昭著的bug,Netty把它封装之后,提供了一个易于操作的使用模式和接口,用户使用起来也就便捷多了。
2.BIO与NIO
2.1BIO
说NIO之前先说一下BIO(Blocking IO),如何理解这个Blocking呢?
传统的BIO模式下,从头到尾的所有线程都是阻塞的,这些线程就干等着,占用系统的资源,什么事也不干。
2.2 NIO
那么NIO是怎么做到非阻塞的呢。它用的是事件机制。它可以用一个线程把Accept,读写操作,请求处理的逻辑全干了。如果什么事都没得做,它也不会死循环,它会将线程休眠起来,直到下一个事件来了再继续干活,这样的一个线程称之为NIO线程。用伪代码表示:
while true {
events = takeEvents(fds) // 获取事件,如果没有事件,线程就休眠
for event in events {
if event.isAcceptable {
doAccept() // 新链接来了
} elif event.isReadable {
request = doRead() // 读消息
if request.isComplete() {
doProcess()
}
} elif event.isWriteable {
doWrite() // 写消息
}
}
}
2.3NIO与IO的区别
IO | NIO |
面向流(Stream Oriented) | 面向缓冲区 |
阻塞IO | 非阻塞IO |
无 | 选择器Selector |
3.NIO详解
3.1 组成
Buffer:
- 缓存数组,就是一个内存块,底层用数组实现
- 与Channel进行数据的读写。
- 数据的读取写入是通过Buffer, 这个和BIO 一样, 而BIO 中要么是输入流,或者是输出流, 不能双向,但是NIO的Buffer 是可以读也可以写, 需要 flip 方法切换。
Channel:
- 通信通道,每个客户端连接都会建立一个Channel通道
- 我的理解是:客户端直接与Channel进行通信,当客户端发送消息时,消息就流通到Channel里面,本地程序需要将Channel里面的数据存放在Buffer里面,才可以查看;当本地需要发送消息时,先把消息存在Buffer里面,再将Buffer里面的数据放入Channel,数据就流通到了客户端
- 总而言之:
Buffer
就是本地程序与Channel
数据交换的一个中间媒介
SelectionKey、Selector:
- NIO之所以是非阻塞的,关键在于它一个线程可以同时处理多个客户端的通信。而
Selector
就是它一个线程如何处理多个客户端通信的关键,一个Selector
就对应一个线程 - 首先在创建与客户端连接的Channel时,应该调用 Channel.register()方法,将Channel注册到一个Selector上面。调用该方法后,会返回一个SelectionKey对象,该对象与Channel是一一对应的。而Selector则通过管理SelectionKey的集合间接的去管理各个Channel。示例图如下:
Selector
具体如何管理这么多个通信的呢?这就引出了事件
事件、以及NIO的工作流程介绍
- **事件:**当将Channel绑定到Selector上面时,必须同时为该Channel声明一个监听该Channel的事件(由Channel和该Channel的事件一起组成了SelectionKey),并将SelectionKey加入到Selector的Set集合中去
- 当有客户端建立连接或者进行通信,会在对应的各个Channel中产生不同的事件。
- Selector会一直监听所有的事件,当他监听到某个SelectionKey中有事件产生时,会将所有产生事件的SelectionKey统一加入到一个集合中去
- 而我们则需要获取到这个集合,首先对集合中的各个SelectionKey进行判断,判断它产生的是什么事件,再根据不同的事件进行不同的处理。
- 在操作这个SelectionKey集合的时候,其实我们就是在一个线程里面对几个不同客户端的连接进行操作。具体的关系图如下:
3.2缓冲区(Buffer)
基本介绍
缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由Buffer。
Buffer类介绍
- 基类是Buffer抽象类
- 基类派生出基于基本数据类型的7个xxxBuffer 抽象类,没有boolean相关的buffer类。
- 除了ByteBuffer外,每个基本数据的抽象类 xxxBuffer 类下面都派生出转向 ByteBuffer 的类 ByteBufferXxxAsBufferL 和 ByteBufferAsXxxBufferB实现类;以及 DirectXxxBufferU 和 DirectXxxBufferS 和 HeapXxxBuffer==(具体实例对象类)==这五个类。
- 就只有抽象类CharBuffer 派生出了第六个类StringCharBuffer。
- ByteBuffer只派生出了 HeapByteBuffer 和 MappedByteBufferR 两个类
Buffer类主要属性
属性 | 描述 |
Capacity | 容量,即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变 |
Limit | 表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作。且极限是可以修改的 |
Position | 位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变改值,为下次读写作准备 |
Mark | 标记 ,一般不会主动修改,在flip() 被调用后,mark就作废了。 |
mark